diff --git a/qt-6.7.0-win.patch b/qt-6.7.0-win.patch
new file mode 100755
index 0000000000..fab2914b84
--- /dev/null
+++ b/qt-6.7.0-win.patch
@@ -0,0 +1,1449 @@
+diff --git a/qtbase/src/corelib/io/qstandardpaths_win.cpp b/qtbase/src/corelib/io/qstandardpaths_win.cpp
+index 805ce65a5a..aade9d5f97 100644
+--- a/qtbase/src/corelib/io/qstandardpaths_win.cpp
++++ b/qtbase/src/corelib/io/qstandardpaths_win.cpp
+@@ -12,6 +12,7 @@
+ 
+ #include <qt_windows.h>
+ #include <shlobj.h>
++#include <VersionHelpers.h>
+ #include <intshcut.h>
+ #include <qvarlengtharray.h>
+ 
+@@ -60,20 +61,26 @@ static inline void appendTestMode(QString &path)
+ 
+ static bool isProcessLowIntegrity()
+ {
++    if (!IsWindows8OrGreater())
++        return false;
++
+     // same as GetCurrentProcessToken()
+     const auto process_token = HANDLE(quintptr(-4));
+ 
+     QVarLengthArray<char,256> token_info_buf(256);
+     auto* token_info = reinterpret_cast<TOKEN_MANDATORY_LABEL*>(token_info_buf.data());
+     DWORD token_info_length = token_info_buf.size();
+-    if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length)) {
++    if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length)
++        && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+         // grow buffer and retry GetTokenInformation
+         token_info_buf.resize(token_info_length);
+         token_info = reinterpret_cast<TOKEN_MANDATORY_LABEL*>(token_info_buf.data());
+         if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length))
+             return false; // assume "normal" process
+     }
+-
++    else
++        return false;
++    
+     // The GetSidSubAuthorityCount return-code is undefined on failure, so
+     // there's no point in checking before dereferencing
+     DWORD integrity_level = *GetSidSubAuthority(token_info->Label.Sid, *GetSidSubAuthorityCount(token_info->Label.Sid) - 1);
+diff --git a/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp b/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp
+index a7663b2481..8459cd5cad 100644
+--- a/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp
++++ b/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp
+@@ -360,10 +360,15 @@ void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
+         ok = t->fastTimerId;
+     }
+ 
+-    if (!ok) {
++    typedef BOOL (WINAPI *SetCoalescableTimerFunc) (HWND, UINT_PTR, UINT, TIMERPROC, ULONG);
++    static SetCoalescableTimerFunc mySetCoalescableTimerFunc = 
++        (SetCoalescableTimerFunc)::GetProcAddress(::GetModuleHandle(L"User32"), "SetCoalescableTimer");
++
++    if (!ok && mySetCoalescableTimerFunc) {
+         // user normal timers for (Very)CoarseTimers, or if no more multimedia timers available
+-        ok = SetCoalescableTimer(internalHwnd, t->timerId, interval, nullptr, tolerance);
++        ok = mySetCoalescableTimerFunc(internalHwnd, t->timerId, interval, nullptr, tolerance);
+     }
++    
+     if (!ok)
+         ok = SetTimer(internalHwnd, t->timerId, interval, nullptr);
+ 
+diff --git a/qtbase/src/corelib/kernel/qfunctions_win.cpp b/qtbase/src/corelib/kernel/qfunctions_win.cpp
+index d5ce3e5894..70b67ea01f 100644
+--- a/qtbase/src/corelib/kernel/qfunctions_win.cpp
++++ b/qtbase/src/corelib/kernel/qfunctions_win.cpp
+@@ -43,23 +43,33 @@ QComHelper::~QComHelper()
+ */
+ bool qt_win_hasPackageIdentity()
+ {
++    typedef BOOL (WINAPI *GetCurrentPackageFullNameFunc) (UINT32 *, PWSTR);
++    static GetCurrentPackageFullNameFunc myGetCurrentPackageFullName = 
++        (GetCurrentPackageFullNameFunc)::GetProcAddress(::GetModuleHandle(L"kernel32"), "GetCurrentPackageFullName");
++
++    if (myGetCurrentPackageFullName)
++    {
+ #if defined(HAS_APPMODEL)
+-    static const bool hasPackageIdentity = []() {
+-        UINT32 length = 0;
+-        switch (const auto result = GetCurrentPackageFullName(&length, nullptr)) {
+-        case ERROR_INSUFFICIENT_BUFFER:
+-            return true;
+-        case APPMODEL_ERROR_NO_PACKAGE:
+-            return false;
+-        default:
+-            qWarning("Failed to resolve package identity (error code %ld)", result);
+-            return false;
+-        }
+-    }();
+-    return hasPackageIdentity;
++
++        static const bool hasPackageIdentity = []() {
++            UINT32 length = 0;
++            switch (const auto result = myGetCurrentPackageFullName(&length, nullptr)) {
++            case ERROR_INSUFFICIENT_BUFFER:
++                return true;
++            case APPMODEL_ERROR_NO_PACKAGE:
++                return false;
++            default:
++                qWarning("Failed to resolve package identity (error code %ld)", result);
++                return false;
++            }
++        }();
++        return hasPackageIdentity;
+ #else
+-    return false;
++        return false;
+ #endif
++    }
++
++    return false;
+ }
+ 
+ QT_END_NAMESPACE
+diff --git a/qtbase/src/corelib/thread/qfutex_p.h b/qtbase/src/corelib/thread/qfutex_p.h
+index 8ba798f920..639946b00b 100644
+--- a/qtbase/src/corelib/thread/qfutex_p.h
++++ b/qtbase/src/corelib/thread/qfutex_p.h
+@@ -40,8 +40,6 @@ QT_END_NAMESPACE
+ #elif defined(Q_OS_LINUX) && !defined(QT_LINUXBASE)
+ // use Linux mutexes everywhere except for LSB builds
+ #  include "qfutex_linux_p.h"
+-#elif defined(Q_OS_WIN)
+-#  include "qfutex_win_p.h"
+ #else
+ QT_BEGIN_NAMESPACE
+ namespace QtFutex = QtDummyFutex;
+diff --git a/qtbase/src/corelib/thread/qmutex.cpp b/qtbase/src/corelib/thread/qmutex.cpp
+index ec6c711a4f..71ef29e675 100644
+--- a/qtbase/src/corelib/thread/qmutex.cpp
++++ b/qtbase/src/corelib/thread/qmutex.cpp
+@@ -910,8 +910,10 @@ QT_END_NAMESPACE
+ 
+ #if defined(QT_ALWAYS_USE_FUTEX)
+ // nothing
+-#elif defined(Q_OS_DARWIN)
++#elif defined(Q_OS_MAC)
+ #  include "qmutex_mac.cpp"
++#elif defined(Q_OS_WIN)
++#  include "qmutex_win.cpp"
+ #else
+ #  include "qmutex_unix.cpp"
+-#endif
++#endif
+\ No newline at end of file
+diff --git a/qtbase/src/corelib/thread/qmutex_p.h b/qtbase/src/corelib/thread/qmutex_p.h
+index aabb66fa55..a16d508215 100644
+--- a/qtbase/src/corelib/thread/qmutex_p.h
++++ b/qtbase/src/corelib/thread/qmutex_p.h
+@@ -86,6 +86,8 @@ public:
+     semaphore_t mach_semaphore;
+ #elif defined(Q_OS_UNIX)
+     sem_t semaphore;
++#elif defined(Q_OS_WIN)
++    Qt::HANDLE event;
+ #endif
+ };
+ 
+diff --git a/qtbase/src/gui/rhi/qrhid3d11.cpp b/qtbase/src/gui/rhi/qrhid3d11.cpp
+index 92e4cacc16..dac5655161 100644
+--- a/qtbase/src/gui/rhi/qrhid3d11.cpp
++++ b/qtbase/src/gui/rhi/qrhid3d11.cpp
+@@ -10,6 +10,8 @@
+ #include <QtCore/private/qsystemerror_p.h>
+ #include "qrhid3dhelpers_p.h"
+ 
++#include <VersionHelpers.h>
++
+ QT_BEGIN_NAMESPACE
+ 
+ using namespace Qt::StringLiterals;
+@@ -155,13 +157,22 @@ inline Int aligned(Int v, Int byteAlign)
+ 
+ static IDXGIFactory1 *createDXGIFactory2()
+ {
++    typedef HRESULT(WINAPI* CreateDXGIFactory2Func) (UINT flags, REFIID riid, void** factory);
++    static CreateDXGIFactory2Func myCreateDXGIFactory2 =
++        (CreateDXGIFactory2Func)::GetProcAddress(::GetModuleHandle(L"dxgi"), "CreateDXGIFactory2");
++
+     IDXGIFactory1 *result = nullptr;
+-    const HRESULT hr = CreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&result));
+-    if (FAILED(hr)) {
+-        qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s",
+-            qPrintable(QSystemError::windowsComString(hr)));
+-        result = nullptr;
++
++    if (myCreateDXGIFactory2)
++    {
++        const HRESULT hr = myCreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&result));
++        if (FAILED(hr)) {
++            qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s",
++                qPrintable(QSystemError::windowsComString(hr)));
++            result = nullptr;
++        }
+     }
++    
+     return result;
+ }
+ 
+@@ -5029,6 +5040,14 @@ static const DXGI_FORMAT DEFAULT_SRGB_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
+ 
+ bool QD3D11SwapChain::createOrResize()
+ {
++    if (IsWindows10OrGreater())
++    {
++        // continue
++    }
++    else
++    {
++        return createOrResizeWin7();
++    }
+     // Can be called multiple times due to window resizes - that is not the
+     // same as a simple destroy+create (as with other resources). Just need to
+     // resize the buffers then.
+@@ -5327,4 +5346,9 @@ bool QD3D11SwapChain::createOrResize()
+     return true;
+ }
+ 
++bool QD3D11SwapChain::createOrResizeWin7()
++{
++    return false; // not implemented yet ;(
++}
++
+ QT_END_NAMESPACE
+diff --git a/qtbase/src/gui/rhi/qrhid3d11_p.h b/qtbase/src/gui/rhi/qrhid3d11_p.h
+index 7644748407..e23529dd24 100644
+--- a/qtbase/src/gui/rhi/qrhid3d11_p.h
++++ b/qtbase/src/gui/rhi/qrhid3d11_p.h
+@@ -596,7 +596,8 @@ struct QD3D11SwapChain : public QRhiSwapChain
+ 
+     QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+     bool createOrResize() override;
+-
++    bool createOrResizeWin7();
++    
+     void releaseBuffers();
+     bool newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI_SAMPLE_DESC sampleDesc,
+                         ID3D11Texture2D **tex, ID3D11RenderTargetView **rtv) const;
+diff --git a/qtbase/src/gui/rhi/qrhid3d12.cpp b/qtbase/src/gui/rhi/qrhid3d12.cpp
+index ce9c5dcbdb..f87867353c 100644
+--- a/qtbase/src/gui/rhi/qrhid3d12.cpp
++++ b/qtbase/src/gui/rhi/qrhid3d12.cpp
+@@ -174,19 +174,35 @@ static inline QD3D12RenderTargetData *rtData(QRhiRenderTarget *rt)
+ 
+ bool QRhiD3D12::create(QRhi::Flags flags)
+ {
++    typedef HRESULT(WINAPI* CreateDXGIFactory2Func) (UINT flags, REFIID riid, void** factory);
++    typedef HRESULT(WINAPI* D3D12CreateDeviceFunc) (IUnknown *, D3D_FEATURE_LEVEL, REFIID, void **);
++    typedef HRESULT(WINAPI* D3D12GetDebugInterfaceFunc) (REFIID, void **);
++
++    static CreateDXGIFactory2Func myCreateDXGIFactory2 =
++        (CreateDXGIFactory2Func)::GetProcAddress(::GetModuleHandle(L"dxgi"), "CreateDXGIFactory2");
++
++    static D3D12CreateDeviceFunc myD3D12CreateDevice =
++        (D3D12CreateDeviceFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12CreateDevice");
++
++    static D3D12GetDebugInterfaceFunc myD3D12GetDebugInterface =
++        (D3D12GetDebugInterfaceFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12GetDebugInterface");
++
++    if (!myCreateDXGIFactory2 || !myD3D12CreateDevice || !myD3D12GetDebugInterface)
++        return false;
++
+     rhiFlags = flags;
+ 
+     UINT factoryFlags = 0;
+     if (debugLayer)
+         factoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
+-    HRESULT hr = CreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
++    HRESULT hr = myCreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
+     if (FAILED(hr)) {
+         // retry without debug, if it was requested (to match D3D11 backend behavior)
+         if (debugLayer) {
+             qCDebug(QRHI_LOG_INFO, "Debug layer was requested but is not available. "
+                                    "Attempting to create DXGIFactory2 without it.");
+             factoryFlags &= ~DXGI_CREATE_FACTORY_DEBUG;
+-            hr = CreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
++            hr = myCreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
+         }
+         if (SUCCEEDED(hr)) {
+             debugLayer = false;
+@@ -208,7 +224,7 @@ bool QRhiD3D12::create(QRhi::Flags flags)
+ 
+     if (debugLayer) {
+         ID3D12Debug1 *debug = nullptr;
+-        if (SUCCEEDED(D3D12GetDebugInterface(__uuidof(ID3D12Debug1), reinterpret_cast<void **>(&debug)))) {
++        if (SUCCEEDED(myD3D12GetDebugInterface(__uuidof(ID3D12Debug1), reinterpret_cast<void **>(&debug)))) {
+             qCDebug(QRHI_LOG_INFO, "Enabling D3D12 debug layer");
+             debug->EnableDebugLayer();
+             debug->Release();
+@@ -276,7 +292,7 @@ bool QRhiD3D12::create(QRhi::Flags flags)
+         if (minimumFeatureLevel == 0)
+             minimumFeatureLevel = MIN_FEATURE_LEVEL;
+ 
+-        hr = D3D12CreateDevice(activeAdapter,
++        hr = myD3D12CreateDevice(activeAdapter,
+                                minimumFeatureLevel,
+                                __uuidof(ID3D12Device2),
+                                reinterpret_cast<void **>(&dev));
+@@ -2713,6 +2729,9 @@ QD3D12Descriptor QD3D12SamplerManager::getShaderVisibleDescriptor(const D3D12_SA
+     return descriptor;
+ }
+ 
++typedef HRESULT(WINAPI* D3D12SerializeVersionedRootSignatureFunc) (const D3D12_VERSIONED_ROOT_SIGNATURE_DESC *, ID3DBlob **, ID3DBlob **);
++D3D12SerializeVersionedRootSignatureFunc myD3D12SerializeVersionedRootSignature = nullptr;
++
+ bool QD3D12MipmapGenerator::create(QRhiD3D12 *rhiD)
+ {
+     this->rhiD = rhiD;
+@@ -2758,8 +2777,15 @@ bool QD3D12MipmapGenerator::create(QRhiD3D12 *rhiD)
+     rsDesc.Desc_1_1.NumStaticSamplers = 1;
+     rsDesc.Desc_1_1.pStaticSamplers = &samplerDesc;
+ 
++    if (!myD3D12SerializeVersionedRootSignature)
++        myD3D12SerializeVersionedRootSignature =
++        (D3D12SerializeVersionedRootSignatureFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12SerializeVersionedRootSignature");
++
++    if (!myD3D12SerializeVersionedRootSignature)
++        return false;
++
+     ID3DBlob *signature = nullptr;
+-    HRESULT hr = D3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
++    HRESULT hr = myD3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
+     if (FAILED(hr)) {
+         qWarning("Failed to serialize root signature: %s", qPrintable(QSystemError::windowsComString(hr)));
+         return false;
+@@ -5013,8 +5039,15 @@ QD3D12ObjectHandle QD3D12ShaderResourceBindings::createRootSignature(const QD3D1
+     }
+     rsDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAGS(rsFlags);
+ 
++    if (!myD3D12SerializeVersionedRootSignature)
++        myD3D12SerializeVersionedRootSignature =
++        (D3D12SerializeVersionedRootSignatureFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12SerializeVersionedRootSignature");
++
++    if (!myD3D12SerializeVersionedRootSignature)
++        return {};
++
+     ID3DBlob *signature = nullptr;
+-    HRESULT hr = D3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
++    HRESULT hr = myD3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
+     if (FAILED(hr)) {
+         qWarning("Failed to serialize root signature: %s", qPrintable(QSystemError::windowsComString(hr)));
+         return {};
+diff --git a/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp b/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp
+index cef739201b..fe2070a069 100644
+--- a/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp
++++ b/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp
+@@ -704,7 +704,16 @@ QFont QWindowsFontDatabaseBase::systemDefaultFont()
+     // Qt 6: Obtain default GUI font (typically "Segoe UI, 9pt", see QTBUG-58610)
+     NONCLIENTMETRICS ncm = {};
+     ncm.cbSize = sizeof(ncm);
+-    SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0, defaultVerticalDPI());
++
++    typedef BOOL (WINAPI *SystemParametersInfoForDpiFunc) (UINT, UINT, PVOID, UINT, UINT);
++    static SystemParametersInfoForDpiFunc mySystemParametersInfoForDpi = 
++        (SystemParametersInfoForDpiFunc)::GetProcAddress(::GetModuleHandle(L"user32"), "SystemParametersInfoForDpi");
++
++    if (mySystemParametersInfoForDpi)
++        mySystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0, defaultVerticalDPI());
++    else
++        SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize , &ncm, 0);
++
+     const QFont systemFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont);
+     qCDebug(lcQpaFonts) << __FUNCTION__ << systemFont;
+     return systemFont;
+diff --git a/qtbase/src/network/kernel/qdnslookup_win.cpp b/qtbase/src/network/kernel/qdnslookup_win.cpp
+index 72d5ae5c86..5410edab88 100644
+--- a/qtbase/src/network/kernel/qdnslookup_win.cpp
++++ b/qtbase/src/network/kernel/qdnslookup_win.cpp
+@@ -65,35 +65,101 @@ QT_BEGIN_NAMESPACE
+ 
+ void QDnsLookupRunnable::query(QDnsLookupReply *reply)
+ {
+-    // Perform DNS query.
+-    alignas(DNS_ADDR_ARRAY) uchar dnsAddresses[sizeof(DNS_ADDR_ARRAY) + sizeof(DNS_ADDR)];
+-    DNS_QUERY_REQUEST request = {};
+-    request.Version = 1;
+-    request.QueryName = reinterpret_cast<const wchar_t *>(requestName.constData());
+-    request.QueryType = requestType;
+-    request.QueryOptions = DNS_QUERY_STANDARD | DNS_QUERY_TREAT_AS_FQDN;
+-
+-    if (!nameserver.isNull()) {
+-        memset(dnsAddresses, 0, sizeof(dnsAddresses));
+-        request.pDnsServerList = new (dnsAddresses) DNS_ADDR_ARRAY;
+-        auto addr = new (request.pDnsServerList->AddrArray) DNS_ADDR[1];
+-        auto sa = new (addr[0].MaxSa) sockaddr;
+-        request.pDnsServerList->MaxCount = sizeof(dnsAddresses);
+-        request.pDnsServerList->AddrCount = 1;
+-        // ### setting port 53 seems to cause some systems to fail
+-        setSockaddr(sa, nameserver, port == DnsPort ? 0 : port);
+-        request.pDnsServerList->Family = sa->sa_family;
++    typedef BOOL (WINAPI *DnsQueryExFunc) (PDNS_QUERY_REQUEST, PDNS_QUERY_RESULT, PDNS_QUERY_CANCEL);
++    static DnsQueryExFunc myDnsQueryEx = 
++        (DnsQueryExFunc)::GetProcAddress(::GetModuleHandle(L"Dnsapi"), "DnsQueryEx");
++
++    PDNS_RECORD ptrStart = nullptr;
++
++    if (myDnsQueryEx)
++    {
++        // Perform DNS query.
++        alignas(DNS_ADDR_ARRAY) uchar dnsAddresses[sizeof(DNS_ADDR_ARRAY) + sizeof(DNS_ADDR)];
++        DNS_QUERY_REQUEST request = {};
++        request.Version = 1;
++        request.QueryName = reinterpret_cast<const wchar_t *>(requestName.constData());
++        request.QueryType = requestType;
++        request.QueryOptions = DNS_QUERY_STANDARD | DNS_QUERY_TREAT_AS_FQDN;
++
++        if (!nameserver.isNull()) {
++            memset(dnsAddresses, 0, sizeof(dnsAddresses));
++            request.pDnsServerList = new (dnsAddresses) DNS_ADDR_ARRAY;
++            auto addr = new (request.pDnsServerList->AddrArray) DNS_ADDR[1];
++            auto sa = new (addr[0].MaxSa) sockaddr;
++            request.pDnsServerList->MaxCount = sizeof(dnsAddresses);
++            request.pDnsServerList->AddrCount = 1;
++            // ### setting port 53 seems to cause some systems to fail
++            setSockaddr(sa, nameserver, port == DnsPort ? 0 : port);
++            request.pDnsServerList->Family = sa->sa_family;
++        }
++
++        DNS_QUERY_RESULT results = {};
++        results.Version = 1;
++        const DNS_STATUS status = myDnsQueryEx(&request, &results, nullptr);
++        if (status >= DNS_ERROR_RCODE_FORMAT_ERROR && status <= DNS_ERROR_RCODE_LAST)
++            return reply->makeDnsRcodeError(status - DNS_ERROR_RCODE_FORMAT_ERROR + 1);
++        else if (status == ERROR_TIMEOUT)
++            return reply->makeTimeoutError();
++        else if (status != ERROR_SUCCESS)
++            return reply->makeResolverSystemError(status);
++
++        ptrStart = results.pQueryRecords;
+     }
++    else
++    {
++        // Perform DNS query.
++        PDNS_RECORD dns_records = 0;
++        QByteArray requestNameUTF8 = requestName.toUtf8();
++        const QString requestNameUtf16 = QString::fromUtf8(requestNameUTF8.data(), requestNameUTF8.size());
++        IP4_ARRAY srvList;
++        memset(&srvList, 0, sizeof(IP4_ARRAY));
++        if (!nameserver.isNull()) {
++            if (nameserver.protocol() == QAbstractSocket::IPv4Protocol) {
++                // The below code is referenced from: http://support.microsoft.com/kb/831226
++                srvList.AddrCount = 1;
++                srvList.AddrArray[0] = htonl(nameserver.toIPv4Address());
++            } else if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) {
++                // For supporting IPv6 nameserver addresses, we'll need to switch
++                // from DnsQuey() to DnsQueryEx() as it supports passing an IPv6
++                // address in the nameserver list
++                qWarning("%s", "IPv6 addresses for nameservers are currently not supported");
++                reply->error = QDnsLookup::ResolverError;
++                reply->errorString = tr("IPv6 addresses for nameservers are currently not supported");
++                return;
++            }
++        }
++        const DNS_STATUS status = DnsQuery_W(reinterpret_cast<const wchar_t*>(requestNameUtf16.utf16()), requestType, DNS_QUERY_STANDARD, &srvList, &dns_records, NULL);
++        switch (status) {
++        case ERROR_SUCCESS:
++            break;
++        case DNS_ERROR_RCODE_FORMAT_ERROR:
++            reply->error = QDnsLookup::InvalidRequestError;
++            reply->errorString = tr("Server could not process query");
++            return;
++        case DNS_ERROR_RCODE_SERVER_FAILURE:
++        case DNS_ERROR_RCODE_NOT_IMPLEMENTED:
++            reply->error = QDnsLookup::ServerFailureError;
++            reply->errorString = tr("Server failure");
++            return;
++        case DNS_ERROR_RCODE_NAME_ERROR:
++            reply->error = QDnsLookup::NotFoundError;
++            reply->errorString = tr("Non existent domain");
++            return;
++        case DNS_ERROR_RCODE_REFUSED:
++            reply->error = QDnsLookup::ServerRefusedError;
++            reply->errorString = tr("Server refused to answer");
++            return;
++        default:
++            reply->error = QDnsLookup::InvalidReplyError;
++            reply->errorString = QSystemError(status, QSystemError::NativeError).toString();
++            return;
++        }
+ 
+-    DNS_QUERY_RESULT results = {};
+-    results.Version = 1;
+-    const DNS_STATUS status = DnsQueryEx(&request, &results, nullptr);
+-    if (status >= DNS_ERROR_RCODE_FORMAT_ERROR && status <= DNS_ERROR_RCODE_LAST)
+-        return reply->makeDnsRcodeError(status - DNS_ERROR_RCODE_FORMAT_ERROR + 1);
+-    else if (status == ERROR_TIMEOUT)
+-        return reply->makeTimeoutError();
+-    else if (status != ERROR_SUCCESS)
+-        return reply->makeResolverSystemError(status);
++        ptrStart = dns_records;
++    }
++
++    if (!ptrStart)
++        return;
+ 
+     QStringView lastEncodedName;
+     QString cachedDecodedName;
+@@ -105,9 +171,9 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply)
+     auto extractMaybeCachedHost = [&](QStringView name) -> const QString & {
+         return lastEncodedName == name ? cachedDecodedName : extractAndCacheHost(name);
+     };
+-
++    
+     // Extract results.
+-    for (PDNS_RECORD ptr = results.pQueryRecords; ptr != NULL; ptr = ptr->pNext) {
++    for (PDNS_RECORD ptr = ptrStart; ptr != NULL; ptr = ptr->pNext) {
+         // warning: always assign name to the record before calling extractXxxHost() again
+         const QString &name = extractMaybeCachedHost(ptr->pName);
+         if (ptr->wType == QDnsLookup::A) {
+@@ -170,7 +236,7 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply)
+         }
+     }
+ 
+-    DnsRecordListFree(results.pQueryRecords, DnsFreeRecordList);
++    DnsRecordListFree(ptrStart, DnsFreeRecordList);
+ }
+ 
+ QT_END_NAMESPACE
+diff --git a/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp b/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp
+index 026e81cb0c..2708424c30 100644
+--- a/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp
++++ b/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp
+@@ -58,43 +58,73 @@ public:
+ } // namespace ABI
+ #endif // HAS_UI_VIEW_SETTINGS
+ 
++// based on SDL approach
++// see SDL_windows_gaming_input.c, SDL_windows.c
++
++void * WIN_LoadComBaseFunction(const char *name)
++{
++    static bool s_bLoaded = false;
++    static HMODULE s_hComBase = NULL;
++   
++    if (!s_bLoaded) {
++       s_hComBase = ::LoadLibraryEx(L"combase.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
++       s_bLoaded = true;
++    }
++    if (s_hComBase) {
++        return (void*)::GetProcAddress(s_hComBase, name);
++    } else {
++        return NULL;
++    }
++}
++
+ QT_BEGIN_NAMESPACE
+ 
+ // Return tablet mode, note: Does not work for GetDesktopWindow().
+ bool qt_windowsIsTabletMode(HWND hwnd)
+ {
+-    bool result = false;
+-
+-    const wchar_t uiViewSettingsId[] = L"Windows.UI.ViewManagement.UIViewSettings";
+-    HSTRING_HEADER uiViewSettingsIdRefHeader;
+-    HSTRING uiViewSettingsIdHs = nullptr;
+-    const auto uiViewSettingsIdLen = UINT32(sizeof(uiViewSettingsId) / sizeof(uiViewSettingsId[0]) - 1);
+-    if (FAILED(WindowsCreateStringReference(uiViewSettingsId, uiViewSettingsIdLen, &uiViewSettingsIdRefHeader, &uiViewSettingsIdHs)))
+-        return false;
+-
+-    IUIViewSettingsInterop *uiViewSettingsInterop = nullptr;
+-    // __uuidof(IUIViewSettingsInterop);
+-    const GUID uiViewSettingsInteropRefId = {0x3694dbf9, 0x8f68, 0x44be,{0x8f, 0xf5, 0x19, 0x5c, 0x98, 0xed, 0xe8, 0xa6}};
+-
+-    HRESULT hr = RoGetActivationFactory(uiViewSettingsIdHs, uiViewSettingsInteropRefId,
+-                                                   reinterpret_cast<void **>(&uiViewSettingsInterop));
+-    if (FAILED(hr))
+-        return false;
+-
+-    //  __uuidof(ABI::Windows::UI::ViewManagement::IUIViewSettings);
+-    const GUID uiViewSettingsRefId = {0xc63657f6, 0x8850, 0x470d,{0x88, 0xf8, 0x45, 0x5e, 0x16, 0xea, 0x2c, 0x26}};
+-    ABI::Windows::UI::ViewManagement::IUIViewSettings *viewSettings = nullptr;
+-    hr = uiViewSettingsInterop->GetForWindow(hwnd, uiViewSettingsRefId,
+-                                             reinterpret_cast<void **>(&viewSettings));
+-    if (SUCCEEDED(hr)) {
+-        ABI::Windows::UI::ViewManagement::UserInteractionMode currentMode;
+-        hr = viewSettings->get_UserInteractionMode(&currentMode);
+-        if (SUCCEEDED(hr))
+-            result = currentMode == 1; // Touch, 1
+-        viewSettings->Release();
++    typedef HRESULT (WINAPI *WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING* string);
++    typedef HRESULT (WINAPI *RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void** factory);
++
++    WindowsCreateStringReference_t WindowsCreateStringReferenceFunc = (WindowsCreateStringReference_t)WIN_LoadComBaseFunction("WindowsCreateStringReference");
++    RoGetActivationFactory_t RoGetActivationFactoryFunc = (RoGetActivationFactory_t)WIN_LoadComBaseFunction("RoGetActivationFactory");
++
++    if (WindowsCreateStringReferenceFunc && RoGetActivationFactoryFunc)
++    {
++        bool result = false;
++
++        const wchar_t uiViewSettingsId[] = L"Windows.UI.ViewManagement.UIViewSettings";
++        HSTRING_HEADER uiViewSettingsIdRefHeader;
++        HSTRING uiViewSettingsIdHs = nullptr;
++        const auto uiViewSettingsIdLen = UINT32(sizeof(uiViewSettingsId) / sizeof(uiViewSettingsId[0]) - 1);
++        if (FAILED(WindowsCreateStringReferenceFunc(uiViewSettingsId, uiViewSettingsIdLen, &uiViewSettingsIdRefHeader, &uiViewSettingsIdHs)))
++            return false;
++
++        IUIViewSettingsInterop *uiViewSettingsInterop = nullptr;
++        // __uuidof(IUIViewSettingsInterop);
++        const GUID uiViewSettingsInteropRefId = {0x3694dbf9, 0x8f68, 0x44be,{0x8f, 0xf5, 0x19, 0x5c, 0x98, 0xed, 0xe8, 0xa6}};
++
++        HRESULT hr = RoGetActivationFactoryFunc(uiViewSettingsIdHs, uiViewSettingsInteropRefId,
++                                                       reinterpret_cast<void **>(&uiViewSettingsInterop));
++        if (FAILED(hr))
++            return false;
++
++        //  __uuidof(ABI::Windows::UI::ViewManagement::IUIViewSettings);
++        const GUID uiViewSettingsRefId = {0xc63657f6, 0x8850, 0x470d,{0x88, 0xf8, 0x45, 0x5e, 0x16, 0xea, 0x2c, 0x26}};
++        ABI::Windows::UI::ViewManagement::IUIViewSettings *viewSettings = nullptr;
++        hr = uiViewSettingsInterop->GetForWindow(hwnd, uiViewSettingsRefId,
++                                                 reinterpret_cast<void **>(&viewSettings));
++        if (SUCCEEDED(hr)) {
++            ABI::Windows::UI::ViewManagement::UserInteractionMode currentMode;
++            hr = viewSettings->get_UserInteractionMode(&currentMode);
++            if (SUCCEEDED(hr))
++                result = currentMode == 1; // Touch, 1
++            viewSettings->Release();
++        }
++        uiViewSettingsInterop->Release();
++        return result;
+     }
+-    uiViewSettingsInterop->Release();
+-    return result;
++
++    return false;
+ }
+ 
+ QT_END_NAMESPACE
+diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
+index c363b85cb3..9b24191b26 100644
+--- a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
++++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
+@@ -44,6 +44,7 @@
+ #include <QtCore/qsysinfo.h>
+ #include <QtCore/qscopedpointer.h>
+ #include <QtCore/quuid.h>
++#include <QtCore/private/qsystemlibrary_p.h>
+ #include <QtCore/private/qwinregistry_p.h>
+ #if QT_CONFIG(cpp_winrt)
+ #  include <QtCore/private/qfactorycacheregistration_p.h>
+@@ -121,6 +122,77 @@ static inline bool sessionManagerInteractionBlocked()
+ static inline bool sessionManagerInteractionBlocked() { return false; }
+ #endif
+ 
++/*!
++    \class QWindowsUser32DLL
++    \brief Struct that contains dynamically resolved symbols of User32.dll.
++    The stub libraries shipped with the MinGW compiler miss some of the
++    functions. They need to be retrieved dynamically.
++    In addition, touch-related functions are available only from Windows onwards.
++    These need to resolved dynamically for Q_CC_MSVC as well.
++    \sa QWindowsShell32DLL
++    \internal
++*/
++
++void QWindowsUser32DLL::init()
++{
++    QSystemLibrary library(QStringLiteral("user32"));
++    setProcessDPIAware = (SetProcessDPIAware)library.resolve("SetProcessDPIAware");
++    setProcessDpiAwarenessContext = (SetProcessDpiAwarenessContext)library.resolve("SetProcessDpiAwarenessContext");
++    getThreadDpiAwarenessContext = (GetThreadDpiAwarenessContext)library.resolve("GetThreadDpiAwarenessContext");
++    isValidDpiAwarenessContext = (IsValidDpiAwarenessContext)library.resolve("IsValidDpiAwarenessContext");
++    areDpiAwarenessContextsEqual = (AreDpiAwarenessContextsEqual)library.resolve("AreDpiAwarenessContextsEqual");
++
++    addClipboardFormatListener = (AddClipboardFormatListener)library.resolve("AddClipboardFormatListener");
++    removeClipboardFormatListener = (RemoveClipboardFormatListener)library.resolve("RemoveClipboardFormatListener");
++
++    getDisplayAutoRotationPreferences = (GetDisplayAutoRotationPreferences)library.resolve("GetDisplayAutoRotationPreferences");
++    setDisplayAutoRotationPreferences = (SetDisplayAutoRotationPreferences)library.resolve("SetDisplayAutoRotationPreferences");
++
++    if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8) {
++        enableMouseInPointer = (EnableMouseInPointer)library.resolve("EnableMouseInPointer");
++        getPointerType = (GetPointerType)library.resolve("GetPointerType");
++        getPointerInfo = (GetPointerInfo)library.resolve("GetPointerInfo");
++        getPointerDeviceRects = (GetPointerDeviceRects)library.resolve("GetPointerDeviceRects");
++        getPointerTouchInfo = (GetPointerTouchInfo)library.resolve("GetPointerTouchInfo");
++        getPointerFrameTouchInfo = (GetPointerFrameTouchInfo)library.resolve("GetPointerFrameTouchInfo");
++        getPointerFrameTouchInfoHistory = (GetPointerFrameTouchInfoHistory)library.resolve("GetPointerFrameTouchInfoHistory");
++        getPointerPenInfo = (GetPointerPenInfo)library.resolve("GetPointerPenInfo");
++        getPointerPenInfoHistory = (GetPointerPenInfoHistory)library.resolve("GetPointerPenInfoHistory");
++        skipPointerFrameMessages = (SkipPointerFrameMessages)library.resolve("SkipPointerFrameMessages");
++    }
++    
++    if (QOperatingSystemVersion::current()
++        >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 14393)) {
++        adjustWindowRectExForDpi = (AdjustWindowRectExForDpi)library.resolve("AdjustWindowRectExForDpi");
++        enableNonClientDpiScaling = (EnableNonClientDpiScaling)library.resolve("EnableNonClientDpiScaling");
++        getWindowDpiAwarenessContext = (GetWindowDpiAwarenessContext)library.resolve("GetWindowDpiAwarenessContext");
++        getAwarenessFromDpiAwarenessContext = (GetAwarenessFromDpiAwarenessContext)library.resolve("GetAwarenessFromDpiAwarenessContext");
++        systemParametersInfoForDpi = (SystemParametersInfoForDpi)library.resolve("SystemParametersInfoForDpi");
++        getDpiForWindow = (GetDpiForWindow)library.resolve("GetDpiForWindow");
++        getSystemMetricsForDpi = (GetSystemMetricsForDpi)library.resolve("GetSystemMetricsForDpi");
++    }
++}
++
++bool QWindowsUser32DLL::supportsPointerApi()
++{
++    return enableMouseInPointer && getPointerType && getPointerInfo && getPointerDeviceRects
++            && getPointerTouchInfo && getPointerFrameTouchInfo && getPointerFrameTouchInfoHistory
++            && getPointerPenInfo && getPointerPenInfoHistory && skipPointerFrameMessages;
++}
++
++void QWindowsShcoreDLL::init()
++{
++    if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows8_1)
++        return;
++    QSystemLibrary library(QStringLiteral("SHCore"));
++    getProcessDpiAwareness = (GetProcessDpiAwareness)library.resolve("GetProcessDpiAwareness");
++    setProcessDpiAwareness = (SetProcessDpiAwareness)library.resolve("SetProcessDpiAwareness");
++    getDpiForMonitor = (GetDpiForMonitor)library.resolve("GetDpiForMonitor");
++}
++
++QWindowsUser32DLL QWindowsContext::user32dll;
++QWindowsShcoreDLL QWindowsContext::shcoredll;
++
+ QWindowsContext *QWindowsContext::m_instance = nullptr;
+ 
+ /*!
+@@ -164,6 +236,9 @@ bool QWindowsContextPrivate::m_v2DpiAware = false;
+ QWindowsContextPrivate::QWindowsContextPrivate()
+     : m_oleInitializeResult(OleInitialize(nullptr))
+ {
++    QWindowsContext::user32dll.init();
++    QWindowsContext::shcoredll.init();
++    
+     if (m_pointerHandler.touchDevice() || m_mouseHandler.touchDevice())
+         m_systemInfo |= QWindowsContext::SI_SupportsTouch;
+     m_displayContext = GetDC(nullptr);
+@@ -282,6 +357,12 @@ bool QWindowsContext::initPointer(unsigned integrationOptions)
+     if (integrationOptions & QWindowsIntegration::DontUseWMPointer)
+         return false;
+ 
++    if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows8)
++        return false;
++
++    if (!QWindowsContext::user32dll.supportsPointerApi())
++        return false;
++
+     d->m_systemInfo |= QWindowsContext::SI_SupportsPointer;
+     return true;
+ }
+@@ -352,41 +433,58 @@ void QWindowsContext::setDetectAltGrModifier(bool a)
+ [[nodiscard]] static inline QtWindows::DpiAwareness
+     dpiAwarenessContextToQtDpiAwareness(DPI_AWARENESS_CONTEXT context)
+ {
+-    // IsValidDpiAwarenessContext() will handle the NULL pointer case.
+-    if (!IsValidDpiAwarenessContext(context))
++    if (QWindowsContext::user32dll.isValidDpiAwarenessContext && QWindowsContext::user32dll.areDpiAwarenessContextsEqual)
++    {
++        // IsValidDpiAwarenessContext() will handle the NULL pointer case.
++        if (!QWindowsContext::user32dll.isValidDpiAwarenessContext(context))
++            return QtWindows::DpiAwareness::Invalid;
++        if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED))
++            return QtWindows::DpiAwareness::Unaware_GdiScaled;
++        if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
++            return QtWindows::DpiAwareness::PerMonitorVersion2;
++        if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
++            return QtWindows::DpiAwareness::PerMonitor;
++        if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_SYSTEM_AWARE))
++            return QtWindows::DpiAwareness::System;
++        if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE))
++            return QtWindows::DpiAwareness::Unaware;
++
+         return QtWindows::DpiAwareness::Invalid;
+-    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED))
+-        return QtWindows::DpiAwareness::Unaware_GdiScaled;
+-    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
+-        return QtWindows::DpiAwareness::PerMonitorVersion2;
+-    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
+-        return QtWindows::DpiAwareness::PerMonitor;
+-    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_SYSTEM_AWARE))
+-        return QtWindows::DpiAwareness::System;
+-    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE))
+-        return QtWindows::DpiAwareness::Unaware;
+-    return QtWindows::DpiAwareness::Invalid;
++    }
++    
++    return QtWindows::DpiAwareness::Unaware; // Windows 7
+ }
+ 
+ QtWindows::DpiAwareness QWindowsContext::windowDpiAwareness(HWND hwnd)
+ {
+     if (!hwnd)
+         return QtWindows::DpiAwareness::Invalid;
+-    const auto context = GetWindowDpiAwarenessContext(hwnd);
+-    return dpiAwarenessContextToQtDpiAwareness(context);
++
++    if (QWindowsContext::user32dll.getWindowDpiAwarenessContext)
++    {
++        const auto context = QWindowsContext::user32dll.getWindowDpiAwarenessContext(hwnd);
++        return dpiAwarenessContextToQtDpiAwareness(context);
++    }
++
++    return dpiAwarenessContextToQtDpiAwareness(DPI_AWARENESS_CONTEXT_UNAWARE);
+ }
+ 
+ QtWindows::DpiAwareness QWindowsContext::processDpiAwareness()
+ {
+-    // Although we have GetDpiAwarenessContextForProcess(), however,
+-    // it's only available on Win10 1903+, which is a little higher
+-    // than Qt's minimum supported version (1809), so we can't use it.
+-    // Luckily, MS docs said GetThreadDpiAwarenessContext() will also
+-    // return the default DPI_AWARENESS_CONTEXT for the process if
+-    // SetThreadDpiAwarenessContext() was never called. So we can use
+-    // it as an equivalent.
+-    const auto context = GetThreadDpiAwarenessContext();
+-    return dpiAwarenessContextToQtDpiAwareness(context);
++    if (QWindowsContext::user32dll.getThreadDpiAwarenessContext)
++    {
++        // Although we have GetDpiAwarenessContextForProcess(), however,
++        // it's only available on Win10 1903+, which is a little higher
++        // than Qt's minimum supported version (1809), so we can't use it.
++        // Luckily, MS docs said GetThreadDpiAwarenessContext() will also
++        // return the default DPI_AWARENESS_CONTEXT for the process if
++        // SetThreadDpiAwarenessContext() was never called. So we can use
++        // it as an equivalent.
++        const auto context = QWindowsContext::user32dll.getThreadDpiAwarenessContext();
++        return dpiAwarenessContextToQtDpiAwareness(context);
++    }
++
++    return dpiAwarenessContextToQtDpiAwareness(DPI_AWARENESS_CONTEXT_UNAWARE);
+ }
+ 
+ [[nodiscard]] static inline DPI_AWARENESS_CONTEXT
+@@ -445,23 +543,29 @@ bool QWindowsContext::setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwarenes
+     if (processDpiAwareness() == dpiAwareness)
+         return true;
+     const auto context = qtDpiAwarenessToDpiAwarenessContext(dpiAwareness);
+-    if (!IsValidDpiAwarenessContext(context)) {
+-        qCWarning(lcQpaWindow) << dpiAwareness << "is not supported by current system.";
+-        return false;
+-    }
+-    if (!SetProcessDpiAwarenessContext(context)) {
+-        qCWarning(lcQpaWindow).noquote().nospace()
+-            << "SetProcessDpiAwarenessContext() failed: "
+-            << QSystemError::windowsString()
+-            << "\nQt's default DPI awareness context is "
+-            << "DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2. If you know what you "
+-            << "are doing, you can overwrite this default using qt.conf "
+-            << "(https://doc.qt.io/qt-6/highdpi.html#configuring-windows).";
+-        return false;
++
++    if (QWindowsContext::user32dll.isValidDpiAwarenessContext && QWindowsContext::user32dll.setProcessDpiAwarenessContext)
++    {
++        if (!QWindowsContext::user32dll.isValidDpiAwarenessContext(context)) {
++            qCWarning(lcQpaWindow) << dpiAwareness << "is not supported by current system.";
++            return false;
++        }
++        if (!QWindowsContext::user32dll.setProcessDpiAwarenessContext(context)) {
++            qCWarning(lcQpaWindow).noquote().nospace()
++                << "SetProcessDpiAwarenessContext() failed: "
++                << QSystemError::windowsString()
++                << "\nQt's default DPI awareness context is "
++                << "DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2. If you know what you "
++                << "are doing, you can overwrite this default using qt.conf "
++                << "(https://doc.qt.io/qt-6/highdpi.html#configuring-windows).";
++            return false;
++        }
++        QWindowsContextPrivate::m_v2DpiAware
++            = processDpiAwareness() == QtWindows::DpiAwareness::PerMonitorVersion2;
++        return true;
+     }
+-    QWindowsContextPrivate::m_v2DpiAware
+-        = processDpiAwareness() == QtWindows::DpiAwareness::PerMonitorVersion2;
+-    return true;
++
++    return false; // Windows 7
+ }
+ 
+ bool QWindowsContext::isDarkMode()
+@@ -891,8 +995,8 @@ void QWindowsContext::forceNcCalcSize(HWND hwnd)
+ bool QWindowsContext::systemParametersInfo(unsigned action, unsigned param, void *out,
+                                            unsigned dpi)
+ {
+-    const BOOL result = dpi != 0
+-        ? SystemParametersInfoForDpi(action, param, out, 0, dpi)
++    const BOOL result = (QWindowsContext::user32dll.systemParametersInfoForDpi != nullptr && dpi != 0)
++        ? QWindowsContext::user32dll.systemParametersInfoForDpi(action, param, out, 0, dpi)
+         : SystemParametersInfo(action, param, out, 0);
+     return result == TRUE;
+ }
+@@ -979,8 +1083,8 @@ static inline bool isInputMessage(UINT m)
+ static bool enableNonClientDpiScaling(HWND hwnd)
+ {
+     bool result = false;
+-    if (QWindowsContext::windowDpiAwareness(hwnd) == QtWindows::DpiAwareness::PerMonitor) {
+-        result = EnableNonClientDpiScaling(hwnd) != FALSE;
++    if (QWindowsContext::user32dll.enableNonClientDpiScaling && QWindowsContext::windowDpiAwareness(hwnd) == QtWindows::DpiAwareness::PerMonitor) {
++        result = QWindowsContext::user32dll.enableNonClientDpiScaling(hwnd) != FALSE;
+         if (!result) {
+             const DWORD errorCode = GetLastError();
+             qErrnoWarning(int(errorCode), "EnableNonClientDpiScaling() failed for HWND %p (%lu)",
+diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.h b/qtbase/src/plugins/platforms/windows/qwindowscontext.h
+index 1089224433..557bd1f63c 100644
+--- a/qtbase/src/plugins/platforms/windows/qwindowscontext.h
++++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.h
+@@ -14,6 +14,7 @@
+ #define STRICT_TYPED_ITEMIDS
+ #include <shlobj.h>
+ #include <shlwapi.h>
++#include <shellscalingapi.h>
+ 
+ QT_BEGIN_NAMESPACE
+ 
+@@ -44,6 +45,95 @@ struct QWindowsContextPrivate;
+ class QPoint;
+ class QKeyEvent;
+ class QPointingDevice;
++
++struct QWindowsUser32DLL
++{
++    inline void init();
++    inline bool supportsPointerApi();
++
++    typedef BOOL (WINAPI *EnableMouseInPointer)(BOOL);
++    typedef BOOL (WINAPI *GetPointerType)(UINT32, PVOID);
++    typedef BOOL (WINAPI *GetPointerInfo)(UINT32, PVOID);
++    typedef BOOL (WINAPI *GetPointerDeviceRects)(HANDLE, RECT *, RECT *);
++    typedef BOOL (WINAPI *GetPointerTouchInfo)(UINT32, PVOID);
++    typedef BOOL (WINAPI *GetPointerFrameTouchInfo)(UINT32, UINT32 *, PVOID);
++    typedef BOOL (WINAPI *GetPointerFrameTouchInfoHistory)(UINT32, UINT32 *, UINT32 *, PVOID);
++    typedef BOOL (WINAPI *GetPointerPenInfo)(UINT32, PVOID);
++    typedef BOOL (WINAPI *GetPointerPenInfoHistory)(UINT32, UINT32 *, PVOID);
++    typedef BOOL (WINAPI *SkipPointerFrameMessages)(UINT32);
++    typedef BOOL (WINAPI *SetProcessDPIAware)();
++    typedef BOOL (WINAPI *SetProcessDpiAwarenessContext)(HANDLE);
++    typedef BOOL (WINAPI *AddClipboardFormatListener)(HWND);
++    typedef BOOL (WINAPI *RemoveClipboardFormatListener)(HWND);
++    typedef BOOL (WINAPI *GetDisplayAutoRotationPreferences)(DWORD *);
++    typedef BOOL (WINAPI *SetDisplayAutoRotationPreferences)(DWORD);
++    typedef BOOL (WINAPI *AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UINT);
++    typedef BOOL (WINAPI *EnableNonClientDpiScaling)(HWND);
++    typedef DPI_AWARENESS_CONTEXT (WINAPI *GetWindowDpiAwarenessContext)(HWND);
++    typedef DPI_AWARENESS (WINAPI *GetAwarenessFromDpiAwarenessContext)(int);
++    typedef BOOL (WINAPI *SystemParametersInfoForDpi)(UINT, UINT, PVOID, UINT, UINT);
++    typedef int  (WINAPI *GetDpiForWindow)(HWND);
++    typedef BOOL (WINAPI *GetSystemMetricsForDpi)(INT, UINT);
++    typedef BOOL (WINAPI *AreDpiAwarenessContextsEqual)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT);
++    typedef DPI_AWARENESS_CONTEXT (WINAPI *GetThreadDpiAwarenessContext)();
++    typedef BOOL (WINAPI *IsValidDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
++
++    // Windows pointer functions (Windows 8 or later).
++    EnableMouseInPointer enableMouseInPointer = nullptr;
++    GetPointerType getPointerType = nullptr;
++    GetPointerInfo getPointerInfo = nullptr;
++    GetPointerDeviceRects getPointerDeviceRects = nullptr;
++    GetPointerTouchInfo getPointerTouchInfo = nullptr;
++    GetPointerFrameTouchInfo getPointerFrameTouchInfo = nullptr;
++    GetPointerFrameTouchInfoHistory getPointerFrameTouchInfoHistory = nullptr;
++    GetPointerPenInfo getPointerPenInfo = nullptr;
++    GetPointerPenInfoHistory getPointerPenInfoHistory = nullptr;
++    SkipPointerFrameMessages skipPointerFrameMessages = nullptr;
++
++    // Windows Vista onwards
++    SetProcessDPIAware setProcessDPIAware = nullptr;
++
++    // Windows 10 version 1607 onwards
++    GetDpiForWindow getDpiForWindow = nullptr;
++    GetThreadDpiAwarenessContext getThreadDpiAwarenessContext = nullptr;
++    IsValidDpiAwarenessContext isValidDpiAwarenessContext = nullptr;
++
++    // Windows 10 version 1703 onwards
++    SetProcessDpiAwarenessContext setProcessDpiAwarenessContext = nullptr;
++    AreDpiAwarenessContextsEqual areDpiAwarenessContextsEqual = nullptr;
++
++    // Clipboard listeners are present on Windows Vista onwards
++    // but missing in MinGW 4.9 stub libs. Can be removed in MinGW 5.
++    AddClipboardFormatListener addClipboardFormatListener = nullptr;
++    RemoveClipboardFormatListener removeClipboardFormatListener = nullptr;
++
++    // Rotation API
++    GetDisplayAutoRotationPreferences getDisplayAutoRotationPreferences = nullptr;
++    SetDisplayAutoRotationPreferences setDisplayAutoRotationPreferences = nullptr;
++
++    AdjustWindowRectExForDpi adjustWindowRectExForDpi = nullptr;
++    EnableNonClientDpiScaling enableNonClientDpiScaling = nullptr;
++    GetWindowDpiAwarenessContext getWindowDpiAwarenessContext = nullptr;
++    GetAwarenessFromDpiAwarenessContext getAwarenessFromDpiAwarenessContext = nullptr;
++    SystemParametersInfoForDpi systemParametersInfoForDpi = nullptr;
++    GetSystemMetricsForDpi getSystemMetricsForDpi = nullptr;
++};
++
++// Shell scaling library (Windows 8.1 onwards)
++struct QWindowsShcoreDLL
++{
++    void init();
++    inline bool isValid() const { return getProcessDpiAwareness && setProcessDpiAwareness && getDpiForMonitor; }
++
++    typedef HRESULT (WINAPI *GetProcessDpiAwareness)(HANDLE,PROCESS_DPI_AWARENESS *);
++    typedef HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS);
++    typedef HRESULT (WINAPI *GetDpiForMonitor)(HMONITOR,int,UINT *,UINT *);
++
++    GetProcessDpiAwareness getProcessDpiAwareness = nullptr;
++    SetProcessDpiAwareness setProcessDpiAwareness = nullptr;
++    GetDpiForMonitor getDpiForMonitor = nullptr;
++};
++
+ class QWindowsContext
+ {
+     Q_DISABLE_COPY_MOVE(QWindowsContext)
+@@ -138,6 +228,9 @@ public:
+     QWindowsScreenManager &screenManager();
+     QWindowsTabletSupport *tabletSupport() const;
+ 
++    static QWindowsUser32DLL user32dll;
++    static QWindowsShcoreDLL shcoredll;
++
+     bool asyncExpose() const;
+     void setAsyncExpose(bool value);
+ 
+diff --git a/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp b/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp
+index c6f55c3509..d97efc6851 100644
+--- a/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp
++++ b/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp
+@@ -673,7 +673,7 @@ static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource,
+                 const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
+ 
+                 POINTER_INFO pointerInfo{};
+-                if (!GetPointerInfo(pointerId, &pointerInfo))
++                if (!QWindowsContext::user32dll.getPointerInfo || !QWindowsContext::user32dll.getPointerInfo(pointerId, &pointerInfo))
+                     return E_FAIL;
+ 
+                 if (pointerInfo.pointerFlags & POINTER_FLAG_PRIMARY) {
+diff --git a/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp b/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp
+index af1230d240..c7e9ecbfec 100644
+--- a/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp
++++ b/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp
+@@ -732,15 +732,28 @@ static inline QString messageKeyText(const MSG &msg)
+ 
+ [[nodiscard]] static inline int getTitleBarHeight(const HWND hwnd)
+ {
+-    const UINT dpi = GetDpiForWindow(hwnd);
+-    const int captionHeight = GetSystemMetricsForDpi(SM_CYCAPTION, dpi);
+-    if (IsZoomed(hwnd))
+-        return captionHeight;
+-    // The frame height should also be taken into account if the window
+-    // is not maximized.
+-    const int frameHeight = GetSystemMetricsForDpi(SM_CYSIZEFRAME, dpi)
+-                            + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
+-    return captionHeight + frameHeight;
++    if (QWindowsContext::user32dll.getDpiForWindow && QWindowsContext::user32dll.getSystemMetricsForDpi)
++    {
++        const UINT dpi = QWindowsContext::user32dll.getDpiForWindow(hwnd);
++        const int captionHeight = QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CYCAPTION, dpi);
++        if (IsZoomed(hwnd))
++            return captionHeight;
++        // The frame height should also be taken into account if the window
++        // is not maximized.
++        const int frameHeight = QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CYSIZEFRAME, dpi)
++                                + QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
++        return captionHeight + frameHeight;    
++    }
++    else
++    {
++        const int captionHeight = GetSystemMetrics(SM_CYCAPTION);
++        if (IsZoomed(hwnd))
++            return captionHeight;
++        // The frame height should also be taken into account if the window
++        // is not maximized.
++        const int frameHeight = GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
++        return captionHeight + frameHeight;    
++    }
+ }
+ 
+ [[nodiscard]] static inline bool isSystemMenuOffsetNeeded(const Qt::WindowFlags flags)
+diff --git a/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp
+index 9f1f25db8c..0a0b8bdf56 100644
+--- a/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp
++++ b/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp
+@@ -48,7 +48,7 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q
+     *result = 0;
+     const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
+ 
+-    if (!GetPointerType(pointerId, &m_pointerType)) {
++    if (!QWindowsContext::user32dll.getPointerType(pointerId, &m_pointerType)) {
+         qWarning() << "GetPointerType() failed:" << qt_error_string();
+         return false;
+     }
+@@ -62,12 +62,12 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q
+     }
+     case QT_PT_TOUCH: {
+         quint32 pointerCount = 0;
+-        if (!GetPointerFrameTouchInfo(pointerId, &pointerCount, nullptr)) {
++        if (!QWindowsContext::user32dll.getPointerFrameTouchInfo(pointerId, &pointerCount, nullptr)) {
+             qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string();
+             return false;
+         }
+         QVarLengthArray<POINTER_TOUCH_INFO, 10> touchInfo(pointerCount);
+-        if (!GetPointerFrameTouchInfo(pointerId, &pointerCount, touchInfo.data())) {
++        if (!QWindowsContext::user32dll.getPointerFrameTouchInfo(pointerId, &pointerCount, touchInfo.data())) {
+             qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string();
+             return false;
+         }
+@@ -80,10 +80,10 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q
+         // dispatch any skipped frames if event compression is disabled by the app
+         if (historyCount > 1 && !QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) {
+             touchInfo.resize(pointerCount * historyCount);
+-            if (!GetPointerFrameTouchInfoHistory(pointerId,
+-                                                 &historyCount,
+-                                                 &pointerCount,
+-                                                 touchInfo.data())) {
++            if (!QWindowsContext::user32dll.getPointerFrameTouchInfoHistory(pointerId,
++                                                                            &historyCount,
++                                                                            &pointerCount,
++                                                                            touchInfo.data())) {
+                 qWarning() << "GetPointerFrameTouchInfoHistory() failed:" << qt_error_string();
+                 return false;
+             }
+@@ -101,7 +101,7 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q
+     }
+     case QT_PT_PEN: {
+         POINTER_PEN_INFO penInfo;
+-        if (!GetPointerPenInfo(pointerId, &penInfo)) {
++        if (!QWindowsContext::user32dll.getPointerPenInfo(pointerId, &penInfo)) {
+             qWarning() << "GetPointerPenInfo() failed:" << qt_error_string();
+             return false;
+         }
+@@ -113,7 +113,7 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q
+                 || !QCoreApplication::testAttribute(Qt::AA_CompressTabletEvents))) {
+             QVarLengthArray<POINTER_PEN_INFO, 10> penInfoHistory(historyCount);
+ 
+-            if (!GetPointerPenInfoHistory(pointerId, &historyCount, penInfoHistory.data())) {
++            if (!QWindowsContext::user32dll.getPointerPenInfoHistory(pointerId, &historyCount, penInfoHistory.data())) {
+                 qWarning() << "GetPointerPenInfoHistory() failed:" << qt_error_string();
+                 return false;
+             }
+@@ -520,7 +520,7 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
+         inputIds.insert(touchPoint.id);
+ 
+         // Avoid getting repeated messages for this frame if there are multiple pointerIds
+-        SkipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId);
++        QWindowsContext::user32dll.skipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId);
+     }
+ 
+     // Some devices send touches for each finger in a different message/frame, instead of consolidating
+@@ -567,7 +567,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
+     auto *penInfo = static_cast<POINTER_PEN_INFO *>(vPenInfo);
+ 
+     RECT pRect, dRect;
+-    if (!GetPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect))
++    if (!QWindowsContext::user32dll.getPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect))
+         return false;
+ 
+     const auto systemId = (qint64)penInfo->pointerInfo.sourceDevice;
+diff --git a/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp b/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp
+index a50f9fd4b0..a8f65209b3 100644
+--- a/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp
++++ b/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp
+@@ -43,10 +43,12 @@ static inline QDpi deviceDPI(HDC hdc)
+ 
+ static inline QDpi monitorDPI(HMONITOR hMonitor)
+ {
+-    UINT dpiX;
+-    UINT dpiY;
+-    if (SUCCEEDED(GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY)))
+-        return QDpi(dpiX, dpiY);
++    if (QWindowsContext::shcoredll.isValid()) {
++        UINT dpiX;
++        UINT dpiY;
++        if (SUCCEEDED(QWindowsContext::shcoredll.getDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY)))
++            return QDpi(dpiX, dpiY);
++    }
+     return {0, 0};
+ }
+ 
+@@ -577,47 +579,51 @@ QRect QWindowsScreen::virtualGeometry(const QPlatformScreen *screen) // cf QScre
+ bool QWindowsScreen::setOrientationPreference(Qt::ScreenOrientation o)
+ {
+     bool result = false;
+-    ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE;
+-    switch (o) {
+-    case Qt::PrimaryOrientation:
+-        break;
+-    case Qt::PortraitOrientation:
+-        orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT;
+-        break;
+-    case Qt::LandscapeOrientation:
+-        orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE;
+-        break;
+-    case Qt::InvertedPortraitOrientation:
+-        orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED;
+-        break;
+-    case Qt::InvertedLandscapeOrientation:
+-        orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED;
+-        break;
++    if (QWindowsContext::user32dll.setDisplayAutoRotationPreferences) {
++        ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE;
++        switch (o) {
++        case Qt::PrimaryOrientation:
++            break;
++        case Qt::PortraitOrientation:
++            orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT;
++            break;
++        case Qt::LandscapeOrientation:
++            orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE;
++            break;
++        case Qt::InvertedPortraitOrientation:
++            orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED;
++            break;
++        case Qt::InvertedLandscapeOrientation:
++            orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED;
++            break;
++        }
++        result = QWindowsContext::user32dll.setDisplayAutoRotationPreferences(orientationPreference);
+     }
+-    result = SetDisplayAutoRotationPreferences(orientationPreference);
+     return result;
+ }
+ 
+ Qt::ScreenOrientation QWindowsScreen::orientationPreference()
+ {
+     Qt::ScreenOrientation result = Qt::PrimaryOrientation;
+-    ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE;
+-    if (GetDisplayAutoRotationPreferences(&orientationPreference)) {
+-        switch (orientationPreference) {
+-        case ORIENTATION_PREFERENCE_NONE:
+-            break;
+-        case ORIENTATION_PREFERENCE_LANDSCAPE:
+-            result = Qt::LandscapeOrientation;
+-            break;
+-        case ORIENTATION_PREFERENCE_PORTRAIT:
+-            result = Qt::PortraitOrientation;
+-            break;
+-        case ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED:
+-            result = Qt::InvertedLandscapeOrientation;
+-            break;
+-        case ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED:
+-            result = Qt::InvertedPortraitOrientation;
+-            break;
++    if (QWindowsContext::user32dll.getDisplayAutoRotationPreferences) {
++        DWORD orientationPreference = ORIENTATION_PREFERENCE_NONE;
++        if (QWindowsContext::user32dll.getDisplayAutoRotationPreferences(&orientationPreference)) {
++            switch (orientationPreference) {
++            case ORIENTATION_PREFERENCE_NONE:
++                break;
++            case ORIENTATION_PREFERENCE_LANDSCAPE:
++                result = Qt::LandscapeOrientation;
++                break;
++            case ORIENTATION_PREFERENCE_PORTRAIT:
++                result = Qt::PortraitOrientation;
++                break;
++            case ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED:
++                result = Qt::InvertedLandscapeOrientation;
++                break;
++            case ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED:
++                result = Qt::InvertedPortraitOrientation;
++                break;
++            }
+         }
+     }
+     return result;
+diff --git a/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp b/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp
+index 2a01b0fa0d..314410dce7 100644
+--- a/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp
++++ b/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp
+@@ -44,6 +44,8 @@
+ 
+ #include <algorithm>
+ 
++#include <VersionHelpers.h>
++
+ #if QT_CONFIG(cpp_winrt)
+ #   include <QtCore/private/qt_winrtbase_p.h>
+ 
+@@ -672,8 +674,14 @@ void QWindowsTheme::refreshFonts()
+     fixedFont.setStyleHint(QFont::TypeWriter);
+ 
+     LOGFONT lfIconTitleFont;
+-    SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi);
+-    const QFont iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont, dpi);
++    QFont iconTitleFont;
++    if (QWindowsContext::user32dll.systemParametersInfoForDpi) {
++        QWindowsContext::user32dll.systemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi);
++        iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont, dpi);
++    } else {
++        SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0);
++        iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont);
++    }
+ 
+     m_fonts[SystemFont] = new QFont(QWindowsFontDatabase::systemDefaultFont());
+     m_fonts[MenuFont] = new QFont(menuFont);
+diff --git a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
+index c2c093faf4..d4910f0645 100644
+--- a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
++++ b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
+@@ -520,11 +520,15 @@ static inline void updateGLWindowSettings(const QWindow *w, HWND hwnd, Qt::Windo
+ 
+ [[nodiscard]] static inline int getResizeBorderThickness(const UINT dpi)
+ {
+-    // The width of the padded border will always be 0 if DWM composition is
+-    // disabled, but since it will always be enabled and can't be programtically
+-    // disabled from Windows 8, we are safe to go.
+-    return GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi)
+-           + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
++    if (QWindowsContext::user32dll.getSystemMetricsForDpi) {
++        // The width of the padded border will always be 0 if DWM composition is
++        // disabled, but since it will always be enabled and can't be programtically
++        // disabled from Windows 8, we are safe to go.
++        return QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CXSIZEFRAME, dpi)
++               + QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
++    }
++    
++    return GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
+ }
+ 
+ /*!
+@@ -534,13 +538,17 @@ static inline void updateGLWindowSettings(const QWindow *w, HWND hwnd, Qt::Windo
+ 
+ static QMargins invisibleMargins(QPoint screenPoint)
+ {
+-    POINT pt = {screenPoint.x(), screenPoint.y()};
+-    if (HMONITOR hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL)) {
+-        UINT dpiX;
+-        UINT dpiY;
+-        if (SUCCEEDED(GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) {
+-            const int gap = getResizeBorderThickness(dpiX);
+-            return QMargins(gap, 0, gap, gap);
++    if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10) {
++        POINT pt = {screenPoint.x(), screenPoint.y()};
++        if (HMONITOR hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL)) {
++            if (QWindowsContext::shcoredll.isValid()) {
++                UINT dpiX;
++                UINT dpiY;
++                if (SUCCEEDED(QWindowsContext::shcoredll.getDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) {
++                    const int gap = getResizeBorderThickness(dpiX);
++                    return QMargins(gap, 0, gap, gap);
++                }
++            }
+         }
+     }
+     return QMargins();
+@@ -548,9 +556,12 @@ static QMargins invisibleMargins(QPoint screenPoint)
+ 
+ [[nodiscard]] static inline QMargins invisibleMargins(const HWND hwnd)
+ {
+-    const UINT dpi = GetDpiForWindow(hwnd);
+-    const int gap = getResizeBorderThickness(dpi);
+-    return QMargins(gap, 0, gap, gap);
++    if (QWindowsContext::user32dll.getDpiForWindow) {
++        const UINT dpi = QWindowsContext::user32dll.getDpiForWindow(hwnd);
++        const int gap = getResizeBorderThickness(dpi);
++        return QMargins(gap, 0, gap, gap);
++    }
++    return QMargins();
+ }
+ 
+ /*!
+@@ -1068,7 +1079,8 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, DWORD style, DWORD exStyl
+         return {};
+     RECT rect = {0,0,0,0};
+     style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs.
+-    if (AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, unsigned(qRound(dpi))) == FALSE) {
++    if (QWindowsContext::user32dll.adjustWindowRectExForDpi &&
++        QWindowsContext::user32dll.adjustWindowRectExForDpi(&rect, style, FALSE, exStyle, unsigned(qRound(dpi))) == FALSE) {
+         qErrnoWarning("%s: AdjustWindowRectExForDpi failed", __FUNCTION__);
+     }
+     const QMargins result(qAbs(rect.left), qAbs(rect.top),
+@@ -1561,7 +1573,8 @@ void QWindowsWindow::initialize()
+             QWindowSystemInterface::handleGeometryChange<QWindowSystemInterface::SynchronousDelivery>(w, obtainedGeometry);
+         }
+     }
+-    QWindowsWindow::setSavedDpi(GetDpiForWindow(handle()));
++    QWindowsWindow::setSavedDpi(QWindowsContext::user32dll.getDpiForWindow ?
++        QWindowsContext::user32dll.getDpiForWindow(handle()) : 96);
+ }
+ 
+ QSurfaceFormat QWindowsWindow::format() const
+@@ -2056,17 +2069,20 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam)
+ 
+ void QWindowsWindow::handleDpiChangedAfterParent(HWND hwnd)
+ {
+-    const UINT dpi = GetDpiForWindow(hwnd);
+-    const qreal scale = dpiRelativeScale(dpi);
+-    setSavedDpi(dpi);
+-
+-    checkForScreenChanged(QWindowsWindow::FromDpiChange);
+-
+-    // Child windows do not get WM_GETDPISCALEDSIZE messages to inform
+-    // Windows about the new size, so we need to manually scale them.
+-    QRect currentGeometry = geometry();
+-    QRect scaledGeometry = QRect(currentGeometry.topLeft() * scale, currentGeometry.size() * scale);
+-    setGeometry(scaledGeometry);
++    if (QWindowsContext::user32dll.getDpiForWindow)
++    {
++        const UINT dpi = QWindowsContext::user32dll.getDpiForWindow(hwnd);
++        const qreal scale = dpiRelativeScale(dpi);
++        setSavedDpi(dpi);
++
++        checkForScreenChanged(QWindowsWindow::FromDpiChange);
++
++        // Child windows do not get WM_GETDPISCALEDSIZE messages to inform
++        // Windows about the new size, so we need to manually scale them.
++        QRect currentGeometry = geometry();
++        QRect scaledGeometry = QRect(currentGeometry.topLeft() * scale, currentGeometry.size() * scale);
++        setGeometry(scaledGeometry);
++    }    
+ }
+ 
+ static QRect normalFrameGeometry(HWND hwnd)
+diff --git a/qtbase/src/widgets/styles/qwindowsstyle.cpp b/qtbase/src/widgets/styles/qwindowsstyle.cpp
+index ae82f784ca..a617ef14c1 100644
+--- a/qtbase/src/widgets/styles/qwindowsstyle.cpp
++++ b/qtbase/src/widgets/styles/qwindowsstyle.cpp
+@@ -262,33 +262,29 @@ void QWindowsStyle::polish(QPalette &pal)
+ int QWindowsStylePrivate::pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *, const QWidget *widget)
+ {
+ #if defined(Q_OS_WIN)
+-    // The pixel metrics are in device indepentent pixels;
+-    // hardcode DPI to 1x 96 DPI.
+-    const int dpi = 96;
+-
+     switch (pm) {
+     case QStyle::PM_DockWidgetFrameWidth:
+-        return GetSystemMetricsForDpi(SM_CXFRAME, dpi);
++        return GetSystemMetrics(SM_CXFRAME);
+ 
+     case QStyle::PM_TitleBarHeight: {
+         const int resizeBorderThickness =
+-            GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
++                GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
+         if (widget && (widget->windowType() == Qt::Tool))
+-            return GetSystemMetricsForDpi(SM_CYSMCAPTION, dpi) + resizeBorderThickness;
+-        return GetSystemMetricsForDpi(SM_CYCAPTION, dpi) + resizeBorderThickness;
++            return GetSystemMetrics(SM_CYSMCAPTION) + resizeBorderThickness;
++        return GetSystemMetrics(SM_CYCAPTION) + resizeBorderThickness;
+     }
+ 
+     case QStyle::PM_ScrollBarExtent:
+-        {
+-            NONCLIENTMETRICS ncm;
+-            ncm.cbSize = sizeof(NONCLIENTMETRICS);
+-            if (SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0, dpi))
+-                return qMax(ncm.iScrollHeight, ncm.iScrollWidth);
+-        }
+-        break;
++    {
++        NONCLIENTMETRICS ncm;
++        ncm.cbSize = FIELD_OFFSET(NONCLIENTMETRICS, lfMessageFont) + sizeof(LOGFONT);
++        if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0))
++            return qMax(ncm.iScrollHeight, ncm.iScrollWidth);
++    }
++    break;
+ 
+     case  QStyle::PM_MdiSubWindowFrameWidth:
+-        return GetSystemMetricsForDpi(SM_CYFRAME, dpi);
++        return GetSystemMetrics(SM_CYFRAME);
+ 
+     default:
+         break;
diff --git a/qtbase/src/corelib/io/qstandardpaths_win.cpp b/qtbase/src/corelib/io/qstandardpaths_win.cpp
index 805ce65a5a..aade9d5f97 100644
--- a/qtbase/src/corelib/io/qstandardpaths_win.cpp
+++ b/qtbase/src/corelib/io/qstandardpaths_win.cpp
@@ -12,6 +12,7 @@
 
 #include <qt_windows.h>
 #include <shlobj.h>
+#include <VersionHelpers.h>
 #include <intshcut.h>
 #include <qvarlengtharray.h>
 
@@ -60,20 +61,26 @@ static inline void appendTestMode(QString &path)
 
 static bool isProcessLowIntegrity()
 {
+    if (!IsWindows8OrGreater())
+        return false;
+
     // same as GetCurrentProcessToken()
     const auto process_token = HANDLE(quintptr(-4));
 
     QVarLengthArray<char,256> token_info_buf(256);
     auto* token_info = reinterpret_cast<TOKEN_MANDATORY_LABEL*>(token_info_buf.data());
     DWORD token_info_length = token_info_buf.size();
-    if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length)) {
+    if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length)
+        && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
         // grow buffer and retry GetTokenInformation
         token_info_buf.resize(token_info_length);
         token_info = reinterpret_cast<TOKEN_MANDATORY_LABEL*>(token_info_buf.data());
         if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length))
             return false; // assume "normal" process
     }
-
+    else
+        return false;
+    
     // The GetSidSubAuthorityCount return-code is undefined on failure, so
     // there's no point in checking before dereferencing
     DWORD integrity_level = *GetSidSubAuthority(token_info->Label.Sid, *GetSidSubAuthorityCount(token_info->Label.Sid) - 1);
diff --git a/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp b/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp
index a7663b2481..8459cd5cad 100644
--- a/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp
+++ b/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp
@@ -360,10 +360,15 @@ void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
         ok = t->fastTimerId;
     }
 
-    if (!ok) {
+    typedef BOOL (WINAPI *SetCoalescableTimerFunc) (HWND, UINT_PTR, UINT, TIMERPROC, ULONG);
+    static SetCoalescableTimerFunc mySetCoalescableTimerFunc = 
+        (SetCoalescableTimerFunc)::GetProcAddress(::GetModuleHandle(L"User32"), "SetCoalescableTimer");
+
+    if (!ok && mySetCoalescableTimerFunc) {
         // user normal timers for (Very)CoarseTimers, or if no more multimedia timers available
-        ok = SetCoalescableTimer(internalHwnd, t->timerId, interval, nullptr, tolerance);
+        ok = mySetCoalescableTimerFunc(internalHwnd, t->timerId, interval, nullptr, tolerance);
     }
+    
     if (!ok)
         ok = SetTimer(internalHwnd, t->timerId, interval, nullptr);
 
diff --git a/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp.orig b/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp.orig
new file mode 100644
index 0000000000..a7663b2481
--- /dev/null
+++ b/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp.orig
@@ -0,0 +1,909 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qeventdispatcher_win_p.h"
+
+#include "qcoreapplication.h"
+#include <private/qsystemlibrary_p.h>
+#include "qoperatingsystemversion.h"
+#include "qset.h"
+#include "qsocketnotifier.h"
+#include "qvarlengtharray.h"
+
+#include "qelapsedtimer.h"
+#include "qcoreapplication_p.h"
+#include <private/qthread_p.h>
+
+QT_BEGIN_NAMESPACE
+
+#ifndef TIME_KILL_SYNCHRONOUS
+#  define TIME_KILL_SYNCHRONOUS 0x0100
+#endif
+
+#ifndef QS_RAWINPUT
+#  define QS_RAWINPUT 0x0400
+#endif
+
+#ifndef WM_TOUCH
+#  define WM_TOUCH 0x0240
+#endif
+#ifndef QT_NO_GESTURES
+#ifndef WM_GESTURE
+#  define WM_GESTURE 0x0119
+#endif
+#ifndef WM_GESTURENOTIFY
+#  define WM_GESTURENOTIFY 0x011A
+#endif
+#endif // QT_NO_GESTURES
+
+enum {
+    WM_QT_SOCKETNOTIFIER = WM_USER,
+    WM_QT_SENDPOSTEDEVENTS = WM_USER + 1,
+    WM_QT_ACTIVATENOTIFIERS = WM_USER + 2
+};
+
+enum {
+    SendPostedEventsTimerId = ~1u
+};
+
+class QEventDispatcherWin32Private;
+
+#if !defined(DWORD_PTR) && !defined(Q_OS_WIN64)
+#define DWORD_PTR DWORD
+#endif
+
+LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp);
+
+static quint64 qt_msectime()
+{
+    using namespace std::chrono;
+    auto t = duration_cast<milliseconds>(steady_clock::now().time_since_epoch());
+    return t.count();
+}
+
+QEventDispatcherWin32Private::QEventDispatcherWin32Private()
+    : interrupt(false), internalHwnd(0),
+      sendPostedEventsTimerId(0), wakeUps(0),
+      activateNotifiersPosted(false)
+{
+}
+
+QEventDispatcherWin32Private::~QEventDispatcherWin32Private()
+{
+    if (internalHwnd)
+        DestroyWindow(internalHwnd);
+}
+
+// This function is called by a workerthread
+void WINAPI QT_WIN_CALLBACK qt_fast_timer_proc(uint timerId, uint /*reserved*/, DWORD_PTR user, DWORD_PTR /*reserved*/, DWORD_PTR /*reserved*/)
+{
+    if (!timerId) // sanity check
+        return;
+    auto t = reinterpret_cast<WinTimerInfo*>(user);
+    Q_ASSERT(t);
+    QCoreApplication::postEvent(t->dispatcher, new QTimerEvent(t->timerId));
+}
+
+LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
+{
+    if (message == WM_NCCREATE)
+        return true;
+
+    MSG msg;
+    msg.hwnd = hwnd;
+    msg.message = message;
+    msg.wParam = wp;
+    msg.lParam = lp;
+    QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance();
+    qintptr result;
+    if (!dispatcher) {
+        if (message == WM_TIMER)
+            KillTimer(hwnd, wp);
+        return 0;
+    }
+    if (dispatcher->filterNativeEvent(QByteArrayLiteral("windows_dispatcher_MSG"), &msg, &result))
+        return result;
+
+    auto q = reinterpret_cast<QEventDispatcherWin32 *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
+    QEventDispatcherWin32Private *d = nullptr;
+    if (q != nullptr)
+        d = q->d_func();
+
+    switch (message) {
+    case WM_QT_SOCKETNOTIFIER: {
+        // socket notifier message
+        int type = -1;
+        switch (WSAGETSELECTEVENT(lp)) {
+        case FD_READ:
+        case FD_ACCEPT:
+            type = 0;
+            break;
+        case FD_WRITE:
+        case FD_CONNECT:
+            type = 1;
+            break;
+        case FD_OOB:
+            type = 2;
+            break;
+        case FD_CLOSE:
+            type = 3;
+            break;
+        }
+        if (type >= 0) {
+            Q_ASSERT(d != nullptr);
+            QSNDict *sn_vec[4] = { &d->sn_read, &d->sn_write, &d->sn_except, &d->sn_read };
+            QSNDict *dict = sn_vec[type];
+
+            QSockNot *sn = dict ? dict->value(qintptr(wp)) : 0;
+            if (sn == nullptr) {
+                d->postActivateSocketNotifiers();
+            } else {
+                Q_ASSERT(d->active_fd.contains(sn->fd));
+                QSockFd &sd = d->active_fd[sn->fd];
+                if (sd.selected) {
+                    Q_ASSERT(sd.mask == 0);
+                    d->doWsaAsyncSelect(sn->fd, 0);
+                    sd.selected = false;
+                }
+                d->postActivateSocketNotifiers();
+
+                // Ignore the message if a notification with the same type was
+                // received previously. Suppressed message is definitely spurious.
+                const long eventCode = WSAGETSELECTEVENT(lp);
+                if ((sd.mask & eventCode) != eventCode) {
+                    sd.mask |= eventCode;
+                    QEvent event(type < 3 ? QEvent::SockAct : QEvent::SockClose);
+                    QCoreApplication::sendEvent(sn->obj, &event);
+                }
+            }
+        }
+        return 0;
+    }
+    case WM_QT_ACTIVATENOTIFIERS: {
+        Q_ASSERT(d != nullptr);
+
+        // Postpone activation if we have unhandled socket notifier messages
+        // in the queue. WM_QT_ACTIVATENOTIFIERS will be posted again as a result of
+        // event processing.
+        MSG msg;
+        if (!PeekMessage(&msg, d->internalHwnd,
+                         WM_QT_SOCKETNOTIFIER, WM_QT_SOCKETNOTIFIER, PM_NOREMOVE)
+            && d->queuedSocketEvents.isEmpty()) {
+            // register all socket notifiers
+            for (QSFDict::iterator it = d->active_fd.begin(), end = d->active_fd.end();
+                 it != end; ++it) {
+                QSockFd &sd = it.value();
+                if (!sd.selected) {
+                    d->doWsaAsyncSelect(it.key(), sd.event);
+                    // allow any event to be accepted
+                    sd.mask = 0;
+                    sd.selected = true;
+                }
+            }
+        }
+        d->activateNotifiersPosted = false;
+        return 0;
+    }
+    case WM_TIMER:
+        Q_ASSERT(d != nullptr);
+
+        if (wp == d->sendPostedEventsTimerId)
+            q->sendPostedEvents();
+        else
+            d->sendTimerEvent(wp);
+        return 0;
+    case WM_QT_SENDPOSTEDEVENTS:
+        Q_ASSERT(d != nullptr);
+
+        // We send posted events manually, if the window procedure was invoked
+        // by the foreign event loop (e.g. from the native modal dialog).
+        // Skip sending, if the message queue is not empty.
+        // sendPostedEventsTimer will deliver posted events later.
+        static const UINT mask = QS_ALLEVENTS;
+        if (HIWORD(GetQueueStatus(mask)) == 0)
+            q->sendPostedEvents();
+        else
+            d->startPostedEventsTimer();
+        return 0;
+    } // switch (message)
+
+    return DefWindowProc(hwnd, message, wp, lp);
+}
+
+void QEventDispatcherWin32Private::startPostedEventsTimer()
+{
+    // we received WM_QT_SENDPOSTEDEVENTS, so allow posting it again
+    wakeUps.storeRelaxed(0);
+    if (sendPostedEventsTimerId == 0) {
+        // Start a timer to deliver posted events when the message queue is emptied.
+        sendPostedEventsTimerId = SetTimer(internalHwnd, SendPostedEventsTimerId,
+                                           USER_TIMER_MINIMUM, NULL);
+    }
+}
+
+// Provide class name and atom for the message window used by
+// QEventDispatcherWin32Private via Q_GLOBAL_STATIC shared between threads.
+struct QWindowsMessageWindowClassContext
+{
+    QWindowsMessageWindowClassContext();
+    ~QWindowsMessageWindowClassContext();
+
+    ATOM atom;
+    wchar_t *className;
+};
+
+QWindowsMessageWindowClassContext::QWindowsMessageWindowClassContext()
+    : atom(0), className(0)
+{
+    // make sure that multiple Qt's can coexist in the same process
+    const QString qClassName = QStringLiteral("QEventDispatcherWin32_Internal_Widget")
+        + QString::number(quintptr(qt_internal_proc));
+    className = new wchar_t[qClassName.size() + 1];
+    qClassName.toWCharArray(className);
+    className[qClassName.size()] = 0;
+
+    WNDCLASS wc;
+    wc.style = 0;
+    wc.lpfnWndProc = qt_internal_proc;
+    wc.cbClsExtra = 0;
+    wc.cbWndExtra = 0;
+    wc.hInstance = GetModuleHandle(0);
+    wc.hIcon = 0;
+    wc.hCursor = 0;
+    wc.hbrBackground = 0;
+    wc.lpszMenuName = NULL;
+    wc.lpszClassName = className;
+    atom = RegisterClass(&wc);
+    if (!atom) {
+        qErrnoWarning("%ls RegisterClass() failed", qUtf16Printable(qClassName));
+        delete [] className;
+        className = 0;
+    }
+}
+
+QWindowsMessageWindowClassContext::~QWindowsMessageWindowClassContext()
+{
+    if (className) {
+        UnregisterClass(className, GetModuleHandle(0));
+        delete [] className;
+    }
+}
+
+Q_GLOBAL_STATIC(QWindowsMessageWindowClassContext, qWindowsMessageWindowClassContext)
+
+static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)
+{
+    QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext();
+    if (!ctx->atom)
+        return 0;
+    HWND wnd = CreateWindow(ctx->className,    // classname
+                            ctx->className,    // window name
+                            0,                 // style
+                            0, 0, 0, 0,        // geometry
+                            HWND_MESSAGE,            // parent
+                            0,                 // menu handle
+                            GetModuleHandle(0),     // application
+                            0);                // windows creation data.
+
+    if (!wnd) {
+        qErrnoWarning("CreateWindow() for QEventDispatcherWin32 internal window failed");
+        return 0;
+    }
+
+    SetWindowLongPtr(wnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(eventDispatcher));
+
+    return wnd;
+}
+
+static ULONG calculateNextTimeout(WinTimerInfo *t, quint64 currentTime)
+{
+    uint interval = t->interval;
+    ULONG tolerance = TIMERV_DEFAULT_COALESCING;
+    switch (t->timerType) {
+    case Qt::PreciseTimer:
+        // high precision timer is based on millisecond precision
+        // so no adjustment is necessary
+        break;
+
+    case Qt::CoarseTimer:
+        // this timer has up to 5% coarseness
+        // so our boundaries are 20 ms and 20 s
+        // below 20 ms, 5% inaccuracy is below 1 ms, so we convert to high precision
+        // above 20 s, 5% inaccuracy is above 1 s, so we convert to VeryCoarseTimer
+        if (interval >= 20000) {
+            t->timerType = Qt::VeryCoarseTimer;
+        } else if (interval <= 20) {
+            // no adjustment necessary
+            t->timerType = Qt::PreciseTimer;
+            break;
+        } else {
+            tolerance = interval / 20;
+            break;
+        }
+        Q_FALLTHROUGH();
+    case Qt::VeryCoarseTimer:
+        // the very coarse timer is based on full second precision,
+        // so we round to closest second (but never to zero)
+        tolerance = 1000;
+        if (interval < 1000)
+            interval = 1000;
+        else
+            interval = (interval + 500) / 1000 * 1000;
+        currentTime = currentTime / 1000 * 1000;
+        break;
+    }
+
+    t->interval = interval;
+    t->timeout = currentTime + interval;
+    return tolerance;
+}
+
+void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
+{
+    Q_ASSERT(internalHwnd);
+
+    Q_Q(QEventDispatcherWin32);
+
+    bool ok = false;
+    ULONG tolerance = calculateNextTimeout(t, qt_msectime());
+    uint interval = t->interval;
+    if (interval == 0u) {
+        // optimization for single-shot-zero-timer
+        QCoreApplication::postEvent(q, new QZeroTimerEvent(t->timerId));
+        ok = true;
+    } else if (tolerance == TIMERV_DEFAULT_COALESCING) {
+        // 3/2016: Although MSDN states timeSetEvent() is deprecated, the function
+        // is still deemed to be the most reliable precision timer.
+        t->fastTimerId = timeSetEvent(interval, 1, qt_fast_timer_proc, DWORD_PTR(t),
+                                      TIME_CALLBACK_FUNCTION | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS);
+        ok = t->fastTimerId;
+    }
+
+    if (!ok) {
+        // user normal timers for (Very)CoarseTimers, or if no more multimedia timers available
+        ok = SetCoalescableTimer(internalHwnd, t->timerId, interval, nullptr, tolerance);
+    }
+    if (!ok)
+        ok = SetTimer(internalHwnd, t->timerId, interval, nullptr);
+
+    if (!ok)
+        qErrnoWarning("QEventDispatcherWin32::registerTimer: Failed to create a timer");
+}
+
+void QEventDispatcherWin32Private::unregisterTimer(WinTimerInfo *t)
+{
+    if (t->interval == 0) {
+        QCoreApplicationPrivate::removePostedTimerEvent(t->dispatcher, t->timerId);
+    } else if (t->fastTimerId != 0) {
+        timeKillEvent(t->fastTimerId);
+        QCoreApplicationPrivate::removePostedTimerEvent(t->dispatcher, t->timerId);
+    } else {
+        KillTimer(internalHwnd, t->timerId);
+    }
+    t->timerId = -1;
+    if (!t->inTimerEvent)
+        delete t;
+}
+
+void QEventDispatcherWin32Private::sendTimerEvent(int timerId)
+{
+    WinTimerInfo *t = timerDict.value(timerId);
+    if (t && !t->inTimerEvent) {
+        // send event, but don't allow it to recurse
+        t->inTimerEvent = true;
+
+        // recalculate next emission
+        calculateNextTimeout(t, qt_msectime());
+
+        QTimerEvent e(t->timerId);
+        QCoreApplication::sendEvent(t->obj, &e);
+
+        // timer could have been removed
+        if (t->timerId == -1) {
+            delete t;
+        } else {
+            t->inTimerEvent = false;
+        }
+    }
+}
+
+void QEventDispatcherWin32Private::doWsaAsyncSelect(qintptr socket, long event)
+{
+    Q_ASSERT(internalHwnd);
+    // BoundsChecker may emit a warning for WSAAsyncSelect when event == 0
+    // This is a BoundsChecker bug and not a Qt bug
+    WSAAsyncSelect(socket, internalHwnd, event ? int(WM_QT_SOCKETNOTIFIER) : 0, event);
+}
+
+void QEventDispatcherWin32Private::postActivateSocketNotifiers()
+{
+    if (!activateNotifiersPosted)
+        activateNotifiersPosted = PostMessage(internalHwnd, WM_QT_ACTIVATENOTIFIERS, 0, 0);
+}
+
+QEventDispatcherWin32::QEventDispatcherWin32(QObject *parent)
+    : QEventDispatcherWin32(*new QEventDispatcherWin32Private, parent)
+{
+}
+
+QEventDispatcherWin32::QEventDispatcherWin32(QEventDispatcherWin32Private &dd, QObject *parent)
+    : QAbstractEventDispatcher(dd, parent)
+{
+    Q_D(QEventDispatcherWin32);
+
+    d->internalHwnd = qt_create_internal_window(this);
+}
+
+QEventDispatcherWin32::~QEventDispatcherWin32()
+{
+}
+
+static bool isUserInputMessage(UINT message)
+{
+    return (message >= WM_KEYFIRST && message <= WM_KEYLAST)
+        || (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST)
+        || message == WM_MOUSEWHEEL
+        || message == WM_MOUSEHWHEEL
+        || message == WM_TOUCH
+#ifndef QT_NO_GESTURES
+        || message == WM_GESTURE
+        || message == WM_GESTURENOTIFY
+#endif
+// Pointer input: Exclude WM_NCPOINTERUPDATE .. WM_POINTERROUTEDRELEASED
+        || (message >= 0x0241 && message <= 0x0253)
+        || message == WM_CLOSE;
+}
+
+bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
+{
+    Q_D(QEventDispatcherWin32);
+
+    // We don't know _when_ the interrupt occurred so we have to honor it.
+    const bool wasInterrupted = d->interrupt.fetchAndStoreRelaxed(false);
+    emit awake();
+
+    // To prevent livelocks, send posted events once per iteration.
+    // QCoreApplication::sendPostedEvents() takes care about recursions.
+    sendPostedEvents();
+
+    if (wasInterrupted)
+        return false;
+
+    auto threadData = d->threadData.loadRelaxed();
+    bool canWait;
+    bool retVal = false;
+    do {
+        QVarLengthArray<MSG> processedTimers;
+        while (!d->interrupt.loadRelaxed()) {
+            MSG msg;
+
+            if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
+                // process queued user input events
+                msg = d->queuedUserInputEvents.takeFirst();
+            } else if (!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {
+                // process queued socket events
+                msg = d->queuedSocketEvents.takeFirst();
+            } else if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
+                if (flags.testFlag(QEventLoop::ExcludeUserInputEvents)
+                    && isUserInputMessage(msg.message)) {
+                    // queue user input events for later processing
+                    d->queuedUserInputEvents.append(msg);
+                    continue;
+                }
+                if ((flags & QEventLoop::ExcludeSocketNotifiers)
+                    && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {
+                    // queue socket events for later processing
+                    d->queuedSocketEvents.append(msg);
+                    continue;
+                }
+            } else if (MsgWaitForMultipleObjectsEx(0, NULL, 0, QS_ALLINPUT, MWMO_ALERTABLE)
+                       == WAIT_OBJECT_0) {
+                // a new message has arrived, process it
+                continue;
+            } else {
+                // nothing to do, so break
+                break;
+            }
+
+            if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) {
+                d->startPostedEventsTimer();
+                // Set result to 'true' because the message was sent by wakeUp().
+                retVal = true;
+                continue;
+            }
+            if (msg.message == WM_TIMER) {
+                // Skip timer event intended for use inside foreign loop.
+                if (d->internalHwnd == msg.hwnd && msg.wParam == d->sendPostedEventsTimerId)
+                    continue;
+
+                // avoid live-lock by keeping track of the timers we've already sent
+                bool found = false;
+                for (int i = 0; !found && i < processedTimers.count(); ++i) {
+                    const MSG processed = processedTimers.constData()[i];
+                    found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam);
+                }
+                if (found)
+                    continue;
+                processedTimers.append(msg);
+            } else if (msg.message == WM_QUIT) {
+                if (QCoreApplication::instance())
+                    QCoreApplication::instance()->quit();
+                return false;
+            }
+
+            if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
+                TranslateMessage(&msg);
+                DispatchMessage(&msg);
+            }
+            retVal = true;
+        }
+
+        // wait for message
+        canWait = (!retVal
+                   && !d->interrupt.loadRelaxed()
+                   && flags.testFlag(QEventLoop::WaitForMoreEvents)
+                   && threadData->canWaitLocked());
+        if (canWait) {
+            emit aboutToBlock();
+            MsgWaitForMultipleObjectsEx(0, NULL, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
+            emit awake();
+        }
+    } while (canWait);
+
+    return retVal;
+}
+
+void QEventDispatcherWin32::registerSocketNotifier(QSocketNotifier *notifier)
+{
+    Q_ASSERT(notifier);
+    qintptr sockfd = notifier->socket();
+    int type = notifier->type();
+#ifndef QT_NO_DEBUG
+    if (sockfd < 0) {
+        qWarning("QEventDispatcherWin32::registerSocketNotifier: invalid socket identifier");
+        return;
+    }
+    if (notifier->thread() != thread() || thread() != QThread::currentThread()) {
+        qWarning("QEventDispatcherWin32: socket notifiers cannot be enabled from another thread");
+        return;
+    }
+#endif
+
+    Q_D(QEventDispatcherWin32);
+    QSNDict *sn_vec[3] = { &d->sn_read, &d->sn_write, &d->sn_except };
+    QSNDict *dict = sn_vec[type];
+
+    if (QCoreApplication::closingDown()) // ### d->exitloop?
+        return; // after sn_cleanup, don't reinitialize.
+
+    if (dict->contains(sockfd)) {
+        const char *t[] = { "Read", "Write", "Exception" };
+    /* Variable "socket" below is a function pointer. */
+        qWarning("QSocketNotifier: Multiple socket notifiers for "
+                 "same socket %" PRIdQINTPTR " and type %s", sockfd, t[type]);
+    }
+
+    QSockNot *sn = new QSockNot;
+    sn->obj = notifier;
+    sn->fd  = sockfd;
+    dict->insert(sn->fd, sn);
+
+    long event = 0;
+    if (d->sn_read.contains(sockfd))
+        event |= FD_READ | FD_CLOSE | FD_ACCEPT;
+    if (d->sn_write.contains(sockfd))
+        event |= FD_WRITE | FD_CONNECT;
+    if (d->sn_except.contains(sockfd))
+        event |= FD_OOB;
+
+    QSFDict::iterator it = d->active_fd.find(sockfd);
+    if (it != d->active_fd.end()) {
+        QSockFd &sd = it.value();
+        if (sd.selected) {
+            d->doWsaAsyncSelect(sockfd, 0);
+            sd.selected = false;
+        }
+        sd.event |= event;
+    } else {
+        // Although WSAAsyncSelect(..., 0), which is called from
+        // unregisterSocketNotifier(), immediately disables event message
+        // posting for the socket, it is possible that messages could be
+        // waiting in the application message queue even if the socket was
+        // closed. Also, some events could be implicitly re-enabled due
+        // to system calls. Ignore these superfluous events until all
+        // pending notifications have been suppressed. Next activation of
+        // socket notifiers will reset the mask.
+        d->active_fd.insert(sockfd, QSockFd(event, FD_READ | FD_CLOSE | FD_ACCEPT | FD_WRITE
+                                                   | FD_CONNECT | FD_OOB));
+    }
+
+    d->postActivateSocketNotifiers();
+}
+
+void QEventDispatcherWin32::unregisterSocketNotifier(QSocketNotifier *notifier)
+{
+    Q_ASSERT(notifier);
+#ifndef QT_NO_DEBUG
+    qintptr sockfd = notifier->socket();
+    if (sockfd < 0) {
+        qWarning("QEventDispatcherWin32::unregisterSocketNotifier: invalid socket identifier");
+        return;
+    }
+    if (notifier->thread() != thread() || thread() != QThread::currentThread()) {
+        qWarning("QEventDispatcherWin32: socket notifiers cannot be disabled from another thread");
+        return;
+    }
+#endif
+    doUnregisterSocketNotifier(notifier);
+}
+
+void QEventDispatcherWin32::doUnregisterSocketNotifier(QSocketNotifier *notifier)
+{
+    Q_D(QEventDispatcherWin32);
+    int type = notifier->type();
+    qintptr sockfd = notifier->socket();
+    Q_ASSERT(sockfd >= 0);
+
+    QSFDict::iterator it = d->active_fd.find(sockfd);
+    if (it != d->active_fd.end()) {
+        QSockFd &sd = it.value();
+        if (sd.selected)
+            d->doWsaAsyncSelect(sockfd, 0);
+        const long event[3] = { FD_READ | FD_CLOSE | FD_ACCEPT, FD_WRITE | FD_CONNECT, FD_OOB };
+        sd.event ^= event[type];
+        if (sd.event == 0) {
+            d->active_fd.erase(it);
+        } else if (sd.selected) {
+            sd.selected = false;
+            d->postActivateSocketNotifiers();
+        }
+    }
+
+    QSNDict *sn_vec[3] = { &d->sn_read, &d->sn_write, &d->sn_except };
+    QSNDict *dict = sn_vec[type];
+    QSockNot *sn = dict->value(sockfd);
+    if (!sn)
+        return;
+
+    dict->remove(sockfd);
+    delete sn;
+}
+
+void QEventDispatcherWin32::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object)
+{
+#ifndef QT_NO_DEBUG
+    if (timerId < 1 || interval < 0 || !object) {
+        qWarning("QEventDispatcherWin32::registerTimer: invalid arguments");
+        return;
+    }
+    if (object->thread() != thread() || thread() != QThread::currentThread()) {
+        qWarning("QEventDispatcherWin32::registerTimer: timers cannot be started from another thread");
+        return;
+    }
+#endif
+
+    Q_D(QEventDispatcherWin32);
+
+    // exiting ... do not register new timers
+    // (QCoreApplication::closingDown() is set too late to be used here)
+    if (d->closingDown)
+        return;
+
+    WinTimerInfo *t = new WinTimerInfo;
+    t->dispatcher = this;
+    t->timerId  = timerId;
+    t->interval = interval;
+    t->timerType = timerType;
+    t->obj  = object;
+    t->inTimerEvent = false;
+    t->fastTimerId = 0;
+
+    d->registerTimer(t);
+
+    d->timerDict.insert(t->timerId, t);          // store timers in dict
+}
+
+bool QEventDispatcherWin32::unregisterTimer(int timerId)
+{
+#ifndef QT_NO_DEBUG
+    if (timerId < 1) {
+        qWarning("QEventDispatcherWin32::unregisterTimer: invalid argument");
+        return false;
+    }
+    if (thread() != QThread::currentThread()) {
+        qWarning("QEventDispatcherWin32::unregisterTimer: timers cannot be stopped from another thread");
+        return false;
+    }
+#endif
+
+    Q_D(QEventDispatcherWin32);
+
+    WinTimerInfo *t = d->timerDict.take(timerId);
+    if (!t)
+        return false;
+
+    d->unregisterTimer(t);
+    return true;
+}
+
+bool QEventDispatcherWin32::unregisterTimers(QObject *object)
+{
+#ifndef QT_NO_DEBUG
+    if (!object) {
+        qWarning("QEventDispatcherWin32::unregisterTimers: invalid argument");
+        return false;
+    }
+    if (object->thread() != thread() || thread() != QThread::currentThread()) {
+        qWarning("QEventDispatcherWin32::unregisterTimers: timers cannot be stopped from another thread");
+        return false;
+    }
+#endif
+
+    Q_D(QEventDispatcherWin32);
+    if (d->timerDict.isEmpty())
+        return false;
+
+    auto it = d->timerDict.begin();
+    while (it != d->timerDict.end()) {
+        WinTimerInfo *t = it.value();
+        Q_ASSERT(t);
+        if (t->obj == object) {
+            it = d->timerDict.erase(it);
+            d->unregisterTimer(t);
+        } else {
+            ++it;
+        }
+    }
+    return true;
+}
+
+QList<QEventDispatcherWin32::TimerInfo>
+QEventDispatcherWin32::registeredTimers(QObject *object) const
+{
+#ifndef QT_NO_DEBUG
+    if (!object) {
+        qWarning("QEventDispatcherWin32:registeredTimers: invalid argument");
+        return QList<TimerInfo>();
+    }
+#endif
+
+    Q_D(const QEventDispatcherWin32);
+    QList<TimerInfo> list;
+    for (WinTimerInfo *t : std::as_const(d->timerDict)) {
+        Q_ASSERT(t);
+        if (t->obj == object)
+            list << TimerInfo(t->timerId, t->interval, t->timerType);
+    }
+    return list;
+}
+
+int QEventDispatcherWin32::remainingTime(int timerId)
+{
+#ifndef QT_NO_DEBUG
+    if (timerId < 1) {
+        qWarning("QEventDispatcherWin32::remainingTime: invalid argument");
+        return -1;
+    }
+#endif
+
+    Q_D(QEventDispatcherWin32);
+
+    quint64 currentTime = qt_msectime();
+
+    WinTimerInfo *t = d->timerDict.value(timerId);
+    if (t) {
+        // timer found, return time to wait
+        return t->timeout > currentTime ? t->timeout - currentTime : 0;
+    }
+
+#ifndef QT_NO_DEBUG
+    qWarning("QEventDispatcherWin32::remainingTime: timer id %d not found", timerId);
+#endif
+
+    return -1;
+}
+
+void QEventDispatcherWin32::wakeUp()
+{
+    Q_D(QEventDispatcherWin32);
+    if (d->wakeUps.testAndSetRelaxed(0, 1)) {
+        // post a WM_QT_SENDPOSTEDEVENTS to this thread if there isn't one already pending
+        if (!PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0))
+            qErrnoWarning("QEventDispatcherWin32::wakeUp: Failed to post a message");
+    }
+}
+
+void QEventDispatcherWin32::interrupt()
+{
+    Q_D(QEventDispatcherWin32);
+    d->interrupt.storeRelaxed(true);
+    wakeUp();
+}
+
+void QEventDispatcherWin32::startingUp()
+{ }
+
+void QEventDispatcherWin32::closingDown()
+{
+    Q_D(QEventDispatcherWin32);
+
+    // clean up any socketnotifiers
+    while (!d->sn_read.isEmpty())
+        doUnregisterSocketNotifier((*(d->sn_read.begin()))->obj);
+    while (!d->sn_write.isEmpty())
+        doUnregisterSocketNotifier((*(d->sn_write.begin()))->obj);
+    while (!d->sn_except.isEmpty())
+        doUnregisterSocketNotifier((*(d->sn_except.begin()))->obj);
+    Q_ASSERT(d->active_fd.isEmpty());
+
+    // clean up any timers
+    for (WinTimerInfo *t : std::as_const(d->timerDict))
+        d->unregisterTimer(t);
+    d->timerDict.clear();
+
+    d->closingDown = true;
+
+    if (d->sendPostedEventsTimerId != 0)
+        KillTimer(d->internalHwnd, d->sendPostedEventsTimerId);
+    d->sendPostedEventsTimerId = 0;
+}
+
+bool QEventDispatcherWin32::event(QEvent *e)
+{
+    Q_D(QEventDispatcherWin32);
+    switch (e->type()) {
+    case QEvent::ZeroTimerEvent: {
+        QZeroTimerEvent *zte = static_cast<QZeroTimerEvent*>(e);
+        WinTimerInfo *t = d->timerDict.value(zte->timerId());
+        if (t) {
+            t->inTimerEvent = true;
+
+            QTimerEvent te(zte->timerId());
+            QCoreApplication::sendEvent(t->obj, &te);
+
+            // timer could have been removed
+            if (t->timerId == -1) {
+                delete t;
+            } else {
+                if (t->interval == 0 && t->inTimerEvent) {
+                    // post the next zero timer event as long as the timer was not restarted
+                    QCoreApplication::postEvent(this, new QZeroTimerEvent(zte->timerId()));
+                }
+
+                t->inTimerEvent = false;
+            }
+        }
+        return true;
+    }
+    case QEvent::Timer:
+        d->sendTimerEvent(static_cast<const QTimerEvent*>(e)->timerId());
+        break;
+    default:
+        break;
+    }
+    return QAbstractEventDispatcher::event(e);
+}
+
+void QEventDispatcherWin32::sendPostedEvents()
+{
+    Q_D(QEventDispatcherWin32);
+
+    if (d->sendPostedEventsTimerId != 0)
+        KillTimer(d->internalHwnd, d->sendPostedEventsTimerId);
+    d->sendPostedEventsTimerId = 0;
+
+    // Allow posting WM_QT_SENDPOSTEDEVENTS message.
+    d->wakeUps.storeRelaxed(0);
+
+    QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData.loadRelaxed());
+}
+
+HWND QEventDispatcherWin32::internalHwnd()
+{
+    return d_func()->internalHwnd;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qeventdispatcher_win_p.cpp"
diff --git a/qtbase/src/corelib/kernel/qfunctions_win.cpp b/qtbase/src/corelib/kernel/qfunctions_win.cpp
index d5ce3e5894..70b67ea01f 100644
--- a/qtbase/src/corelib/kernel/qfunctions_win.cpp
+++ b/qtbase/src/corelib/kernel/qfunctions_win.cpp
@@ -43,23 +43,33 @@ QComHelper::~QComHelper()
 */
 bool qt_win_hasPackageIdentity()
 {
+    typedef BOOL (WINAPI *GetCurrentPackageFullNameFunc) (UINT32 *, PWSTR);
+    static GetCurrentPackageFullNameFunc myGetCurrentPackageFullName = 
+        (GetCurrentPackageFullNameFunc)::GetProcAddress(::GetModuleHandle(L"kernel32"), "GetCurrentPackageFullName");
+
+    if (myGetCurrentPackageFullName)
+    {
 #if defined(HAS_APPMODEL)
-    static const bool hasPackageIdentity = []() {
-        UINT32 length = 0;
-        switch (const auto result = GetCurrentPackageFullName(&length, nullptr)) {
-        case ERROR_INSUFFICIENT_BUFFER:
-            return true;
-        case APPMODEL_ERROR_NO_PACKAGE:
-            return false;
-        default:
-            qWarning("Failed to resolve package identity (error code %ld)", result);
-            return false;
-        }
-    }();
-    return hasPackageIdentity;
+
+        static const bool hasPackageIdentity = []() {
+            UINT32 length = 0;
+            switch (const auto result = myGetCurrentPackageFullName(&length, nullptr)) {
+            case ERROR_INSUFFICIENT_BUFFER:
+                return true;
+            case APPMODEL_ERROR_NO_PACKAGE:
+                return false;
+            default:
+                qWarning("Failed to resolve package identity (error code %ld)", result);
+                return false;
+            }
+        }();
+        return hasPackageIdentity;
 #else
-    return false;
+        return false;
 #endif
+    }
+
+    return false;
 }
 
 QT_END_NAMESPACE
diff --git a/qtbase/src/corelib/thread/qfutex_p.h b/qtbase/src/corelib/thread/qfutex_p.h
index 8ba798f920..639946b00b 100644
--- a/qtbase/src/corelib/thread/qfutex_p.h
+++ b/qtbase/src/corelib/thread/qfutex_p.h
@@ -40,8 +40,6 @@ QT_END_NAMESPACE
 #elif defined(Q_OS_LINUX) && !defined(QT_LINUXBASE)
 // use Linux mutexes everywhere except for LSB builds
 #  include "qfutex_linux_p.h"
-#elif defined(Q_OS_WIN)
-#  include "qfutex_win_p.h"
 #else
 QT_BEGIN_NAMESPACE
 namespace QtFutex = QtDummyFutex;
diff --git a/qtbase/src/corelib/thread/qfutex_p.h.orig b/qtbase/src/corelib/thread/qfutex_p.h.orig
new file mode 100644
index 0000000000..8ba798f920
--- /dev/null
+++ b/qtbase/src/corelib/thread/qfutex_p.h.orig
@@ -0,0 +1,51 @@
+// Copyright (C) 2017 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QFUTEX_P_H
+#define QFUTEX_P_H
+
+//
+//  W A R N I N G
+//  -------------
+//
+// This file is not part of the Qt API.  It exists purely as an
+// implementation detail.  This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qdeadlinetimer.h>
+#include <private/qglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QtDummyFutex {
+    constexpr inline bool futexAvailable() { return false; }
+    template <typename Atomic>
+    inline bool futexWait(Atomic &, typename Atomic::Type, QDeadlineTimer = {})
+    { Q_UNREACHABLE_RETURN(false); }
+    template <typename Atomic> inline void futexWakeOne(Atomic &)
+    { Q_UNREACHABLE(); }
+    template <typename Atomic> inline void futexWakeAll(Atomic &)
+    { Q_UNREACHABLE(); }
+}
+
+QT_END_NAMESPACE
+
+#if defined(Q_OS_DARWIN)
+#  include "qfutex_mac_p.h"
+#elif defined(Q_OS_FREEBSD)
+#  include "qfutex_freebsd_p.h"
+#elif defined(Q_OS_LINUX) && !defined(QT_LINUXBASE)
+// use Linux mutexes everywhere except for LSB builds
+#  include "qfutex_linux_p.h"
+#elif defined(Q_OS_WIN)
+#  include "qfutex_win_p.h"
+#else
+QT_BEGIN_NAMESPACE
+namespace QtFutex = QtDummyFutex;
+QT_END_NAMESPACE
+#endif
+
+#endif // QFUTEX_P_H
diff --git a/qtbase/src/corelib/thread/qfutex_p.h.rej b/qtbase/src/corelib/thread/qfutex_p.h.rej
new file mode 100644
index 0000000000..4193346599
--- /dev/null
+++ b/qtbase/src/corelib/thread/qfutex_p.h.rej
@@ -0,0 +1,42 @@
+--- qtbase/src/corelib/thread/qfutex_p.h
++++ qtbase/src/corelib/thread/qfutex_p.h
+@@ -107,39 +107,6 @@ namespace QtLinuxFutex {
+ }
+ namespace QtFutex = QtLinuxFutex;
+ QT_END_NAMESPACE
+-
+-#elif defined(Q_OS_WIN)
+-#  include <qt_windows.h>
+-
+-QT_BEGIN_NAMESPACE
+-namespace QtWindowsFutex {
+-#define QT_ALWAYS_USE_FUTEX
+-constexpr inline bool futexAvailable() { return true; }
+-
+-template <typename Atomic>
+-inline void futexWait(Atomic &futex, typename Atomic::Type expectedValue)
+-{
+-    QtTsan::futexRelease(&futex);
+-    WaitOnAddress(&futex, &expectedValue, sizeof(expectedValue), INFINITE);
+-    QtTsan::futexAcquire(&futex);
+-}
+-template <typename Atomic>
+-inline bool futexWait(Atomic &futex, typename Atomic::Type expectedValue, qint64 nstimeout)
+-{
+-    BOOL r = WaitOnAddress(&futex, &expectedValue, sizeof(expectedValue), DWORD(nstimeout / 1000 / 1000));
+-    return r || GetLastError() != ERROR_TIMEOUT;
+-}
+-template <typename Atomic> inline void futexWakeAll(Atomic &futex)
+-{
+-    WakeByAddressAll(&futex);
+-}
+-template <typename Atomic> inline void futexWakeOne(Atomic &futex)
+-{
+-    WakeByAddressSingle(&futex);
+-}
+-}
+-namespace QtFutex = QtWindowsFutex;
+-QT_END_NAMESPACE
+ #else
+ 
+ QT_BEGIN_NAMESPACE
diff --git a/qtbase/src/corelib/thread/qmutex.cpp b/qtbase/src/corelib/thread/qmutex.cpp
index ec6c711a4f..71ef29e675 100644
--- a/qtbase/src/corelib/thread/qmutex.cpp
+++ b/qtbase/src/corelib/thread/qmutex.cpp
@@ -910,8 +910,10 @@ QT_END_NAMESPACE
 
 #if defined(QT_ALWAYS_USE_FUTEX)
 // nothing
-#elif defined(Q_OS_DARWIN)
+#elif defined(Q_OS_MAC)
 #  include "qmutex_mac.cpp"
+#elif defined(Q_OS_WIN)
+#  include "qmutex_win.cpp"
 #else
 #  include "qmutex_unix.cpp"
-#endif
+#endif
\ No newline at end of file
diff --git a/qtbase/src/corelib/thread/qmutex.cpp.orig b/qtbase/src/corelib/thread/qmutex.cpp.orig
new file mode 100644
index 0000000000..ec6c711a4f
--- /dev/null
+++ b/qtbase/src/corelib/thread/qmutex.cpp.orig
@@ -0,0 +1,917 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// Copyright (C) 2012 Olivier Goffart <ogoffart@woboq.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "global/qglobal.h"
+#include "qplatformdefs.h"
+#include "qmutex.h"
+#include <qdebug.h>
+#include "qatomic.h"
+#include "qfutex_p.h"
+#include "qthread.h"
+#include "qmutex_p.h"
+
+#ifndef QT_ALWAYS_USE_FUTEX
+#include "private/qfreelist_p.h"
+#endif
+
+QT_BEGIN_NAMESPACE
+
+using namespace QtFutex;
+static inline QMutexPrivate *dummyFutexValue()
+{
+    return reinterpret_cast<QMutexPrivate *>(quintptr(3));
+}
+
+/*
+    \class QBasicMutex
+    \inmodule QtCore
+    \brief QMutex POD
+    \internal
+
+    \ingroup thread
+
+    - Can be used as global static object.
+    - Always non-recursive
+    - Do not use tryLock with timeout > 0, else you can have a leak (see the ~QMutex destructor)
+*/
+
+/*!
+    \class QMutex
+    \inmodule QtCore
+    \brief The QMutex class provides access serialization between threads.
+
+    \threadsafe
+
+    \ingroup thread
+
+    The purpose of a QMutex is to protect an object, data structure or
+    section of code so that only one thread can access it at a time
+    (this is similar to the Java \c synchronized keyword). It is
+    usually best to use a mutex with a QMutexLocker since this makes
+    it easy to ensure that locking and unlocking are performed
+    consistently.
+
+    For example, say there is a method that prints a message to the
+    user on two lines:
+
+    \snippet code/src_corelib_thread_qmutex.cpp 0
+
+    If these two methods are called in succession, the following happens:
+
+    \snippet code/src_corelib_thread_qmutex.cpp 1
+
+    If these two methods are called simultaneously from two threads then the
+    following sequence could result:
+
+    \snippet code/src_corelib_thread_qmutex.cpp 2
+
+    If we add a mutex, we should get the result we want:
+
+    \snippet code/src_corelib_thread_qmutex.cpp 3
+
+    Then only one thread can modify \c number at any given time and
+    the result is correct. This is a trivial example, of course, but
+    applies to any other case where things need to happen in a
+    particular sequence.
+
+    When you call lock() in a thread, other threads that try to call
+    lock() in the same place will block until the thread that got the
+    lock calls unlock(). A non-blocking alternative to lock() is
+    tryLock().
+
+    QMutex is optimized to be fast in the non-contended case. It
+    will not allocate memory if there is no contention on that mutex.
+    It is constructed and destroyed with almost no overhead,
+    which means it is fine to have many mutexes as part of other classes.
+
+    \sa QRecursiveMutex, QMutexLocker, QReadWriteLock, QSemaphore, QWaitCondition
+*/
+
+/*!
+    \fn QMutex::QMutex()
+
+    Constructs a new mutex. The mutex is created in an unlocked state.
+*/
+
+/*! \fn QMutex::~QMutex()
+
+    Destroys the mutex.
+
+    \warning Destroying a locked mutex may result in undefined behavior.
+*/
+void QBasicMutex::destroyInternal(QMutexPrivate *d)
+{
+    if (!d)
+        return;
+    if (!futexAvailable()) {
+        if (d != dummyLocked() && d->possiblyUnlocked.loadRelaxed() && tryLock()) {
+            unlock();
+            return;
+        }
+    }
+    qWarning("QMutex: destroying locked mutex");
+}
+
+/*! \fn void QMutex::lock()
+
+    Locks the mutex. If another thread has locked the mutex then this
+    call will block until that thread has unlocked it.
+
+    Calling this function multiple times on the same mutex from the
+    same thread will cause a \e dead-lock.
+
+    \sa unlock()
+*/
+
+/*! \fn bool QMutex::tryLock(int timeout)
+
+    Attempts to lock the mutex. This function returns \c true if the lock
+    was obtained; otherwise it returns \c false. If another thread has
+    locked the mutex, this function will wait for at most \a timeout
+    milliseconds for the mutex to become available.
+
+    Note: Passing a negative number as the \a timeout is equivalent to
+    calling lock(), i.e. this function will wait forever until mutex
+    can be locked if \a timeout is negative.
+
+    If the lock was obtained, the mutex must be unlocked with unlock()
+    before another thread can successfully lock it.
+
+    Calling this function multiple times on the same mutex from the
+    same thread will cause a \e dead-lock.
+
+    \sa lock(), unlock()
+*/
+
+/*! \fn bool QMutex::tryLock(QDeadlineTimer timer)
+    \since 6.6
+
+    Attempts to lock the mutex. This function returns \c true if the lock
+    was obtained; otherwise it returns \c false. If another thread has
+    locked the mutex, this function will wait until \a timer expires
+    for the mutex to become available.
+
+    If the lock was obtained, the mutex must be unlocked with unlock()
+    before another thread can successfully lock it.
+
+    Calling this function multiple times on the same mutex from the
+    same thread will cause a \e dead-lock.
+
+    \sa lock(), unlock()
+*/
+
+/*! \fn bool QMutex::tryLock()
+    \overload
+
+    Attempts to lock the mutex. This function returns \c true if the lock
+    was obtained; otherwise it returns \c false.
+
+    If the lock was obtained, the mutex must be unlocked with unlock()
+    before another thread can successfully lock it.
+
+    Calling this function multiple times on the same mutex from the
+    same thread will cause a \e dead-lock.
+
+    \sa lock(), unlock()
+*/
+
+/*! \fn bool QMutex::try_lock()
+    \since 5.8
+
+    Attempts to lock the mutex. This function returns \c true if the lock
+    was obtained; otherwise it returns \c false.
+
+    This function is provided for compatibility with the Standard Library
+    concept \c Lockable. It is equivalent to tryLock().
+*/
+
+/*! \fn template <class Rep, class Period> bool QMutex::try_lock_for(std::chrono::duration<Rep, Period> duration)
+    \since 5.8
+
+    Attempts to lock the mutex. This function returns \c true if the lock
+    was obtained; otherwise it returns \c false. If another thread has
+    locked the mutex, this function will wait for at least \a duration
+    for the mutex to become available.
+
+    Note: Passing a negative duration as the \a duration is equivalent to
+    calling try_lock(). This behavior differs from tryLock().
+
+    If the lock was obtained, the mutex must be unlocked with unlock()
+    before another thread can successfully lock it.
+
+    Calling this function multiple times on the same mutex from the
+    same thread will cause a \e dead-lock.
+
+    \sa lock(), unlock()
+*/
+
+/*! \fn template<class Clock, class Duration> bool QMutex::try_lock_until(std::chrono::time_point<Clock, Duration> timePoint)
+    \since 5.8
+
+    Attempts to lock the mutex. This function returns \c true if the lock
+    was obtained; otherwise it returns \c false. If another thread has
+    locked the mutex, this function will wait at least until \a timePoint
+    for the mutex to become available.
+
+    Note: Passing a \a timePoint which has already passed is equivalent
+    to calling try_lock(). This behavior differs from tryLock().
+
+    If the lock was obtained, the mutex must be unlocked with unlock()
+    before another thread can successfully lock it.
+
+    Calling this function multiple times on the same mutex from the
+    same thread will cause a \e dead-lock.
+
+    \sa lock(), unlock()
+*/
+
+/*! \fn void QMutex::unlock()
+
+    Unlocks the mutex. Attempting to unlock a mutex in a different
+    thread to the one that locked it results in an error. Unlocking a
+    mutex that is not locked results in undefined behavior.
+
+    \sa lock()
+*/
+
+/*!
+    \class QRecursiveMutex
+    \inmodule QtCore
+    \since 5.14
+    \brief The QRecursiveMutex class provides access serialization between threads.
+
+    \threadsafe
+
+    \ingroup thread
+
+    The QRecursiveMutex class is a mutex, like QMutex, with which it is
+    API-compatible. It differs from QMutex by accepting lock() calls from
+    the same thread any number of times. QMutex would deadlock in this situation.
+
+    QRecursiveMutex is much more expensive to construct and operate on, so
+    use a plain QMutex whenever you can. Sometimes, one public function,
+    however, calls another public function, and they both need to lock the
+    same mutex. In this case, you have two options:
+
+    \list
+    \li Factor the code that needs mutex protection into private functions,
+    which assume that the mutex is held when they are called, and lock a
+    plain QMutex in the public functions before you call the private
+    implementation ones.
+    \li Or use a recursive mutex, so it doesn't matter that the first public
+    function has already locked the mutex when the second one wishes to do so.
+    \endlist
+
+    \sa QMutex, QMutexLocker, QReadWriteLock, QSemaphore, QWaitCondition
+*/
+
+/*! \fn QRecursiveMutex::QRecursiveMutex()
+
+    Constructs a new recursive mutex. The mutex is created in an unlocked state.
+
+    \sa lock(), unlock()
+*/
+
+/*!
+    Destroys the mutex.
+
+    \warning Destroying a locked mutex may result in undefined behavior.
+*/
+QRecursiveMutex::~QRecursiveMutex()
+{
+}
+
+/*! \fn void QRecursiveMutex::lock()
+
+    Locks the mutex. If another thread has locked the mutex then this
+    call will block until that thread has unlocked it.
+
+    Calling this function multiple times on the same mutex from the
+    same thread is allowed.
+
+    \sa unlock()
+*/
+
+/*!
+    \fn QRecursiveMutex::tryLock(int timeout)
+
+    Attempts to lock the mutex. This function returns \c true if the lock
+    was obtained; otherwise it returns \c false. If another thread has
+    locked the mutex, this function will wait for at most \a timeout
+    milliseconds for the mutex to become available.
+
+    Note: Passing a negative number as the \a timeout is equivalent to
+    calling lock(), i.e. this function will wait forever until mutex
+    can be locked if \a timeout is negative.
+
+    If the lock was obtained, the mutex must be unlocked with unlock()
+    before another thread can successfully lock it.
+
+    Calling this function multiple times on the same mutex from the
+    same thread is allowed.
+
+    \sa lock(), unlock()
+*/
+
+/*!
+    \since 6.6
+
+    Attempts to lock the mutex. This function returns \c true if the lock
+    was obtained; otherwise it returns \c false. If another thread has
+    locked the mutex, this function will wait until \a timeout expires
+    for the mutex to become available.
+
+    If the lock was obtained, the mutex must be unlocked with unlock()
+    before another thread can successfully lock it.
+
+    Calling this function multiple times on the same mutex from the
+    same thread is allowed.
+
+    \sa lock(), unlock()
+*/
+bool QRecursiveMutex::tryLock(QDeadlineTimer timeout) QT_MUTEX_LOCK_NOEXCEPT
+{
+    unsigned tsanFlags = QtTsan::MutexWriteReentrant | QtTsan::TryLock;
+    QtTsan::mutexPreLock(this, tsanFlags);
+
+    Qt::HANDLE self = QThread::currentThreadId();
+    if (owner.loadRelaxed() == self) {
+        ++count;
+        Q_ASSERT_X(count != 0, "QMutex::lock", "Overflow in recursion counter");
+        QtTsan::mutexPostLock(this, tsanFlags, 0);
+        return true;
+    }
+    bool success = true;
+    if (timeout.isForever()) {
+        mutex.lock();
+    } else {
+        success = mutex.tryLock(timeout);
+    }
+
+    if (success)
+        owner.storeRelaxed(self);
+    else
+        tsanFlags |= QtTsan::TryLockFailed;
+
+    QtTsan::mutexPostLock(this, tsanFlags, 0);
+
+    return success;
+}
+
+/*! \fn bool QRecursiveMutex::try_lock()
+    \since 5.8
+
+    Attempts to lock the mutex. This function returns \c true if the lock
+    was obtained; otherwise it returns \c false.
+
+    This function is provided for compatibility with the Standard Library
+    concept \c Lockable. It is equivalent to tryLock().
+*/
+
+/*! \fn template <class Rep, class Period> bool QRecursiveMutex::try_lock_for(std::chrono::duration<Rep, Period> duration)
+    \since 5.8
+
+    Attempts to lock the mutex. This function returns \c true if the lock
+    was obtained; otherwise it returns \c false. If another thread has
+    locked the mutex, this function will wait for at least \a duration
+    for the mutex to become available.
+
+    Note: Passing a negative duration as the \a duration is equivalent to
+    calling try_lock(). This behavior differs from tryLock().
+
+    If the lock was obtained, the mutex must be unlocked with unlock()
+    before another thread can successfully lock it.
+
+    Calling this function multiple times on the same mutex from the
+    same thread is allowed.
+
+    \sa lock(), unlock()
+*/
+
+/*! \fn template<class Clock, class Duration> bool QRecursiveMutex::try_lock_until(std::chrono::time_point<Clock, Duration> timePoint)
+    \since 5.8
+
+    Attempts to lock the mutex. This function returns \c true if the lock
+    was obtained; otherwise it returns \c false. If another thread has
+    locked the mutex, this function will wait at least until \a timePoint
+    for the mutex to become available.
+
+    Note: Passing a \a timePoint which has already passed is equivalent
+    to calling try_lock(). This behavior differs from tryLock().
+
+    If the lock was obtained, the mutex must be unlocked with unlock()
+    before another thread can successfully lock it.
+
+    Calling this function multiple times on the same mutex from the
+    same thread is allowed.
+
+    \sa lock(), unlock()
+*/
+
+/*!
+    Unlocks the mutex. Attempting to unlock a mutex in a different
+    thread to the one that locked it results in an error. Unlocking a
+    mutex that is not locked results in undefined behavior.
+
+    \sa lock()
+*/
+void QRecursiveMutex::unlock() noexcept
+{
+    Q_ASSERT(owner.loadRelaxed() == QThread::currentThreadId());
+    QtTsan::mutexPreUnlock(this, 0u);
+
+    if (count > 0) {
+        count--;
+    } else {
+        owner.storeRelaxed(nullptr);
+        mutex.unlock();
+    }
+
+    QtTsan::mutexPostUnlock(this, 0u);
+}
+
+
+/*!
+    \class QMutexLocker
+    \inmodule QtCore
+    \brief The QMutexLocker class is a convenience class that simplifies
+    locking and unlocking mutexes.
+
+    \threadsafe
+
+    \ingroup thread
+
+    Locking and unlocking a QMutex or QRecursiveMutex in complex functions and
+    statements or in exception handling code is error-prone and
+    difficult to debug. QMutexLocker can be used in such situations
+    to ensure that the state of the mutex is always well-defined.
+
+    QMutexLocker should be created within a function where a
+    QMutex needs to be locked. The mutex is locked when QMutexLocker
+    is created. You can unlock and relock the mutex with \c unlock()
+    and \c relock(). If locked, the mutex will be unlocked when the
+    QMutexLocker is destroyed.
+
+    For example, this complex function locks a QMutex upon entering
+    the function and unlocks the mutex at all the exit points:
+
+    \snippet code/src_corelib_thread_qmutex.cpp 4
+
+    This example function will get more complicated as it is
+    developed, which increases the likelihood that errors will occur.
+
+    Using QMutexLocker greatly simplifies the code, and makes it more
+    readable:
+
+    \snippet code/src_corelib_thread_qmutex.cpp 5
+
+    Now, the mutex will always be unlocked when the QMutexLocker
+    object is destroyed (when the function returns since \c locker is
+    an auto variable).
+
+    The same principle applies to code that throws and catches
+    exceptions. An exception that is not caught in the function that
+    has locked the mutex has no way of unlocking the mutex before the
+    exception is passed up the stack to the calling function.
+
+    QMutexLocker also provides a \c mutex() member function that returns
+    the mutex on which the QMutexLocker is operating. This is useful
+    for code that needs access to the mutex, such as
+    QWaitCondition::wait(). For example:
+
+    \snippet code/src_corelib_thread_qmutex.cpp 6
+
+    \sa QReadLocker, QWriteLocker, QMutex
+*/
+
+/*!
+    \fn template <typename Mutex> QMutexLocker<Mutex>::QMutexLocker(Mutex *mutex) noexcept
+
+    Constructs a QMutexLocker and locks \a mutex. The mutex will be
+    unlocked when the QMutexLocker is destroyed. If \a mutex is \nullptr,
+    QMutexLocker does nothing.
+
+    \sa QMutex::lock()
+*/
+
+/*!
+    \fn template <typename Mutex> QMutexLocker<Mutex>::QMutexLocker(QMutexLocker &&other) noexcept
+    \since 6.4
+
+    Move-constructs a QMutexLocker from \a other. The mutex and the
+    state of \a other is transferred to the newly constructed instance.
+    After the move, \a other will no longer be managing any mutex.
+
+    \sa QMutex::lock()
+*/
+
+/*!
+    \fn template <typename Mutex> QMutexLocker<Mutex> &QMutexLocker<Mutex>::operator=(QMutexLocker &&other) noexcept
+    \since 6.4
+
+    Move-assigns \a other onto this QMutexLocker. If this QMutexLocker
+    was holding a locked mutex before the assignment, the mutex will be
+    unlocked. The mutex and the state of \a other is then transferred
+    to this QMutexLocker. After the move, \a other will no longer be
+    managing any mutex.
+
+    \sa QMutex::lock()
+*/
+
+/*!
+    \fn template <typename Mutex> void QMutexLocker<Mutex>::swap(QMutexLocker &other) noexcept
+    \since 6.4
+
+    Swaps the mutex and the state of this QMutexLocker with \a other.
+    This operation is very fast and never fails.
+
+    \sa QMutex::lock()
+*/
+
+/*!
+    \fn template <typename Mutex> QMutexLocker<Mutex>::~QMutexLocker() noexcept
+
+    Destroys the QMutexLocker and unlocks the mutex that was locked
+    in the constructor.
+
+    \sa QMutex::unlock()
+*/
+
+/*!
+    \fn template <typename Mutex> bool QMutexLocker<Mutex>::isLocked() const noexcept
+    \since 6.4
+
+    Returns true if this QMutexLocker is currently locking its associated
+    mutex, or false otherwise.
+*/
+
+/*!
+    \fn template <typename Mutex> void QMutexLocker<Mutex>::unlock() noexcept
+
+    Unlocks this mutex locker. You can use \c relock() to lock
+    it again. It does not need to be locked when destroyed.
+
+    \sa relock()
+*/
+
+/*!
+    \fn template <typename Mutex> void QMutexLocker<Mutex>::relock() noexcept
+
+    Relocks an unlocked mutex locker.
+
+    \sa unlock()
+*/
+
+/*!
+    \fn template <typename Mutex> QMutex *QMutexLocker<Mutex>::mutex() const
+
+    Returns the mutex on which the QMutexLocker is operating.
+
+*/
+
+/*
+  For a rough introduction on how this works, refer to
+  http://woboq.com/blog/internals-of-qmutex-in-qt5.html
+  which explains a slightly simplified version of it.
+  The differences are that here we try to work with timeout (requires the
+  possiblyUnlocked flag) and that we only wake one thread when unlocking
+  (requires maintaining the waiters count)
+  We also support recursive mutexes which always have a valid d_ptr.
+
+  The waiters flag represents the number of threads that are waiting or about
+  to wait on the mutex. There are two tricks to keep in mind:
+  We don't want to increment waiters after we checked no threads are waiting
+  (waiters == 0). That's why we atomically set the BigNumber flag on waiters when
+  we check waiters. Similarly, if waiters is decremented right after we checked,
+  the mutex would be unlocked (d->wakeUp() has (or will) be called), but there is
+  no thread waiting. This is only happening if there was a timeout in tryLock at the
+  same time as the mutex is unlocked. So when there was a timeout, we set the
+  possiblyUnlocked flag.
+*/
+
+/*
+ * QBasicMutex implementation with futexes (Linux, Windows 10)
+ *
+ * QBasicMutex contains one pointer value, which can contain one of four
+ * different values:
+ *    0x0       unlocked
+ *    0x1       locked, no waiters
+ *    0x3       locked, at least one waiter
+ *
+ * LOCKING:
+ *
+ * A starts in the 0x0 state, indicating that it's unlocked. When the first
+ * thread attempts to lock it, it will perform a testAndSetAcquire
+ * from 0x0 to 0x1. If that succeeds, the caller concludes that it
+ * successfully locked the mutex. That happens in fastTryLock().
+ *
+ * If that testAndSetAcquire fails, QBasicMutex::lockInternal is called.
+ *
+ * lockInternal will examine the value of the pointer. Otherwise, it will use
+ * futexes to sleep and wait for another thread to unlock. To do that, it needs
+ * to set a pointer value of 0x3, which indicates that thread is waiting. It
+ * does that by a simple fetchAndStoreAcquire operation.
+ *
+ * If the pointer value was 0x0, it means we succeeded in acquiring the mutex.
+ * For other values, it will then call FUTEX_WAIT and with an expected value of
+ * 0x3.
+ *
+ * If the pointer value changed before futex(2) managed to sleep, it will
+ * return -1 / EWOULDBLOCK, in which case we have to start over. And even if we
+ * are woken up directly by a FUTEX_WAKE, we need to acquire the mutex, so we
+ * start over again.
+ *
+ * UNLOCKING:
+ *
+ * To unlock, we need to set a value of 0x0 to indicate it's unlocked. The
+ * first attempt is a testAndSetRelease operation from 0x1 to 0x0. If that
+ * succeeds, we're done.
+ *
+ * If it fails, unlockInternal() is called. The only possibility is that the
+ * mutex value was 0x3, which indicates some other thread is waiting or was
+ * waiting in the past. We then set the mutex to 0x0 and perform a FUTEX_WAKE.
+ */
+
+/*!
+    \internal helper for lock()
+ */
+void QBasicMutex::lockInternal() QT_MUTEX_LOCK_NOEXCEPT
+{
+    if (futexAvailable()) {
+        // note we must set to dummyFutexValue because there could be other threads
+        // also waiting
+        while (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) != nullptr) {
+            // successfully set the waiting bit, now sleep
+            futexWait(d_ptr, dummyFutexValue());
+
+            // we got woken up, so try to acquire the mutex
+        }
+        Q_ASSERT(d_ptr.loadRelaxed());
+    } else {
+        lockInternal(-1);
+    }
+}
+
+/*!
+    \internal helper for lock(int)
+ */
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+bool QBasicMutex::lockInternal(int timeout) QT_MUTEX_LOCK_NOEXCEPT
+{
+    if (timeout == 0)
+        return false;
+
+    return lockInternal(QDeadlineTimer(timeout));
+}
+#endif
+
+/*!
+    \internal helper for tryLock(QDeadlineTimer)
+ */
+bool QBasicMutex::lockInternal(QDeadlineTimer deadlineTimer) QT_MUTEX_LOCK_NOEXCEPT
+{
+    if (deadlineTimer.hasExpired())
+        return false;
+
+    if (futexAvailable()) {
+        if (Q_UNLIKELY(deadlineTimer.isForever())) {
+            lockInternal();
+            return true;
+        }
+
+        // The mutex is already locked, set a bit indicating we're waiting.
+        // Note we must set to dummyFutexValue because there could be other threads
+        // also waiting.
+        if (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) == nullptr)
+            return true;
+
+        for (;;) {
+            if (!futexWait(d_ptr, dummyFutexValue(), deadlineTimer))
+                return false;
+
+            // We got woken up, so must try to acquire the mutex. We must set
+            // to dummyFutexValue() again because there could be other threads
+            // waiting.
+            if (d_ptr.fetchAndStoreAcquire(dummyFutexValue()) == nullptr)
+                return true;
+
+            if (deadlineTimer.hasExpired())
+                return false;
+        }
+    }
+
+#if !defined(QT_ALWAYS_USE_FUTEX)
+    while (!fastTryLock()) {
+        QMutexPrivate *copy = d_ptr.loadAcquire();
+        if (!copy) // if d is 0, the mutex is unlocked
+            continue;
+
+        if (copy == dummyLocked()) {
+            if (deadlineTimer.hasExpired())
+                return false;
+            // The mutex is locked but does not have a QMutexPrivate yet.
+            // we need to allocate a QMutexPrivate
+            QMutexPrivate *newD = QMutexPrivate::allocate();
+            if (!d_ptr.testAndSetOrdered(dummyLocked(), newD)) {
+                //Either the mutex is already unlocked, or another thread already set it.
+                newD->deref();
+                continue;
+            }
+            copy = newD;
+            //the d->refCount is already 1 the deref will occurs when we unlock
+        }
+
+        QMutexPrivate *d = static_cast<QMutexPrivate *>(copy);
+        if (deadlineTimer.hasExpired() && !d->possiblyUnlocked.loadRelaxed())
+            return false;
+
+        // At this point we have a pointer to a QMutexPrivate. But the other thread
+        // may unlock the mutex at any moment and release the QMutexPrivate to the pool.
+        // We will try to reference it to avoid unlock to release it to the pool to make
+        // sure it won't be released. But if the refcount is already 0 it has been released.
+        if (!d->ref())
+            continue; //that QMutexPrivate was already released
+
+        // We now hold a reference to the QMutexPrivate. It won't be released and re-used.
+        // But it is still possible that it was already re-used by another QMutex right before
+        // we did the ref(). So check if we still hold a pointer to the right mutex.
+        if (d != d_ptr.loadAcquire()) {
+            //Either the mutex is already unlocked, or relocked with another mutex
+            d->deref();
+            continue;
+        }
+
+        // In this part, we will try to increment the waiters count.
+        // We just need to take care of the case in which the old_waiters
+        // is set to the BigNumber magic value set in unlockInternal()
+        int old_waiters;
+        do {
+            old_waiters = d->waiters.loadAcquire();
+            if (old_waiters == -QMutexPrivate::BigNumber) {
+                // we are unlocking, and the thread that unlocks is about to change d to 0
+                // we try to acquire the mutex by changing to dummyLocked()
+                if (d_ptr.testAndSetAcquire(d, dummyLocked())) {
+                    // Mutex acquired
+                    d->deref();
+                    return true;
+                } else {
+                    Q_ASSERT(d != d_ptr.loadRelaxed()); //else testAndSetAcquire should have succeeded
+                    // Mutex is likely to bo 0, we should continue the outer-loop,
+                    //  set old_waiters to the magic value of BigNumber
+                    old_waiters = QMutexPrivate::BigNumber;
+                    break;
+                }
+            }
+        } while (!d->waiters.testAndSetRelaxed(old_waiters, old_waiters + 1));
+
+        if (d != d_ptr.loadAcquire()) {
+            // The mutex was unlocked before we incremented waiters.
+            if (old_waiters != QMutexPrivate::BigNumber) {
+                //we did not break the previous loop
+                Q_ASSERT(d->waiters.loadRelaxed() >= 1);
+                d->waiters.deref();
+            }
+            d->deref();
+            continue;
+        }
+
+        if (d->wait(deadlineTimer)) {
+            // reset the possiblyUnlocked flag if needed (and deref its corresponding reference)
+            if (d->possiblyUnlocked.loadRelaxed() && d->possiblyUnlocked.testAndSetRelaxed(true, false))
+                d->deref();
+            d->derefWaiters(1);
+            //we got the lock. (do not deref)
+            Q_ASSERT(d == d_ptr.loadRelaxed());
+            return true;
+        } else {
+            // timed out
+            d->derefWaiters(1);
+            //There may be a race in which the mutex is unlocked right after we timed out,
+            // and before we deref the waiters, so maybe the mutex is actually unlocked.
+            // Set the possiblyUnlocked flag to indicate this possibility.
+            if (!d->possiblyUnlocked.testAndSetRelaxed(false, true)) {
+                // We keep a reference when possiblyUnlocked is true.
+                // but if possiblyUnlocked was already true, we don't need to keep the reference.
+                d->deref();
+            }
+            return false;
+        }
+    }
+    Q_ASSERT(d_ptr.loadRelaxed() != 0);
+    return true;
+#else
+    Q_UNREACHABLE();
+#endif
+}
+
+/*!
+    \internal
+*/
+void QBasicMutex::unlockInternal() noexcept
+{
+    QMutexPrivate *copy = d_ptr.loadAcquire();
+    Q_ASSERT(copy); //we must be locked
+    Q_ASSERT(copy != dummyLocked()); // testAndSetRelease(dummyLocked(), 0) failed
+
+    if (futexAvailable()) {
+        d_ptr.storeRelease(nullptr);
+        return futexWakeOne(d_ptr);
+    }
+
+#if !defined(QT_ALWAYS_USE_FUTEX)
+    QMutexPrivate *d = reinterpret_cast<QMutexPrivate *>(copy);
+
+    // If no one is waiting for the lock anymore, we should reset d to 0x0.
+    // Using fetchAndAdd, we atomically check that waiters was equal to 0, and add a flag
+    // to the waiters variable (BigNumber). That way, we avoid the race in which waiters is
+    // incremented right after we checked, because we won't increment waiters if is
+    // equal to -BigNumber
+    if (d->waiters.fetchAndAddRelease(-QMutexPrivate::BigNumber) == 0) {
+        //there is no one waiting on this mutex anymore, set the mutex as unlocked (d = 0)
+        if (d_ptr.testAndSetRelease(d, 0)) {
+            // reset the possiblyUnlocked flag if needed (and deref its corresponding reference)
+            if (d->possiblyUnlocked.loadRelaxed() && d->possiblyUnlocked.testAndSetRelaxed(true, false))
+                d->deref();
+        }
+        d->derefWaiters(0);
+    } else {
+        d->derefWaiters(0);
+        //there are thread waiting, transfer the lock.
+        d->wakeUp();
+    }
+    d->deref();
+#else
+    Q_UNUSED(copy);
+#endif
+}
+
+#if !defined(QT_ALWAYS_USE_FUTEX)
+//The freelist management
+namespace {
+struct FreeListConstants : QFreeListDefaultConstants {
+    enum { BlockCount = 4, MaxIndex=0xffff };
+    static const int Sizes[BlockCount];
+};
+Q_CONSTINIT const int FreeListConstants::Sizes[FreeListConstants::BlockCount] = {
+    16,
+    128,
+    1024,
+    FreeListConstants::MaxIndex - (16 + 128 + 1024)
+};
+
+typedef QFreeList<QMutexPrivate, FreeListConstants> FreeList;
+// We cannot use Q_GLOBAL_STATIC because it uses QMutex
+Q_CONSTINIT static FreeList freeList_;
+FreeList *freelist()
+{
+    return &freeList_;
+}
+}
+
+QMutexPrivate *QMutexPrivate::allocate()
+{
+    int i = freelist()->next();
+    QMutexPrivate *d = &(*freelist())[i];
+    d->id = i;
+    Q_ASSERT(d->refCount.loadRelaxed() == 0);
+    Q_ASSERT(!d->possiblyUnlocked.loadRelaxed());
+    Q_ASSERT(d->waiters.loadRelaxed() == 0);
+    d->refCount.storeRelaxed(1);
+    return d;
+}
+
+void QMutexPrivate::release()
+{
+    Q_ASSERT(refCount.loadRelaxed() == 0);
+    Q_ASSERT(!possiblyUnlocked.loadRelaxed());
+    Q_ASSERT(waiters.loadRelaxed() == 0);
+    freelist()->release(id);
+}
+
+// atomically subtract "value" to the waiters, and remove the QMutexPrivate::BigNumber flag
+void QMutexPrivate::derefWaiters(int value) noexcept
+{
+    int old_waiters;
+    int new_waiters;
+    do {
+        old_waiters = waiters.loadRelaxed();
+        new_waiters = old_waiters;
+        if (new_waiters < 0) {
+            new_waiters += QMutexPrivate::BigNumber;
+        }
+        new_waiters -= value;
+    } while (!waiters.testAndSetRelaxed(old_waiters, new_waiters));
+}
+#endif
+
+QT_END_NAMESPACE
+
+#if defined(QT_ALWAYS_USE_FUTEX)
+// nothing
+#elif defined(Q_OS_DARWIN)
+#  include "qmutex_mac.cpp"
+#else
+#  include "qmutex_unix.cpp"
+#endif
diff --git a/qtbase/src/corelib/thread/qmutex_p.h b/qtbase/src/corelib/thread/qmutex_p.h
index aabb66fa55..a16d508215 100644
--- a/qtbase/src/corelib/thread/qmutex_p.h
+++ b/qtbase/src/corelib/thread/qmutex_p.h
@@ -86,6 +86,8 @@ public:
     semaphore_t mach_semaphore;
 #elif defined(Q_OS_UNIX)
     sem_t semaphore;
+#elif defined(Q_OS_WIN)
+    Qt::HANDLE event;
 #endif
 };
 
diff --git a/qtbase/src/corelib/thread/qmutex_win.cpp b/qtbase/src/corelib/thread/qmutex_win.cpp
new file mode 100644
index 0000000000..c685ddd2bc
--- /dev/null
+++ b/qtbase/src/corelib/thread/qmutex_win.cpp
@@ -0,0 +1,30 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qmutex.h"
+#include <qatomic.h>
+#include "qmutex_p.h"
+#include <qt_windows.h>
+
+QT_BEGIN_NAMESPACE
+
+QMutexPrivate::QMutexPrivate()
+{
+    event = CreateEvent(0, FALSE, FALSE, 0);
+    
+    if (!event)
+        qWarning("QMutexPrivate::QMutexPrivate: Cannot create event");
+}
+
+QMutexPrivate::~QMutexPrivate()
+{ CloseHandle(event); }
+
+bool QMutexPrivate::wait(QDeadlineTimer timeout)
+{
+    return (WaitForSingleObjectEx(event, timeout.isForever() ? INFINITE : timeout.remainingTime(), FALSE) == WAIT_OBJECT_0);
+}
+
+void QMutexPrivate::wakeUp() noexcept
+{ SetEvent(event); }
+
+QT_END_NAMESPACE
diff --git a/qtbase/src/gui/rhi/qrhid3d11.cpp b/qtbase/src/gui/rhi/qrhid3d11.cpp
index 92e4cacc16..dac5655161 100644
--- a/qtbase/src/gui/rhi/qrhid3d11.cpp
+++ b/qtbase/src/gui/rhi/qrhid3d11.cpp
@@ -10,6 +10,8 @@
 #include <QtCore/private/qsystemerror_p.h>
 #include "qrhid3dhelpers_p.h"
 
+#include <VersionHelpers.h>
+
 QT_BEGIN_NAMESPACE
 
 using namespace Qt::StringLiterals;
@@ -155,13 +157,22 @@ inline Int aligned(Int v, Int byteAlign)
 
 static IDXGIFactory1 *createDXGIFactory2()
 {
+    typedef HRESULT(WINAPI* CreateDXGIFactory2Func) (UINT flags, REFIID riid, void** factory);
+    static CreateDXGIFactory2Func myCreateDXGIFactory2 =
+        (CreateDXGIFactory2Func)::GetProcAddress(::GetModuleHandle(L"dxgi"), "CreateDXGIFactory2");
+
     IDXGIFactory1 *result = nullptr;
-    const HRESULT hr = CreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&result));
-    if (FAILED(hr)) {
-        qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s",
-            qPrintable(QSystemError::windowsComString(hr)));
-        result = nullptr;
+
+    if (myCreateDXGIFactory2)
+    {
+        const HRESULT hr = myCreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&result));
+        if (FAILED(hr)) {
+            qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s",
+                qPrintable(QSystemError::windowsComString(hr)));
+            result = nullptr;
+        }
     }
+    
     return result;
 }
 
@@ -5029,6 +5040,14 @@ static const DXGI_FORMAT DEFAULT_SRGB_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
 
 bool QD3D11SwapChain::createOrResize()
 {
+    if (IsWindows10OrGreater())
+    {
+        // continue
+    }
+    else
+    {
+        return createOrResizeWin7();
+    }
     // Can be called multiple times due to window resizes - that is not the
     // same as a simple destroy+create (as with other resources). Just need to
     // resize the buffers then.
@@ -5327,4 +5346,9 @@ bool QD3D11SwapChain::createOrResize()
     return true;
 }
 
+bool QD3D11SwapChain::createOrResizeWin7()
+{
+    return false; // not implemented yet ;(
+}
+
 QT_END_NAMESPACE
diff --git a/qtbase/src/gui/rhi/qrhid3d11.cpp.orig b/qtbase/src/gui/rhi/qrhid3d11.cpp.orig
new file mode 100644
index 0000000000..92e4cacc16
--- /dev/null
+++ b/qtbase/src/gui/rhi/qrhid3d11.cpp.orig
@@ -0,0 +1,5330 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qrhid3d11_p.h"
+#include "qshader.h"
+#include "vs_test_p.h"
+#include <QWindow>
+#include <qmath.h>
+#include <QtCore/qcryptographichash.h>
+#include <QtCore/private/qsystemerror_p.h>
+#include "qrhid3dhelpers_p.h"
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+/*
+  Direct3D 11 backend. Provides a double-buffered flip model swapchain.
+  Textures and "static" buffers are USAGE_DEFAULT, leaving it to
+  UpdateSubResource to upload the data in any way it sees fit. "Dynamic"
+  buffers are USAGE_DYNAMIC and updating is done by mapping with WRITE_DISCARD.
+  (so here QRhiBuffer keeps a copy of the buffer contents and all of it is
+  memcpy'd every time, leaving the rest (juggling with the memory area Map
+  returns) to the driver).
+*/
+
+/*!
+    \class QRhiD3D11InitParams
+    \inmodule QtGui
+    \since 6.6
+    \brief Direct3D 11 specific initialization parameters.
+
+    \note This is a RHI API with limited compatibility guarantees, see \l QRhi
+    for details.
+
+    A D3D11-based QRhi needs no special parameters for initialization. If
+    desired, enableDebugLayer can be set to \c true to enable the Direct3D
+    debug layer. This can be useful during development, but should be avoided
+    in production builds.
+
+    \badcode
+        QRhiD3D11InitParams params;
+        params.enableDebugLayer = true;
+        rhi = QRhi::create(QRhi::D3D11, &params);
+    \endcode
+
+    \note QRhiSwapChain should only be used in combination with QWindow
+    instances that have their surface type set to QSurface::Direct3DSurface.
+
+    \section2 Working with existing Direct3D 11 devices
+
+    When interoperating with another graphics engine, it may be necessary to
+    get a QRhi instance that uses the same Direct3D device. This can be
+    achieved by passing a pointer to a QRhiD3D11NativeHandles to
+    QRhi::create(). When the device is set to a non-null value, the device
+    context must be specified as well. QRhi does not take ownership of any of
+    the external objects.
+
+    Sometimes, for example when using QRhi in combination with OpenXR, one will
+    want to specify which adapter to use, and optionally, which feature level
+    to request on the device, while leaving the device creation to QRhi. This
+    is achieved by leaving the device and context pointers set to null, while
+    specifying the adapter LUID and feature level.
+
+    \note QRhi works with immediate contexts only. Deferred contexts are not
+    used in any way.
+
+    \note Regardless of using an imported or a QRhi-created device context, the
+    \c ID3D11DeviceContext1 interface (Direct3D 11.1) must be supported.
+    Initialization will fail otherwise.
+ */
+
+/*!
+    \variable QRhiD3D11InitParams::enableDebugLayer
+
+    When set to true, a debug device is created, assuming the debug layer is
+    available. The default value is false.
+*/
+
+/*!
+    \class QRhiD3D11NativeHandles
+    \inmodule QtGui
+    \since 6.6
+    \brief Holds the D3D device and device context used by the QRhi.
+
+    \note The class uses \c{void *} as the type since including the COM-based
+    \c{d3d11.h} headers is not acceptable here. The actual types are
+    \c{ID3D11Device *} and \c{ID3D11DeviceContext *}.
+
+    \note This is a RHI API with limited compatibility guarantees, see \l QRhi
+    for details.
+ */
+
+/*!
+    \variable QRhiD3D11NativeHandles::dev
+*/
+
+/*!
+    \variable QRhiD3D11NativeHandles::context
+*/
+
+/*!
+    \variable QRhiD3D11NativeHandles::featureLevel
+*/
+
+/*!
+    \variable QRhiD3D11NativeHandles::adapterLuidLow
+*/
+
+/*!
+    \variable QRhiD3D11NativeHandles::adapterLuidHigh
+*/
+
+// help mingw with its ancient sdk headers
+#ifndef DXGI_ADAPTER_FLAG_SOFTWARE
+#define DXGI_ADAPTER_FLAG_SOFTWARE 2
+#endif
+
+#ifndef D3D11_1_UAV_SLOT_COUNT
+#define D3D11_1_UAV_SLOT_COUNT 64
+#endif
+
+#ifndef D3D11_VS_INPUT_REGISTER_COUNT
+#define D3D11_VS_INPUT_REGISTER_COUNT 32
+#endif
+
+QRhiD3D11::QRhiD3D11(QRhiD3D11InitParams *params, QRhiD3D11NativeHandles *importParams)
+    : ofr(this)
+{
+    debugLayer = params->enableDebugLayer;
+
+    if (importParams) {
+        if (importParams->dev && importParams->context) {
+            dev = reinterpret_cast<ID3D11Device *>(importParams->dev);
+            ID3D11DeviceContext *ctx = reinterpret_cast<ID3D11DeviceContext *>(importParams->context);
+            if (SUCCEEDED(ctx->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast<void **>(&context)))) {
+                // get rid of the ref added by QueryInterface
+                ctx->Release();
+                importedDeviceAndContext = true;
+            } else {
+                qWarning("ID3D11DeviceContext1 not supported by context, cannot import");
+            }
+        }
+        featureLevel = D3D_FEATURE_LEVEL(importParams->featureLevel);
+        adapterLuid.LowPart = importParams->adapterLuidLow;
+        adapterLuid.HighPart = importParams->adapterLuidHigh;
+    }
+}
+
+template <class Int>
+inline Int aligned(Int v, Int byteAlign)
+{
+    return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+static IDXGIFactory1 *createDXGIFactory2()
+{
+    IDXGIFactory1 *result = nullptr;
+    const HRESULT hr = CreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&result));
+    if (FAILED(hr)) {
+        qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        result = nullptr;
+    }
+    return result;
+}
+
+bool QRhiD3D11::create(QRhi::Flags flags)
+{
+    rhiFlags = flags;
+
+    uint devFlags = 0;
+    if (debugLayer)
+        devFlags |= D3D11_CREATE_DEVICE_DEBUG;
+
+    dxgiFactory = createDXGIFactory2();
+    if (!dxgiFactory)
+        return false;
+
+    // For a FLIP_* swapchain Present(0, 0) is not necessarily
+    // sufficient to get non-blocking behavior, try using ALLOW_TEARING
+    // when available.
+    supportsAllowTearing = false;
+    IDXGIFactory5 *factory5 = nullptr;
+    if (SUCCEEDED(dxgiFactory->QueryInterface(__uuidof(IDXGIFactory5), reinterpret_cast<void **>(&factory5)))) {
+        BOOL allowTearing = false;
+        if (SUCCEEDED(factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing))))
+            supportsAllowTearing = allowTearing;
+        factory5->Release();
+    }
+
+    if (qEnvironmentVariableIntValue("QT_D3D_FLIP_DISCARD"))
+        qWarning("The default swap effect is FLIP_DISCARD, QT_D3D_FLIP_DISCARD is now ignored");
+
+    // Support for flip model swapchains is required now (since we are
+    // targeting Windows 10+), but the option for using the old model is still
+    // 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",
+            supportsAllowTearing ? "true" : "false",
+            useLegacySwapchainModel ? "true" : "false");
+
+    if (!importedDeviceAndContext) {
+        IDXGIAdapter1 *adapter;
+        int requestedAdapterIndex = -1;
+        if (qEnvironmentVariableIsSet("QT_D3D_ADAPTER_INDEX"))
+            requestedAdapterIndex = qEnvironmentVariableIntValue("QT_D3D_ADAPTER_INDEX");
+
+        // The importParams may specify an adapter by the luid, take that into account.
+        if (requestedAdapterIndex < 0 && (adapterLuid.LowPart || adapterLuid.HighPart)) {
+            for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
+                DXGI_ADAPTER_DESC1 desc;
+                adapter->GetDesc1(&desc);
+                adapter->Release();
+                if (desc.AdapterLuid.LowPart == adapterLuid.LowPart
+                        && desc.AdapterLuid.HighPart == adapterLuid.HighPart)
+                {
+                    requestedAdapterIndex = adapterIndex;
+                    break;
+                }
+            }
+        }
+
+        if (requestedAdapterIndex < 0 && flags.testFlag(QRhi::PreferSoftwareRenderer)) {
+            for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
+                DXGI_ADAPTER_DESC1 desc;
+                adapter->GetDesc1(&desc);
+                adapter->Release();
+                if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) {
+                    requestedAdapterIndex = adapterIndex;
+                    break;
+                }
+            }
+        }
+
+        activeAdapter = nullptr;
+        for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
+            DXGI_ADAPTER_DESC1 desc;
+            adapter->GetDesc1(&desc);
+            const QString name = QString::fromUtf16(reinterpret_cast<char16_t *>(desc.Description));
+            qCDebug(QRHI_LOG_INFO, "Adapter %d: '%s' (vendor 0x%X device 0x%X flags 0x%X)",
+                    adapterIndex,
+                    qPrintable(name),
+                    desc.VendorId,
+                    desc.DeviceId,
+                    desc.Flags);
+            if (!activeAdapter && (requestedAdapterIndex < 0 || requestedAdapterIndex == adapterIndex)) {
+                activeAdapter = adapter;
+                adapterLuid = desc.AdapterLuid;
+                QRhiD3D::fillDriverInfo(&driverInfoStruct, desc);
+                qCDebug(QRHI_LOG_INFO, "  using this adapter");
+            } else {
+                adapter->Release();
+            }
+        }
+        if (!activeAdapter) {
+            qWarning("No adapter");
+            return false;
+        }
+
+        // Normally we won't specify a requested feature level list,
+        // except when a level was specified in importParams.
+        QVarLengthArray<D3D_FEATURE_LEVEL, 4> requestedFeatureLevels;
+        bool requestFeatureLevels = false;
+        if (featureLevel) {
+            requestFeatureLevels = true;
+            requestedFeatureLevels.append(featureLevel);
+        }
+
+        ID3D11DeviceContext *ctx = nullptr;
+        HRESULT hr = D3D11CreateDevice(activeAdapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr, devFlags,
+                                       requestFeatureLevels ? requestedFeatureLevels.constData() : nullptr,
+                                       requestFeatureLevels ? requestedFeatureLevels.count() : 0,
+                                       D3D11_SDK_VERSION,
+                                       &dev, &featureLevel, &ctx);
+        // We cannot assume that D3D11_CREATE_DEVICE_DEBUG is always available. Retry without it, if needed.
+        if (hr == DXGI_ERROR_SDK_COMPONENT_MISSING && debugLayer) {
+            qCDebug(QRHI_LOG_INFO, "Debug layer was requested but is not available. "
+                                   "Attempting to create D3D11 device without it.");
+            devFlags &= ~D3D11_CREATE_DEVICE_DEBUG;
+            hr = D3D11CreateDevice(activeAdapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr, devFlags,
+                                   requestFeatureLevels ? requestedFeatureLevels.constData() : nullptr,
+                                   requestFeatureLevels ? requestedFeatureLevels.count() : 0,
+                                   D3D11_SDK_VERSION,
+                                   &dev, &featureLevel, &ctx);
+        }
+        if (FAILED(hr)) {
+            qWarning("Failed to create D3D11 device and context: %s",
+                qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+
+        const bool supports11_1 = SUCCEEDED(ctx->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast<void **>(&context)));
+        ctx->Release();
+        if (!supports11_1) {
+            qWarning("ID3D11DeviceContext1 not supported");
+            return false;
+        }
+
+        // Test if creating a Shader Model 5.0 vertex shader works; we want to
+        // fail already in create() if that's not the case.
+        ID3D11VertexShader *testShader = nullptr;
+        if (SUCCEEDED(dev->CreateVertexShader(g_testVertexShader, sizeof(g_testVertexShader), nullptr, &testShader))) {
+            testShader->Release();
+        } else {
+            static const char *msg = "D3D11 smoke test: Failed to create vertex shader";
+            if (flags.testFlag(QRhi::SuppressSmokeTestWarnings))
+                qCDebug(QRHI_LOG_INFO, "%s", msg);
+            else
+                qWarning("%s", msg);
+            return false;
+        }
+
+        D3D11_FEATURE_DATA_D3D11_OPTIONS features = {};
+        if (SUCCEEDED(dev->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, &features, sizeof(features)))) {
+            // The D3D _runtime_ may be 11.1, but the underlying _driver_ may
+            // still not support this D3D_FEATURE_LEVEL_11_1 feature. (e.g.
+            // because it only does 11_0)
+            if (!features.ConstantBufferOffsetting) {
+                static const char *msg = "D3D11 smoke test: Constant buffer offsetting is not supported by the driver";
+                if (flags.testFlag(QRhi::SuppressSmokeTestWarnings))
+                    qCDebug(QRHI_LOG_INFO, "%s", msg);
+                else
+                    qWarning("%s", msg);
+                return false;
+            }
+        } else {
+            static const char *msg = "D3D11 smoke test: Failed to query D3D11_FEATURE_D3D11_OPTIONS";
+            if (flags.testFlag(QRhi::SuppressSmokeTestWarnings))
+                qCDebug(QRHI_LOG_INFO, "%s", msg);
+            else
+                qWarning("%s", msg);
+            return false;
+        }
+    } else {
+        Q_ASSERT(dev && context);
+        featureLevel = dev->GetFeatureLevel();
+        IDXGIDevice *dxgiDev = nullptr;
+        if (SUCCEEDED(dev->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void **>(&dxgiDev)))) {
+            IDXGIAdapter *adapter = nullptr;
+            if (SUCCEEDED(dxgiDev->GetAdapter(&adapter))) {
+                IDXGIAdapter1 *adapter1 = nullptr;
+                if (SUCCEEDED(adapter->QueryInterface(__uuidof(IDXGIAdapter1), reinterpret_cast<void **>(&adapter1)))) {
+                    DXGI_ADAPTER_DESC1 desc;
+                    adapter1->GetDesc1(&desc);
+                    adapterLuid = desc.AdapterLuid;
+                    QRhiD3D::fillDriverInfo(&driverInfoStruct, desc);
+                    adapter1->Release();
+                }
+                adapter->Release();
+            }
+            dxgiDev->Release();
+        }
+        qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev);
+    }
+
+    if (FAILED(context->QueryInterface(__uuidof(ID3DUserDefinedAnnotation), reinterpret_cast<void **>(&annotations))))
+        annotations = nullptr;
+
+    deviceLost = false;
+
+    nativeHandlesStruct.dev = dev;
+    nativeHandlesStruct.context = context;
+    nativeHandlesStruct.featureLevel = featureLevel;
+    nativeHandlesStruct.adapterLuidLow = adapterLuid.LowPart;
+    nativeHandlesStruct.adapterLuidHigh = adapterLuid.HighPart;
+
+    return true;
+}
+
+void QRhiD3D11::clearShaderCache()
+{
+    for (Shader &s : m_shaderCache)
+        s.s->Release();
+
+    m_shaderCache.clear();
+}
+
+void QRhiD3D11::destroy()
+{
+    finishActiveReadbacks();
+
+    clearShaderCache();
+
+    if (ofr.tsDisjointQuery) {
+        ofr.tsDisjointQuery->Release();
+        ofr.tsDisjointQuery = nullptr;
+    }
+    for (int i = 0; i < 2; ++i) {
+        if (ofr.tsQueries[i]) {
+            ofr.tsQueries[i]->Release();
+            ofr.tsQueries[i] = nullptr;
+        }
+    }
+
+    if (annotations) {
+        annotations->Release();
+        annotations = nullptr;
+    }
+
+    if (!importedDeviceAndContext) {
+        if (context) {
+            context->Release();
+            context = nullptr;
+        }
+        if (dev) {
+            dev->Release();
+            dev = nullptr;
+        }
+    }
+
+    if (dcompDevice) {
+        dcompDevice->Release();
+        dcompDevice = nullptr;
+    }
+
+    if (activeAdapter) {
+        activeAdapter->Release();
+        activeAdapter = nullptr;
+    }
+
+    if (dxgiFactory) {
+        dxgiFactory->Release();
+        dxgiFactory = nullptr;
+    }
+}
+
+void QRhiD3D11::reportLiveObjects(ID3D11Device *device)
+{
+    // this works only when params.enableDebugLayer was true
+    ID3D11Debug *debug;
+    if (SUCCEEDED(device->QueryInterface(__uuidof(ID3D11Debug), reinterpret_cast<void **>(&debug)))) {
+        debug->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
+        debug->Release();
+    }
+}
+
+QList<int> QRhiD3D11::supportedSampleCounts() const
+{
+    return { 1, 2, 4, 8 };
+}
+
+DXGI_SAMPLE_DESC QRhiD3D11::effectiveSampleDesc(int sampleCount) const
+{
+    DXGI_SAMPLE_DESC desc;
+    desc.Count = 1;
+    desc.Quality = 0;
+
+    const int s = effectiveSampleCount(sampleCount);
+
+    desc.Count = UINT(s);
+    if (s > 1)
+        desc.Quality = UINT(D3D11_STANDARD_MULTISAMPLE_PATTERN);
+    else
+        desc.Quality = 0;
+
+    return desc;
+}
+
+QRhiSwapChain *QRhiD3D11::createSwapChain()
+{
+    return new QD3D11SwapChain(this);
+}
+
+QRhiBuffer *QRhiD3D11::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
+{
+    return new QD3D11Buffer(this, type, usage, size);
+}
+
+int QRhiD3D11::ubufAlignment() const
+{
+    return 256;
+}
+
+bool QRhiD3D11::isYUpInFramebuffer() const
+{
+    return false;
+}
+
+bool QRhiD3D11::isYUpInNDC() const
+{
+    return true;
+}
+
+bool QRhiD3D11::isClipDepthZeroToOne() const
+{
+    return true;
+}
+
+QMatrix4x4 QRhiD3D11::clipSpaceCorrMatrix() const
+{
+    // Like with Vulkan, but Y is already good.
+
+    static QMatrix4x4 m;
+    if (m.isIdentity()) {
+        // NB the ctor takes row-major
+        m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
+                       0.0f, 1.0f, 0.0f, 0.0f,
+                       0.0f, 0.0f, 0.5f, 0.5f,
+                       0.0f, 0.0f, 0.0f, 1.0f);
+    }
+    return m;
+}
+
+bool QRhiD3D11::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+    Q_UNUSED(flags);
+
+    if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ASTC_12x12)
+        return false;
+
+    return true;
+}
+
+bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
+{
+    switch (feature) {
+    case QRhi::MultisampleTexture:
+        return true;
+    case QRhi::MultisampleRenderBuffer:
+        return true;
+    case QRhi::DebugMarkers:
+        return annotations != nullptr;
+    case QRhi::Timestamps:
+        return true;
+    case QRhi::Instancing:
+        return true;
+    case QRhi::CustomInstanceStepRate:
+        return true;
+    case QRhi::PrimitiveRestart:
+        return true;
+    case QRhi::NonDynamicUniformBuffers:
+        return false; // because UpdateSubresource cannot deal with this
+    case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
+        return true;
+    case QRhi::NPOTTextureRepeat:
+        return true;
+    case QRhi::RedOrAlpha8IsRed:
+        return true;
+    case QRhi::ElementIndexUint:
+        return true;
+    case QRhi::Compute:
+        return true;
+    case QRhi::WideLines:
+        return false;
+    case QRhi::VertexShaderPointSize:
+        return false;
+    case QRhi::BaseVertex:
+        return true;
+    case QRhi::BaseInstance:
+        return true;
+    case QRhi::TriangleFanTopology:
+        return false;
+    case QRhi::ReadBackNonUniformBuffer:
+        return true;
+    case QRhi::ReadBackNonBaseMipLevel:
+        return true;
+    case QRhi::TexelFetch:
+        return true;
+    case QRhi::RenderToNonBaseMipLevel:
+        return true;
+    case QRhi::IntAttributes:
+        return true;
+    case QRhi::ScreenSpaceDerivatives:
+        return true;
+    case QRhi::ReadBackAnyTextureFormat:
+        return true;
+    case QRhi::PipelineCacheDataLoadSave:
+        return true;
+    case QRhi::ImageDataStride:
+        return true;
+    case QRhi::RenderBufferImport:
+        return false;
+    case QRhi::ThreeDimensionalTextures:
+        return true;
+    case QRhi::RenderTo3DTextureSlice:
+        return true;
+    case QRhi::TextureArrays:
+        return true;
+    case QRhi::Tessellation:
+        return true;
+    case QRhi::GeometryShader:
+        return true;
+    case QRhi::TextureArrayRange:
+        return true;
+    case QRhi::NonFillPolygonMode:
+        return true;
+    case QRhi::OneDimensionalTextures:
+        return true;
+    case QRhi::OneDimensionalTextureMipmaps:
+        return true;
+    case QRhi::HalfAttributes:
+        return true;
+    case QRhi::RenderToOneDimensionalTexture:
+        return true;
+    case QRhi::ThreeDimensionalTextureMipmaps:
+        return true;
+    case QRhi::MultiView:
+        return false;
+    default:
+        Q_UNREACHABLE();
+        return false;
+    }
+}
+
+int QRhiD3D11::resourceLimit(QRhi::ResourceLimit limit) const
+{
+    switch (limit) {
+    case QRhi::TextureSizeMin:
+        return 1;
+    case QRhi::TextureSizeMax:
+        return D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
+    case QRhi::MaxColorAttachments:
+        return 8;
+    case QRhi::FramesInFlight:
+        // From our perspective. What D3D does internally is another question
+        // (there could be pipelining, helped f.ex. by our MAP_DISCARD based
+        // uniform buffer update strategy), but that's out of our hands and
+        // does not concern us here.
+        return 1;
+    case QRhi::MaxAsyncReadbackFrames:
+        return 1;
+    case QRhi::MaxThreadGroupsPerDimension:
+        return D3D11_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION;
+    case QRhi::MaxThreadsPerThreadGroup:
+        return D3D11_CS_THREAD_GROUP_MAX_THREADS_PER_GROUP;
+    case QRhi::MaxThreadGroupX:
+        return D3D11_CS_THREAD_GROUP_MAX_X;
+    case QRhi::MaxThreadGroupY:
+        return D3D11_CS_THREAD_GROUP_MAX_Y;
+    case QRhi::MaxThreadGroupZ:
+        return D3D11_CS_THREAD_GROUP_MAX_Z;
+    case QRhi::TextureArraySizeMax:
+        return D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION;
+    case QRhi::MaxUniformBufferRange:
+        return 65536;
+    case QRhi::MaxVertexInputs:
+        return D3D11_VS_INPUT_REGISTER_COUNT;
+    case QRhi::MaxVertexOutputs:
+        return D3D11_VS_OUTPUT_REGISTER_COUNT;
+    default:
+        Q_UNREACHABLE();
+        return 0;
+    }
+}
+
+const QRhiNativeHandles *QRhiD3D11::nativeHandles()
+{
+    return &nativeHandlesStruct;
+}
+
+QRhiDriverInfo QRhiD3D11::driverInfo() const
+{
+    return driverInfoStruct;
+}
+
+QRhiStats QRhiD3D11::statistics()
+{
+    QRhiStats result;
+    result.totalPipelineCreationTime = totalPipelineCreationTime();
+    return result;
+}
+
+bool QRhiD3D11::makeThreadLocalNativeContextCurrent()
+{
+    // not applicable
+    return false;
+}
+
+void QRhiD3D11::releaseCachedResources()
+{
+    clearShaderCache();
+    m_bytecodeCache.clear();
+}
+
+bool QRhiD3D11::isDeviceLost() const
+{
+    return deviceLost;
+}
+
+struct QD3D11PipelineCacheDataHeader
+{
+    quint32 rhiId;
+    quint32 arch;
+    // no need for driver specifics
+    quint32 count;
+    quint32 dataSize;
+};
+
+QByteArray QRhiD3D11::pipelineCacheData()
+{
+    QByteArray data;
+    if (m_bytecodeCache.isEmpty())
+        return data;
+
+    QD3D11PipelineCacheDataHeader header;
+    memset(&header, 0, sizeof(header));
+    header.rhiId = pipelineCacheRhiId();
+    header.arch = quint32(sizeof(void*));
+    header.count = m_bytecodeCache.count();
+
+    const size_t dataOffset = sizeof(header);
+    size_t dataSize = 0;
+    for (auto it = m_bytecodeCache.cbegin(), end = m_bytecodeCache.cend(); it != end; ++it) {
+        BytecodeCacheKey key = it.key();
+        QByteArray bytecode = it.value();
+        dataSize +=
+                  sizeof(quint32) + key.sourceHash.size()
+                + sizeof(quint32) + key.target.size()
+                + sizeof(quint32) + key.entryPoint.size()
+                + sizeof(quint32) // compileFlags
+                + sizeof(quint32) + bytecode.size();
+    }
+
+    QByteArray buf(dataOffset + dataSize, Qt::Uninitialized);
+    char *p = buf.data() + dataOffset;
+    for (auto it = m_bytecodeCache.cbegin(), end = m_bytecodeCache.cend(); it != end; ++it) {
+        BytecodeCacheKey key = it.key();
+        QByteArray bytecode = it.value();
+
+        quint32 i = key.sourceHash.size();
+        memcpy(p, &i, 4);
+        p += 4;
+        memcpy(p, key.sourceHash.constData(), key.sourceHash.size());
+        p += key.sourceHash.size();
+
+        i = key.target.size();
+        memcpy(p, &i, 4);
+        p += 4;
+        memcpy(p, key.target.constData(), key.target.size());
+        p += key.target.size();
+
+        i = key.entryPoint.size();
+        memcpy(p, &i, 4);
+        p += 4;
+        memcpy(p, key.entryPoint.constData(), key.entryPoint.size());
+        p += key.entryPoint.size();
+
+        quint32 f = key.compileFlags;
+        memcpy(p, &f, 4);
+        p += 4;
+
+        i = bytecode.size();
+        memcpy(p, &i, 4);
+        p += 4;
+        memcpy(p, bytecode.constData(), bytecode.size());
+        p += bytecode.size();
+    }
+    Q_ASSERT(p == buf.data() + dataOffset + dataSize);
+
+    header.dataSize = quint32(dataSize);
+    memcpy(buf.data(), &header, sizeof(header));
+
+    return buf;
+}
+
+void QRhiD3D11::setPipelineCacheData(const QByteArray &data)
+{
+    if (data.isEmpty())
+        return;
+
+    const size_t headerSize = sizeof(QD3D11PipelineCacheDataHeader);
+    if (data.size() < qsizetype(headerSize)) {
+        qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size (header incomplete)");
+        return;
+    }
+    const size_t dataOffset = headerSize;
+    QD3D11PipelineCacheDataHeader header;
+    memcpy(&header, data.constData(), headerSize);
+
+    const quint32 rhiId = pipelineCacheRhiId();
+    if (header.rhiId != rhiId) {
+        qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)",
+                rhiId, header.rhiId);
+        return;
+    }
+    const quint32 arch = quint32(sizeof(void*));
+    if (header.arch != arch) {
+        qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Architecture does not match (%u, %u)",
+                arch, header.arch);
+        return;
+    }
+    if (header.count == 0)
+        return;
+
+    if (data.size() < qsizetype(dataOffset + header.dataSize)) {
+        qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size (data incomplete)");
+        return;
+    }
+
+    m_bytecodeCache.clear();
+
+    const char *p = data.constData() + dataOffset;
+    for (quint32 i = 0; i < header.count; ++i) {
+        quint32 len = 0;
+        memcpy(&len, p, 4);
+        p += 4;
+        QByteArray sourceHash(len, Qt::Uninitialized);
+        memcpy(sourceHash.data(), p, len);
+        p += len;
+
+        memcpy(&len, p, 4);
+        p += 4;
+        QByteArray target(len, Qt::Uninitialized);
+        memcpy(target.data(), p, len);
+        p += len;
+
+        memcpy(&len, p, 4);
+        p += 4;
+        QByteArray entryPoint(len, Qt::Uninitialized);
+        memcpy(entryPoint.data(), p, len);
+        p += len;
+
+        quint32 flags;
+        memcpy(&flags, p, 4);
+        p += 4;
+
+        memcpy(&len, p, 4);
+        p += 4;
+        QByteArray bytecode(len, Qt::Uninitialized);
+        memcpy(bytecode.data(), p, len);
+        p += len;
+
+        BytecodeCacheKey cacheKey;
+        cacheKey.sourceHash = sourceHash;
+        cacheKey.target = target;
+        cacheKey.entryPoint = entryPoint;
+        cacheKey.compileFlags = flags;
+
+        m_bytecodeCache.insert(cacheKey, bytecode);
+    }
+
+    qCDebug(QRHI_LOG_INFO, "Seeded bytecode cache with %d shaders", int(m_bytecodeCache.count()));
+}
+
+QRhiRenderBuffer *QRhiD3D11::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+                                                int sampleCount, QRhiRenderBuffer::Flags flags,
+                                                QRhiTexture::Format backingFormatHint)
+{
+    return new QD3D11RenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint);
+}
+
+QRhiTexture *QRhiD3D11::createTexture(QRhiTexture::Format format,
+                                      const QSize &pixelSize, int depth, int arraySize,
+                                      int sampleCount, QRhiTexture::Flags flags)
+{
+    return new QD3D11Texture(this, format, pixelSize, depth, arraySize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiD3D11::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+                                      QRhiSampler::Filter mipmapMode,
+                                      QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w)
+{
+    return new QD3D11Sampler(this, magFilter, minFilter, mipmapMode, u, v, w);
+}
+
+QRhiTextureRenderTarget *QRhiD3D11::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+                                                              QRhiTextureRenderTarget::Flags flags)
+{
+    return new QD3D11TextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiD3D11::createGraphicsPipeline()
+{
+    return new QD3D11GraphicsPipeline(this);
+}
+
+QRhiComputePipeline *QRhiD3D11::createComputePipeline()
+{
+    return new QD3D11ComputePipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiD3D11::createShaderResourceBindings()
+{
+    return new QD3D11ShaderResourceBindings(this);
+}
+
+void QRhiD3D11::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+    QD3D11GraphicsPipeline *psD = QRHI_RES(QD3D11GraphicsPipeline, ps);
+    const bool pipelineChanged = cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation;
+
+    if (pipelineChanged) {
+        cbD->currentGraphicsPipeline = ps;
+        cbD->currentComputePipeline = nullptr;
+        cbD->currentPipelineGeneration = psD->generation;
+
+        QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+        cmd.cmd = QD3D11CommandBuffer::Command::BindGraphicsPipeline;
+        cmd.args.bindGraphicsPipeline.ps = psD;
+    }
+}
+
+static const int RBM_SUPPORTED_STAGES = 6;
+static const int RBM_VERTEX = 0;
+static const int RBM_HULL = 1;
+static const int RBM_DOMAIN = 2;
+static const int RBM_GEOMETRY = 3;
+static const int RBM_FRAGMENT = 4;
+static const int RBM_COMPUTE = 5;
+
+void QRhiD3D11::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+                                   int dynamicOffsetCount,
+                                   const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass != QD3D11CommandBuffer::NoPass);
+    QD3D11GraphicsPipeline *gfxPsD = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline);
+    QD3D11ComputePipeline *compPsD = QRHI_RES(QD3D11ComputePipeline, cbD->currentComputePipeline);
+
+    if (!srb) {
+        if (gfxPsD)
+            srb = gfxPsD->m_shaderResourceBindings;
+        else
+            srb = compPsD->m_shaderResourceBindings;
+    }
+
+    QD3D11ShaderResourceBindings *srbD = QRHI_RES(QD3D11ShaderResourceBindings, srb);
+
+    bool srbUpdate = false;
+    for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+        const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings.at(i));
+        QD3D11ShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
+        switch (b->type) {
+        case QRhiShaderResourceBinding::UniformBuffer:
+        {
+            QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, b->u.ubuf.buf);
+            // NonDynamicUniformBuffers is not supported by this backend
+            Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic && bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
+
+            executeBufferHostWrites(bufD);
+
+            if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
+                srbUpdate = true;
+                bd.ubuf.id = bufD->m_id;
+                bd.ubuf.generation = bufD->generation;
+            }
+        }
+            break;
+        case QRhiShaderResourceBinding::SampledTexture:
+        case QRhiShaderResourceBinding::Texture:
+        case QRhiShaderResourceBinding::Sampler:
+        {
+            const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
+            if (bd.stex.count != data->count) {
+                bd.stex.count = data->count;
+                srbUpdate = true;
+            }
+            for (int elem = 0; elem < data->count; ++elem) {
+                QD3D11Texture *texD = QRHI_RES(QD3D11Texture, data->texSamplers[elem].tex);
+                QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, data->texSamplers[elem].sampler);
+                // We use the same code path for both combined and separate
+                // images and samplers, so tex or sampler (but not both) can be
+                // null here.
+                Q_ASSERT(texD || samplerD);
+                const quint64 texId = texD ? texD->m_id : 0;
+                const uint texGen = texD ? texD->generation : 0;
+                const quint64 samplerId = samplerD ? samplerD->m_id : 0;
+                const uint samplerGen = samplerD ? samplerD->generation : 0;
+                if (texGen != bd.stex.d[elem].texGeneration
+                        || texId != bd.stex.d[elem].texId
+                        || samplerGen != bd.stex.d[elem].samplerGeneration
+                        || samplerId != bd.stex.d[elem].samplerId)
+                {
+                    srbUpdate = true;
+                    bd.stex.d[elem].texId = texId;
+                    bd.stex.d[elem].texGeneration = texGen;
+                    bd.stex.d[elem].samplerId = samplerId;
+                    bd.stex.d[elem].samplerGeneration = samplerGen;
+                }
+            }
+        }
+            break;
+        case QRhiShaderResourceBinding::ImageLoad:
+        case QRhiShaderResourceBinding::ImageStore:
+        case QRhiShaderResourceBinding::ImageLoadStore:
+        {
+            QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.simage.tex);
+            if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
+                srbUpdate = true;
+                bd.simage.id = texD->m_id;
+                bd.simage.generation = texD->generation;
+            }
+        }
+            break;
+        case QRhiShaderResourceBinding::BufferLoad:
+        case QRhiShaderResourceBinding::BufferStore:
+        case QRhiShaderResourceBinding::BufferLoadStore:
+        {
+            QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, b->u.sbuf.buf);
+            if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
+                srbUpdate = true;
+                bd.sbuf.id = bufD->m_id;
+                bd.sbuf.generation = bufD->generation;
+            }
+        }
+            break;
+        default:
+            Q_UNREACHABLE();
+            break;
+        }
+    }
+
+    if (srbUpdate) {
+        const QShader::NativeResourceBindingMap *resBindMaps[RBM_SUPPORTED_STAGES];
+        memset(resBindMaps, 0, sizeof(resBindMaps));
+        if (gfxPsD) {
+            resBindMaps[RBM_VERTEX] = &gfxPsD->vs.nativeResourceBindingMap;
+            resBindMaps[RBM_HULL] = &gfxPsD->hs.nativeResourceBindingMap;
+            resBindMaps[RBM_DOMAIN] = &gfxPsD->ds.nativeResourceBindingMap;
+            resBindMaps[RBM_GEOMETRY] = &gfxPsD->gs.nativeResourceBindingMap;
+            resBindMaps[RBM_FRAGMENT] = &gfxPsD->fs.nativeResourceBindingMap;
+        } else {
+            resBindMaps[RBM_COMPUTE] = &compPsD->cs.nativeResourceBindingMap;
+        }
+        updateShaderResourceBindings(srbD, resBindMaps);
+    }
+
+    const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
+    const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
+
+    if (srbChanged || srbRebuilt || srbUpdate || srbD->hasDynamicOffset) {
+        if (gfxPsD) {
+            cbD->currentGraphicsSrb = srb;
+            cbD->currentComputeSrb = nullptr;
+        } else {
+            cbD->currentGraphicsSrb = nullptr;
+            cbD->currentComputeSrb = srb;
+        }
+        cbD->currentSrbGeneration = srbD->generation;
+
+        QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+        cmd.cmd = QD3D11CommandBuffer::Command::BindShaderResources;
+        cmd.args.bindShaderResources.srb = srbD;
+        // dynamic offsets have to be applied at the time of executing the bind
+        // operations, not here
+        cmd.args.bindShaderResources.offsetOnlyChange = !srbChanged && !srbRebuilt && !srbUpdate && srbD->hasDynamicOffset;
+        cmd.args.bindShaderResources.dynamicOffsetCount = 0;
+        if (srbD->hasDynamicOffset) {
+            if (dynamicOffsetCount < QD3D11CommandBuffer::MAX_DYNAMIC_OFFSET_COUNT) {
+                cmd.args.bindShaderResources.dynamicOffsetCount = dynamicOffsetCount;
+                uint *p = cmd.args.bindShaderResources.dynamicOffsetPairs;
+                for (int i = 0; i < dynamicOffsetCount; ++i) {
+                    const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
+                    const uint binding = uint(dynOfs.first);
+                    Q_ASSERT(aligned(dynOfs.second, 256u) == dynOfs.second);
+                    const quint32 offsetInConstants = dynOfs.second / 16;
+                    *p++ = binding;
+                    *p++ = offsetInConstants;
+                }
+            } else {
+                qWarning("Too many dynamic offsets (%d, max is %d)",
+                         dynamicOffsetCount, QD3D11CommandBuffer::MAX_DYNAMIC_OFFSET_COUNT);
+            }
+        }
+    }
+}
+
+void QRhiD3D11::setVertexInput(QRhiCommandBuffer *cb,
+                               int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+                               QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+
+    bool needsBindVBuf = false;
+    for (int i = 0; i < bindingCount; ++i) {
+        const int inputSlot = startBinding + i;
+        QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, bindings[i].first);
+        Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer));
+        if (bufD->m_type == QRhiBuffer::Dynamic)
+            executeBufferHostWrites(bufD);
+
+        if (cbD->currentVertexBuffers[inputSlot] != bufD->buffer
+                || cbD->currentVertexOffsets[inputSlot] != bindings[i].second)
+        {
+            needsBindVBuf = true;
+            cbD->currentVertexBuffers[inputSlot] = bufD->buffer;
+            cbD->currentVertexOffsets[inputSlot] = bindings[i].second;
+        }
+    }
+
+    if (needsBindVBuf) {
+        QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+        cmd.cmd = QD3D11CommandBuffer::Command::BindVertexBuffers;
+        cmd.args.bindVertexBuffers.startSlot = startBinding;
+        if (bindingCount > QD3D11CommandBuffer::MAX_VERTEX_BUFFER_BINDING_COUNT) {
+            qWarning("Too many vertex buffer bindings (%d, max is %d)",
+                     bindingCount, QD3D11CommandBuffer::MAX_VERTEX_BUFFER_BINDING_COUNT);
+            bindingCount = QD3D11CommandBuffer::MAX_VERTEX_BUFFER_BINDING_COUNT;
+        }
+        cmd.args.bindVertexBuffers.slotCount = bindingCount;
+        QD3D11GraphicsPipeline *psD = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline);
+        const QRhiVertexInputLayout &inputLayout(psD->m_vertexInputLayout);
+        const int inputBindingCount = inputLayout.cendBindings() - inputLayout.cbeginBindings();
+        for (int i = 0, ie = qMin(bindingCount, inputBindingCount); i != ie; ++i) {
+            QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, bindings[i].first);
+            cmd.args.bindVertexBuffers.buffers[i] = bufD->buffer;
+            cmd.args.bindVertexBuffers.offsets[i] = bindings[i].second;
+            cmd.args.bindVertexBuffers.strides[i] = inputLayout.bindingAt(i)->stride();
+        }
+    }
+
+    if (indexBuf) {
+        QD3D11Buffer *ibufD = QRHI_RES(QD3D11Buffer, indexBuf);
+        Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer));
+        if (ibufD->m_type == QRhiBuffer::Dynamic)
+            executeBufferHostWrites(ibufD);
+
+        const DXGI_FORMAT dxgiFormat = indexFormat == QRhiCommandBuffer::IndexUInt16 ? DXGI_FORMAT_R16_UINT
+                                                                                     : DXGI_FORMAT_R32_UINT;
+        if (cbD->currentIndexBuffer != ibufD->buffer
+                || cbD->currentIndexOffset != indexOffset
+                || cbD->currentIndexFormat != dxgiFormat)
+        {
+            cbD->currentIndexBuffer = ibufD->buffer;
+            cbD->currentIndexOffset = indexOffset;
+            cbD->currentIndexFormat = dxgiFormat;
+
+            QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+            cmd.cmd = QD3D11CommandBuffer::Command::BindIndexBuffer;
+            cmd.args.bindIndexBuffer.buffer = ibufD->buffer;
+            cmd.args.bindIndexBuffer.offset = indexOffset;
+            cmd.args.bindIndexBuffer.format = dxgiFormat;
+        }
+    }
+}
+
+void QRhiD3D11::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+    Q_ASSERT(cbD->currentTarget);
+    const QSize outputSize = cbD->currentTarget->pixelSize();
+
+    // d3d expects top-left, QRhiViewport is bottom-left
+    float x, y, w, h;
+    if (!qrhi_toTopLeftRenderTargetRect<UnBounded>(outputSize, viewport.viewport(), &x, &y, &w, &h))
+        return;
+
+    QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::Viewport;
+    cmd.args.viewport.x = x;
+    cmd.args.viewport.y = y;
+    cmd.args.viewport.w = w;
+    cmd.args.viewport.h = h;
+    cmd.args.viewport.d0 = viewport.minDepth();
+    cmd.args.viewport.d1 = viewport.maxDepth();
+}
+
+void QRhiD3D11::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+    Q_ASSERT(cbD->currentTarget);
+    const QSize outputSize = cbD->currentTarget->pixelSize();
+
+    // d3d expects top-left, QRhiScissor is bottom-left
+    int x, y, w, h;
+    if (!qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, scissor.scissor(), &x, &y, &w, &h))
+        return;
+
+    QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::Scissor;
+    cmd.args.scissor.x = x;
+    cmd.args.scissor.y = y;
+    cmd.args.scissor.w = w;
+    cmd.args.scissor.h = h;
+}
+
+void QRhiD3D11::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+
+    QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::BlendConstants;
+    cmd.args.blendConstants.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline);
+    cmd.args.blendConstants.c[0] = float(c.redF());
+    cmd.args.blendConstants.c[1] = float(c.greenF());
+    cmd.args.blendConstants.c[2] = float(c.blueF());
+    cmd.args.blendConstants.c[3] = float(c.alphaF());
+}
+
+void QRhiD3D11::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+
+    QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::StencilRef;
+    cmd.args.stencilRef.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline);
+    cmd.args.stencilRef.ref = refValue;
+}
+
+void QRhiD3D11::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+                     quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+
+    QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::Draw;
+    cmd.args.draw.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline);
+    cmd.args.draw.vertexCount = vertexCount;
+    cmd.args.draw.instanceCount = instanceCount;
+    cmd.args.draw.firstVertex = firstVertex;
+    cmd.args.draw.firstInstance = firstInstance;
+}
+
+void QRhiD3D11::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+                            quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+
+    QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::DrawIndexed;
+    cmd.args.drawIndexed.ps = QRHI_RES(QD3D11GraphicsPipeline, cbD->currentGraphicsPipeline);
+    cmd.args.drawIndexed.indexCount = indexCount;
+    cmd.args.drawIndexed.instanceCount = instanceCount;
+    cmd.args.drawIndexed.firstIndex = firstIndex;
+    cmd.args.drawIndexed.vertexOffset = vertexOffset;
+    cmd.args.drawIndexed.firstInstance = firstInstance;
+}
+
+void QRhiD3D11::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+    if (!debugMarkers || !annotations)
+        return;
+
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::DebugMarkBegin;
+    qstrncpy(cmd.args.debugMark.s, name.constData(), sizeof(cmd.args.debugMark.s));
+}
+
+void QRhiD3D11::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+    if (!debugMarkers || !annotations)
+        return;
+
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::DebugMarkEnd;
+}
+
+void QRhiD3D11::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+    if (!debugMarkers || !annotations)
+        return;
+
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::DebugMarkMsg;
+    qstrncpy(cmd.args.debugMark.s, msg.constData(), sizeof(cmd.args.debugMark.s));
+}
+
+const QRhiNativeHandles *QRhiD3D11::nativeHandles(QRhiCommandBuffer *cb)
+{
+    Q_UNUSED(cb);
+    return nullptr;
+}
+
+void QRhiD3D11::beginExternal(QRhiCommandBuffer *cb)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    executeCommandBuffer(cbD);
+    cbD->resetCommands();
+}
+
+void QRhiD3D11::endExternal(QRhiCommandBuffer *cb)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->commands.isEmpty());
+    cbD->resetCachedState();
+    if (cbD->currentTarget) { // could be compute, no rendertarget then
+        QD3D11CommandBuffer::Command &fbCmd(cbD->commands.get());
+        fbCmd.cmd = QD3D11CommandBuffer::Command::SetRenderTarget;
+        fbCmd.args.setRenderTarget.rt = cbD->currentTarget;
+    }
+}
+
+double QRhiD3D11::lastCompletedGpuTime(QRhiCommandBuffer *cb)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    return cbD->lastGpuTime;
+}
+
+static inline QD3D11RenderTargetData *rtData(QRhiRenderTarget *rt)
+{
+    switch (rt->resourceType()) {
+    case QRhiResource::SwapChainRenderTarget:
+        return &QRHI_RES(QD3D11SwapChainRenderTarget, rt)->d;
+    case QRhiResource::TextureRenderTarget:
+        return &QRHI_RES(QD3D11TextureRenderTarget, rt)->d;
+    default:
+        Q_UNREACHABLE();
+        return nullptr;
+    }
+}
+
+QRhi::FrameOpResult QRhiD3D11::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+    Q_UNUSED(flags);
+
+    QD3D11SwapChain *swapChainD = QRHI_RES(QD3D11SwapChain, swapChain);
+    contextState.currentSwapChain = swapChainD;
+    const int currentFrameSlot = swapChainD->currentFrameSlot;
+
+    swapChainD->cb.resetState();
+
+    swapChainD->rt.d.rtv[0] = swapChainD->sampleDesc.Count > 1 ?
+                swapChainD->msaaRtv[currentFrameSlot] : swapChainD->backBufferRtv;
+    swapChainD->rt.d.dsv = swapChainD->ds ? swapChainD->ds->dsv : nullptr;
+
+    finishActiveReadbacks();
+
+    if (swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex]) {
+        double elapsedSec = 0;
+        if (swapChainD->timestamps.tryQueryTimestamps(swapChainD->currentTimestampPairIndex, context, &elapsedSec))
+            swapChainD->cb.lastGpuTime = elapsedSec;
+    }
+
+    ID3D11Query *tsStart = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2];
+    ID3D11Query *tsDisjoint = swapChainD->timestamps.disjointQuery[swapChainD->currentTimestampPairIndex];
+    const bool recordTimestamps = tsStart && tsDisjoint && !swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex];
+
+    QD3D11CommandBuffer::Command &cmd(swapChainD->cb.commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::BeginFrame;
+    cmd.args.beginFrame.tsQuery = recordTimestamps ? tsStart : nullptr;
+    cmd.args.beginFrame.tsDisjointQuery = recordTimestamps ? tsDisjoint : nullptr;
+    cmd.args.beginFrame.swapchainData = rtData(&swapChainD->rt);
+
+    return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D11::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+    QD3D11SwapChain *swapChainD = QRHI_RES(QD3D11SwapChain, swapChain);
+    Q_ASSERT(contextState.currentSwapChain = swapChainD);
+    const int currentFrameSlot = swapChainD->currentFrameSlot;
+
+    QD3D11CommandBuffer::Command &cmd(swapChainD->cb.commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::EndFrame;
+    cmd.args.endFrame.tsQuery = nullptr; // done later manually, see below
+    cmd.args.endFrame.tsDisjointQuery = nullptr;
+
+    // send all commands to the context
+    executeCommandBuffer(&swapChainD->cb);
+
+    if (swapChainD->sampleDesc.Count > 1) {
+        context->ResolveSubresource(swapChainD->backBufferTex, 0,
+                                    swapChainD->msaaTex[currentFrameSlot], 0,
+                                    swapChainD->colorFormat);
+    }
+
+    // this is here because we want to include the time spent on the ResolveSubresource as well
+    ID3D11Query *tsEnd = swapChainD->timestamps.query[swapChainD->currentTimestampPairIndex * 2 + 1];
+    ID3D11Query *tsDisjoint = swapChainD->timestamps.disjointQuery[swapChainD->currentTimestampPairIndex];
+    const bool recordTimestamps = tsEnd && tsDisjoint && !swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex];
+    if (recordTimestamps) {
+        context->End(tsEnd);
+        context->End(tsDisjoint);
+        swapChainD->timestamps.active[swapChainD->currentTimestampPairIndex] = true;
+        swapChainD->currentTimestampPairIndex = (swapChainD->currentTimestampPairIndex + 1) % QD3D11SwapChainTimestamps::TIMESTAMP_PAIRS;
+    }
+
+    if (!flags.testFlag(QRhi::SkipPresent)) {
+        UINT presentFlags = 0;
+        if (swapChainD->swapInterval == 0 && (swapChainD->swapChainFlags & DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING))
+            presentFlags |= DXGI_PRESENT_ALLOW_TEARING;
+        if (!swapChainD->swapChain) {
+            qWarning("Failed to present: IDXGISwapChain is unavailable");
+            return QRhi::FrameOpError;
+        }
+        HRESULT hr = swapChainD->swapChain->Present(swapChainD->swapInterval, presentFlags);
+        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(QSystemError::windowsComString(hr)));
+            return QRhi::FrameOpError;
+        }
+
+        if (dcompDevice && swapChainD->dcompTarget && swapChainD->dcompVisual)
+            dcompDevice->Commit();
+
+        // move on to the next buffer
+        swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QD3D11SwapChain::BUFFER_COUNT;
+    } else {
+        context->Flush();
+    }
+
+    swapChainD->frameCount += 1;
+    contextState.currentSwapChain = nullptr;
+
+    return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D11::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags)
+{
+    Q_UNUSED(flags);
+    ofr.active = true;
+
+    ofr.cbWrapper.resetState();
+    *cb = &ofr.cbWrapper;
+
+    if (rhiFlags.testFlag(QRhi::EnableTimestamps)) {
+        D3D11_QUERY_DESC queryDesc = {};
+        if (!ofr.tsDisjointQuery) {
+            queryDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT;
+            HRESULT hr = dev->CreateQuery(&queryDesc, &ofr.tsDisjointQuery);
+            if (FAILED(hr)) {
+                qWarning("Failed to create timestamp disjoint query: %s",
+                         qPrintable(QSystemError::windowsComString(hr)));
+                return QRhi::FrameOpError;
+            }
+        }
+        queryDesc.Query = D3D11_QUERY_TIMESTAMP;
+        for (int i = 0; i < 2; ++i) {
+            if (!ofr.tsQueries[i]) {
+                HRESULT hr = dev->CreateQuery(&queryDesc, &ofr.tsQueries[i]);
+                if (FAILED(hr)) {
+                    qWarning("Failed to create timestamp query: %s",
+                             qPrintable(QSystemError::windowsComString(hr)));
+                    return QRhi::FrameOpError;
+                }
+            }
+        }
+    }
+
+    QD3D11CommandBuffer::Command &cmd(ofr.cbWrapper.commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::BeginFrame;
+    cmd.args.beginFrame.tsQuery = ofr.tsQueries[0] ? ofr.tsQueries[0] : nullptr;
+    cmd.args.beginFrame.tsDisjointQuery = ofr.tsDisjointQuery ? ofr.tsDisjointQuery : nullptr;
+    cmd.args.beginFrame.swapchainData = nullptr;
+
+    return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D11::endOffscreenFrame(QRhi::EndFrameFlags flags)
+{
+    Q_UNUSED(flags);
+    ofr.active = false;
+
+    QD3D11CommandBuffer::Command &cmd(ofr.cbWrapper.commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::EndFrame;
+    cmd.args.endFrame.tsQuery = ofr.tsQueries[1] ? ofr.tsQueries[1] : nullptr;
+    cmd.args.endFrame.tsDisjointQuery = ofr.tsDisjointQuery ? ofr.tsDisjointQuery : nullptr;
+
+    executeCommandBuffer(&ofr.cbWrapper);
+    context->Flush();
+
+    finishActiveReadbacks();
+
+    if (ofr.tsQueries[0]) {
+        quint64 timestamps[2];
+        D3D11_QUERY_DATA_TIMESTAMP_DISJOINT dj;
+        HRESULT hr;
+        bool ok = true;
+        do {
+            hr = context->GetData(ofr.tsDisjointQuery, &dj, sizeof(dj), 0);
+        } while (hr == S_FALSE);
+        ok &= hr == S_OK;
+        do {
+            hr = context->GetData(ofr.tsQueries[1], &timestamps[1], sizeof(quint64), 0);
+        } while (hr == S_FALSE);
+        ok &= hr == S_OK;
+        do {
+            hr = context->GetData(ofr.tsQueries[0], &timestamps[0], sizeof(quint64), 0);
+        } while (hr == S_FALSE);
+        ok &= hr == S_OK;
+        if (ok) {
+            if (!dj.Disjoint && dj.Frequency) {
+                const float elapsedMs = (timestamps[1] - timestamps[0]) / float(dj.Frequency) * 1000.0f;
+                ofr.cbWrapper.lastGpuTime = elapsedMs / 1000.0;
+            }
+        }
+    }
+
+    return QRhi::FrameOpSuccess;
+}
+
+static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
+{
+    const bool srgb = flags.testFlag(QRhiTexture::sRGB);
+    switch (format) {
+    case QRhiTexture::RGBA8:
+        return srgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM;
+    case QRhiTexture::BGRA8:
+        return srgb ? DXGI_FORMAT_B8G8R8A8_UNORM_SRGB : DXGI_FORMAT_B8G8R8A8_UNORM;
+    case QRhiTexture::R8:
+        return DXGI_FORMAT_R8_UNORM;
+    case QRhiTexture::RG8:
+        return DXGI_FORMAT_R8G8_UNORM;
+    case QRhiTexture::R16:
+        return DXGI_FORMAT_R16_UNORM;
+    case QRhiTexture::RG16:
+        return DXGI_FORMAT_R16G16_UNORM;
+    case QRhiTexture::RED_OR_ALPHA8:
+        return DXGI_FORMAT_R8_UNORM;
+
+    case QRhiTexture::RGBA16F:
+        return DXGI_FORMAT_R16G16B16A16_FLOAT;
+    case QRhiTexture::RGBA32F:
+        return DXGI_FORMAT_R32G32B32A32_FLOAT;
+    case QRhiTexture::R16F:
+        return DXGI_FORMAT_R16_FLOAT;
+    case QRhiTexture::R32F:
+        return DXGI_FORMAT_R32_FLOAT;
+
+    case QRhiTexture::RGB10A2:
+        return DXGI_FORMAT_R10G10B10A2_UNORM;
+
+    case QRhiTexture::D16:
+        return DXGI_FORMAT_R16_TYPELESS;
+    case QRhiTexture::D24:
+        return DXGI_FORMAT_R24G8_TYPELESS;
+    case QRhiTexture::D24S8:
+        return DXGI_FORMAT_R24G8_TYPELESS;
+    case QRhiTexture::D32F:
+        return DXGI_FORMAT_R32_TYPELESS;
+
+    case QRhiTexture::BC1:
+        return srgb ? DXGI_FORMAT_BC1_UNORM_SRGB : DXGI_FORMAT_BC1_UNORM;
+    case QRhiTexture::BC2:
+        return srgb ? DXGI_FORMAT_BC2_UNORM_SRGB : DXGI_FORMAT_BC2_UNORM;
+    case QRhiTexture::BC3:
+        return srgb ? DXGI_FORMAT_BC3_UNORM_SRGB : DXGI_FORMAT_BC3_UNORM;
+    case QRhiTexture::BC4:
+        return DXGI_FORMAT_BC4_UNORM;
+    case QRhiTexture::BC5:
+        return DXGI_FORMAT_BC5_UNORM;
+    case QRhiTexture::BC6H:
+        return DXGI_FORMAT_BC6H_UF16;
+    case QRhiTexture::BC7:
+        return srgb ? DXGI_FORMAT_BC7_UNORM_SRGB : DXGI_FORMAT_BC7_UNORM;
+
+    case QRhiTexture::ETC2_RGB8:
+    case QRhiTexture::ETC2_RGB8A1:
+    case QRhiTexture::ETC2_RGBA8:
+        qWarning("QRhiD3D11 does not support ETC2 textures");
+        return DXGI_FORMAT_R8G8B8A8_UNORM;
+
+    case QRhiTexture::ASTC_4x4:
+    case QRhiTexture::ASTC_5x4:
+    case QRhiTexture::ASTC_5x5:
+    case QRhiTexture::ASTC_6x5:
+    case QRhiTexture::ASTC_6x6:
+    case QRhiTexture::ASTC_8x5:
+    case QRhiTexture::ASTC_8x6:
+    case QRhiTexture::ASTC_8x8:
+    case QRhiTexture::ASTC_10x5:
+    case QRhiTexture::ASTC_10x6:
+    case QRhiTexture::ASTC_10x8:
+    case QRhiTexture::ASTC_10x10:
+    case QRhiTexture::ASTC_12x10:
+    case QRhiTexture::ASTC_12x12:
+        qWarning("QRhiD3D11 does not support ASTC textures");
+        return DXGI_FORMAT_R8G8B8A8_UNORM;
+
+    default:
+        Q_UNREACHABLE();
+        return DXGI_FORMAT_R8G8B8A8_UNORM;
+    }
+}
+
+static inline QRhiTexture::Format swapchainReadbackTextureFormat(DXGI_FORMAT format, QRhiTexture::Flags *flags)
+{
+    switch (format) {
+    case DXGI_FORMAT_R8G8B8A8_UNORM:
+        return QRhiTexture::RGBA8;
+    case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+        if (flags)
+            (*flags) |= QRhiTexture::sRGB;
+        return QRhiTexture::RGBA8;
+    case DXGI_FORMAT_B8G8R8A8_UNORM:
+        return QRhiTexture::BGRA8;
+    case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
+        if (flags)
+            (*flags) |= QRhiTexture::sRGB;
+        return QRhiTexture::BGRA8;
+    case DXGI_FORMAT_R16G16B16A16_FLOAT:
+        return QRhiTexture::RGBA16F;
+    case DXGI_FORMAT_R32G32B32A32_FLOAT:
+        return QRhiTexture::RGBA32F;
+    case DXGI_FORMAT_R10G10B10A2_UNORM:
+        return QRhiTexture::RGB10A2;
+    default:
+        qWarning("DXGI_FORMAT %d cannot be read back", format);
+        break;
+    }
+    return QRhiTexture::UnknownFormat;
+}
+
+static inline bool isDepthTextureFormat(QRhiTexture::Format format)
+{
+    switch (format) {
+    case QRhiTexture::Format::D16:
+    case QRhiTexture::Format::D24:
+    case QRhiTexture::Format::D24S8:
+    case QRhiTexture::Format::D32F:
+        return true;
+
+    default:
+        return false;
+    }
+}
+
+QRhi::FrameOpResult QRhiD3D11::finish()
+{
+    if (inFrame) {
+        if (ofr.active) {
+            Q_ASSERT(!contextState.currentSwapChain);
+            Q_ASSERT(ofr.cbWrapper.recordingPass == QD3D11CommandBuffer::NoPass);
+            executeCommandBuffer(&ofr.cbWrapper);
+            ofr.cbWrapper.resetCommands();
+        } else {
+            Q_ASSERT(contextState.currentSwapChain);
+            Q_ASSERT(contextState.currentSwapChain->cb.recordingPass == QD3D11CommandBuffer::NoPass);
+            executeCommandBuffer(&contextState.currentSwapChain->cb);
+            contextState.currentSwapChain->cb.resetCommands();
+        }
+    }
+
+    finishActiveReadbacks();
+
+    return QRhi::FrameOpSuccess;
+}
+
+void QRhiD3D11::enqueueSubresUpload(QD3D11Texture *texD, QD3D11CommandBuffer *cbD,
+                                    int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc)
+{
+    const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
+    UINT subres = D3D11CalcSubresource(UINT(level), is3D ? 0u : UINT(layer), texD->mipLevelCount);
+    D3D11_BOX box;
+    box.front = is3D ? UINT(layer) : 0u;
+    // back, right, bottom are exclusive
+    box.back = box.front + 1;
+    QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::UpdateSubRes;
+    cmd.args.updateSubRes.dst = texD->textureResource();
+    cmd.args.updateSubRes.dstSubRes = subres;
+
+    const QPoint dp = subresDesc.destinationTopLeft();
+    if (!subresDesc.image().isNull()) {
+        QImage img = subresDesc.image();
+        QSize size = img.size();
+        int bpl = img.bytesPerLine();
+        if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
+            const QPoint sp = subresDesc.sourceTopLeft();
+            if (!subresDesc.sourceSize().isEmpty())
+                size = subresDesc.sourceSize();
+            if (img.depth() == 32) {
+                const int offset = sp.y() * img.bytesPerLine() + sp.x() * 4;
+                cmd.args.updateSubRes.src = cbD->retainImage(img) + offset;
+            } else {
+                img = img.copy(sp.x(), sp.y(), size.width(), size.height());
+                bpl = img.bytesPerLine();
+                cmd.args.updateSubRes.src = cbD->retainImage(img);
+            }
+        } else {
+            cmd.args.updateSubRes.src = cbD->retainImage(img);
+        }
+        box.left = UINT(dp.x());
+        box.top = UINT(dp.y());
+        box.right = UINT(dp.x() + size.width());
+        box.bottom = UINT(dp.y() + size.height());
+        cmd.args.updateSubRes.hasDstBox = true;
+        cmd.args.updateSubRes.dstBox = box;
+        cmd.args.updateSubRes.srcRowPitch = UINT(bpl);
+    } else if (!subresDesc.data().isEmpty() && isCompressedFormat(texD->m_format)) {
+        const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
+                                                             : subresDesc.sourceSize();
+        quint32 bpl = 0;
+        QSize blockDim;
+        compressedFormatInfo(texD->m_format, size, &bpl, nullptr, &blockDim);
+        // Everything must be a multiple of the block width and
+        // height, so e.g. a mip level of size 2x2 will be 4x4 when it
+        // comes to the actual data.
+        box.left = UINT(aligned(dp.x(), blockDim.width()));
+        box.top = UINT(aligned(dp.y(), blockDim.height()));
+        box.right = UINT(aligned(dp.x() + size.width(), blockDim.width()));
+        box.bottom = UINT(aligned(dp.y() + size.height(), blockDim.height()));
+        cmd.args.updateSubRes.hasDstBox = true;
+        cmd.args.updateSubRes.dstBox = box;
+        cmd.args.updateSubRes.src = cbD->retainData(subresDesc.data());
+        cmd.args.updateSubRes.srcRowPitch = bpl;
+    } else if (!subresDesc.data().isEmpty()) {
+        const QSize size = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
+                                                             : subresDesc.sourceSize();
+        quint32 bpl = 0;
+        if (subresDesc.dataStride())
+            bpl = subresDesc.dataStride();
+        else
+            textureFormatInfo(texD->m_format, size, &bpl, nullptr, nullptr);
+        box.left = UINT(dp.x());
+        box.top = UINT(dp.y());
+        box.right = UINT(dp.x() + size.width());
+        box.bottom = UINT(dp.y() + size.height());
+        cmd.args.updateSubRes.hasDstBox = true;
+        cmd.args.updateSubRes.dstBox = box;
+        cmd.args.updateSubRes.src = cbD->retainData(subresDesc.data());
+        cmd.args.updateSubRes.srcRowPitch = bpl;
+    } else {
+        qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
+        cbD->commands.unget();
+    }
+}
+
+void QRhiD3D11::enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+
+    for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) {
+        const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]);
+        if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) {
+            QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf);
+            Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+            memcpy(bufD->dynBuf + u.offset, u.data.constData(), size_t(u.data.size()));
+            bufD->hasPendingDynamicUpdates = true;
+        } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) {
+            QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf);
+            Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
+            Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
+            QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+            cmd.cmd = QD3D11CommandBuffer::Command::UpdateSubRes;
+            cmd.args.updateSubRes.dst = bufD->buffer;
+            cmd.args.updateSubRes.dstSubRes = 0;
+            cmd.args.updateSubRes.src = cbD->retainBufferData(u.data);
+            cmd.args.updateSubRes.srcRowPitch = 0;
+            // Specify the region (even when offset is 0 and all data is provided)
+            // since the ID3D11Buffer's size is rounded up to be a multiple of 256
+            // while the data we have has the original size.
+            D3D11_BOX box;
+            box.left = u.offset;
+            box.top = box.front = 0;
+            box.back = box.bottom = 1;
+            box.right = u.offset + u.data.size(); // no -1: right, bottom, back are exclusive, see D3D11_BOX doc
+            cmd.args.updateSubRes.hasDstBox = true;
+            cmd.args.updateSubRes.dstBox = box;
+        } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) {
+            QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, u.buf);
+            if (bufD->m_type == QRhiBuffer::Dynamic) {
+                u.result->data.resize(u.readSize);
+                memcpy(u.result->data.data(), bufD->dynBuf + u.offset, size_t(u.readSize));
+                if (u.result->completed)
+                    u.result->completed();
+            } else {
+                BufferReadback readback;
+                readback.result = u.result;
+                readback.byteSize = u.readSize;
+
+                D3D11_BUFFER_DESC desc = {};
+                desc.ByteWidth = readback.byteSize;
+                desc.Usage = D3D11_USAGE_STAGING;
+                desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+                HRESULT hr = dev->CreateBuffer(&desc, nullptr, &readback.stagingBuf);
+                if (FAILED(hr)) {
+                    qWarning("Failed to create buffer: %s",
+                        qPrintable(QSystemError::windowsComString(hr)));
+                    continue;
+                }
+
+                QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+                cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes;
+                cmd.args.copySubRes.dst = readback.stagingBuf;
+                cmd.args.copySubRes.dstSubRes = 0;
+                cmd.args.copySubRes.dstX = 0;
+                cmd.args.copySubRes.dstY = 0;
+                cmd.args.copySubRes.dstZ = 0;
+                cmd.args.copySubRes.src = bufD->buffer;
+                cmd.args.copySubRes.srcSubRes = 0;
+                cmd.args.copySubRes.hasSrcBox = true;
+                D3D11_BOX box;
+                box.left = u.offset;
+                box.top = box.front = 0;
+                box.back = box.bottom = 1;
+                box.right = u.offset + u.readSize;
+                cmd.args.copySubRes.srcBox = box;
+
+                activeBufferReadbacks.append(readback);
+            }
+        }
+    }
+    for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) {
+        const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]);
+        if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
+            QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.dst);
+            for (int layer = 0, maxLayer = u.subresDesc.count(); layer < maxLayer; ++layer) {
+                for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) {
+                    for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(u.subresDesc[layer][level]))
+                        enqueueSubresUpload(texD, cbD, layer, level, subresDesc);
+                }
+            }
+        } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
+            Q_ASSERT(u.src && u.dst);
+            QD3D11Texture *srcD = QRHI_RES(QD3D11Texture, u.src);
+            QD3D11Texture *dstD = QRHI_RES(QD3D11Texture, u.dst);
+            const bool srcIs3D = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
+            const bool dstIs3D = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
+            UINT srcSubRes = D3D11CalcSubresource(UINT(u.desc.sourceLevel()), srcIs3D ? 0u : UINT(u.desc.sourceLayer()), srcD->mipLevelCount);
+            UINT dstSubRes = D3D11CalcSubresource(UINT(u.desc.destinationLevel()), dstIs3D ? 0u : UINT(u.desc.destinationLayer()), dstD->mipLevelCount);
+            const QPoint dp = u.desc.destinationTopLeft();
+            const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
+            const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
+            const QPoint sp = u.desc.sourceTopLeft();
+            D3D11_BOX srcBox;
+            srcBox.left = UINT(sp.x());
+            srcBox.top = UINT(sp.y());
+            srcBox.front = srcIs3D ? UINT(u.desc.sourceLayer()) : 0u;
+            // back, right, bottom are exclusive
+            srcBox.right = srcBox.left + UINT(copySize.width());
+            srcBox.bottom = srcBox.top + UINT(copySize.height());
+            srcBox.back = srcBox.front + 1;
+            QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+            cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes;
+            cmd.args.copySubRes.dst = dstD->textureResource();
+            cmd.args.copySubRes.dstSubRes = dstSubRes;
+            cmd.args.copySubRes.dstX = UINT(dp.x());
+            cmd.args.copySubRes.dstY = UINT(dp.y());
+            cmd.args.copySubRes.dstZ = dstIs3D ? UINT(u.desc.destinationLayer()) : 0u;
+            cmd.args.copySubRes.src = srcD->textureResource();
+            cmd.args.copySubRes.srcSubRes = srcSubRes;
+            cmd.args.copySubRes.hasSrcBox = true;
+            cmd.args.copySubRes.srcBox = srcBox;
+        } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+            TextureReadback readback;
+            readback.desc = u.rb;
+            readback.result = u.result;
+
+            ID3D11Resource *src;
+            DXGI_FORMAT dxgiFormat;
+            QSize pixelSize;
+            QRhiTexture::Format format;
+            UINT subres = 0;
+            QD3D11Texture *texD = QRHI_RES(QD3D11Texture, u.rb.texture());
+            QD3D11SwapChain *swapChainD = nullptr;
+            bool is3D = false;
+
+            if (texD) {
+                if (texD->sampleDesc.Count > 1) {
+                    qWarning("Multisample texture cannot be read back");
+                    continue;
+                }
+                src = texD->textureResource();
+                dxgiFormat = texD->dxgiFormat;
+                pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
+                format = texD->m_format;
+                is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
+                subres = D3D11CalcSubresource(UINT(u.rb.level()), UINT(is3D ? 0 : u.rb.layer()), texD->mipLevelCount);
+            } else {
+                Q_ASSERT(contextState.currentSwapChain);
+                swapChainD = QRHI_RES(QD3D11SwapChain, contextState.currentSwapChain);
+                if (swapChainD->sampleDesc.Count > 1) {
+                    // Unlike with textures, reading back a multisample swapchain image
+                    // has to be supported. Insert a resolve.
+                    QD3D11CommandBuffer::Command &rcmd(cbD->commands.get());
+                    rcmd.cmd = QD3D11CommandBuffer::Command::ResolveSubRes;
+                    rcmd.args.resolveSubRes.dst = swapChainD->backBufferTex;
+                    rcmd.args.resolveSubRes.dstSubRes = 0;
+                    rcmd.args.resolveSubRes.src = swapChainD->msaaTex[swapChainD->currentFrameSlot];
+                    rcmd.args.resolveSubRes.srcSubRes = 0;
+                    rcmd.args.resolveSubRes.format = swapChainD->colorFormat;
+                }
+                src = swapChainD->backBufferTex;
+                dxgiFormat = swapChainD->colorFormat;
+                pixelSize = swapChainD->pixelSize;
+                format = swapchainReadbackTextureFormat(dxgiFormat, nullptr);
+                if (format == QRhiTexture::UnknownFormat)
+                    continue;
+            }
+            quint32 byteSize = 0;
+            quint32 bpl = 0;
+            textureFormatInfo(format, pixelSize, &bpl, &byteSize, nullptr);
+
+            D3D11_TEXTURE2D_DESC desc = {};
+            desc.Width = UINT(pixelSize.width());
+            desc.Height = UINT(pixelSize.height());
+            desc.MipLevels = 1;
+            desc.ArraySize = 1;
+            desc.Format = dxgiFormat;
+            desc.SampleDesc.Count = 1;
+            desc.Usage = D3D11_USAGE_STAGING;
+            desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+            ID3D11Texture2D *stagingTex;
+            HRESULT hr = dev->CreateTexture2D(&desc, nullptr, &stagingTex);
+            if (FAILED(hr)) {
+                qWarning("Failed to create readback staging texture: %s",
+                    qPrintable(QSystemError::windowsComString(hr)));
+                return;
+            }
+
+            QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+            cmd.cmd = QD3D11CommandBuffer::Command::CopySubRes;
+            cmd.args.copySubRes.dst = stagingTex;
+            cmd.args.copySubRes.dstSubRes = 0;
+            cmd.args.copySubRes.dstX = 0;
+            cmd.args.copySubRes.dstY = 0;
+            cmd.args.copySubRes.dstZ = 0;
+            cmd.args.copySubRes.src = src;
+            cmd.args.copySubRes.srcSubRes = subres;
+            if (is3D) {
+                D3D11_BOX srcBox = {};
+                srcBox.front = UINT(u.rb.layer());
+                srcBox.right = desc.Width; // exclusive
+                srcBox.bottom = desc.Height;
+                srcBox.back = srcBox.front + 1;
+                cmd.args.copySubRes.hasSrcBox = true;
+                cmd.args.copySubRes.srcBox = srcBox;
+            } else {
+                cmd.args.copySubRes.hasSrcBox = false;
+            }
+
+            readback.stagingTex = stagingTex;
+            readback.byteSize = byteSize;
+            readback.bpl = bpl;
+            readback.pixelSize = pixelSize;
+            readback.format = format;
+
+            activeTextureReadbacks.append(readback);
+        } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
+            Q_ASSERT(u.dst->flags().testFlag(QRhiTexture::UsedWithGenerateMips));
+            QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+            cmd.cmd = QD3D11CommandBuffer::Command::GenMip;
+            cmd.args.genMip.srv = QRHI_RES(QD3D11Texture, u.dst)->srv;
+        }
+    }
+
+    ud->free();
+}
+
+void QRhiD3D11::finishActiveReadbacks()
+{
+    QVarLengthArray<std::function<void()>, 4> completedCallbacks;
+
+    for (int i = activeTextureReadbacks.count() - 1; i >= 0; --i) {
+        const QRhiD3D11::TextureReadback &readback(activeTextureReadbacks[i]);
+        readback.result->format = readback.format;
+        readback.result->pixelSize = readback.pixelSize;
+
+        D3D11_MAPPED_SUBRESOURCE mp;
+        HRESULT hr = context->Map(readback.stagingTex, 0, D3D11_MAP_READ, 0, &mp);
+        if (SUCCEEDED(hr)) {
+            readback.result->data.resize(int(readback.byteSize));
+            // nothing says the rows are tightly packed in the texture, must take
+            // the stride into account
+            char *dst = readback.result->data.data();
+            char *src = static_cast<char *>(mp.pData);
+            for (int y = 0, h = readback.pixelSize.height(); y != h; ++y) {
+                memcpy(dst, src, readback.bpl);
+                dst += readback.bpl;
+                src += mp.RowPitch;
+            }
+            context->Unmap(readback.stagingTex, 0);
+        } else {
+            qWarning("Failed to map readback staging texture: %s",
+                qPrintable(QSystemError::windowsComString(hr)));
+        }
+
+        readback.stagingTex->Release();
+
+        if (readback.result->completed)
+            completedCallbacks.append(readback.result->completed);
+
+        activeTextureReadbacks.removeLast();
+    }
+
+    for (int i = activeBufferReadbacks.count() - 1; i >= 0; --i) {
+        const QRhiD3D11::BufferReadback &readback(activeBufferReadbacks[i]);
+
+        D3D11_MAPPED_SUBRESOURCE mp;
+        HRESULT hr = context->Map(readback.stagingBuf, 0, D3D11_MAP_READ, 0, &mp);
+        if (SUCCEEDED(hr)) {
+            readback.result->data.resize(int(readback.byteSize));
+            memcpy(readback.result->data.data(), mp.pData, readback.byteSize);
+            context->Unmap(readback.stagingBuf, 0);
+        } else {
+            qWarning("Failed to map readback staging texture: %s",
+                qPrintable(QSystemError::windowsComString(hr)));
+        }
+
+        readback.stagingBuf->Release();
+
+        if (readback.result->completed)
+            completedCallbacks.append(readback.result->completed);
+
+        activeBufferReadbacks.removeLast();
+    }
+
+    for (auto f : completedCallbacks)
+        f();
+}
+
+void QRhiD3D11::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+    Q_ASSERT(QRHI_RES(QD3D11CommandBuffer, cb)->recordingPass == QD3D11CommandBuffer::NoPass);
+
+    enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiD3D11::beginPass(QRhiCommandBuffer *cb,
+                          QRhiRenderTarget *rt,
+                          const QColor &colorClearValue,
+                          const QRhiDepthStencilClearValue &depthStencilClearValue,
+                          QRhiResourceUpdateBatch *resourceUpdates,
+                          QRhiCommandBuffer::BeginPassFlags)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::NoPass);
+
+    if (resourceUpdates)
+        enqueueResourceUpdates(cb, resourceUpdates);
+
+    bool wantsColorClear = true;
+    bool wantsDsClear = true;
+    QD3D11RenderTargetData *rtD = rtData(rt);
+    if (rt->resourceType() == QRhiRenderTarget::TextureRenderTarget) {
+        QD3D11TextureRenderTarget *rtTex = QRHI_RES(QD3D11TextureRenderTarget, rt);
+        wantsColorClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
+        wantsDsClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
+        if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QD3D11Texture, QD3D11RenderBuffer>(rtTex->description(), rtD->currentResIdList))
+            rtTex->create();
+    }
+
+    cbD->commands.get().cmd = QD3D11CommandBuffer::Command::ResetShaderResources;
+
+    QD3D11CommandBuffer::Command &fbCmd(cbD->commands.get());
+    fbCmd.cmd = QD3D11CommandBuffer::Command::SetRenderTarget;
+    fbCmd.args.setRenderTarget.rt = rt;
+
+    QD3D11CommandBuffer::Command &clearCmd(cbD->commands.get());
+    clearCmd.cmd = QD3D11CommandBuffer::Command::Clear;
+    clearCmd.args.clear.rt = rt;
+    clearCmd.args.clear.mask = 0;
+    if (rtD->colorAttCount && wantsColorClear)
+        clearCmd.args.clear.mask |= QD3D11CommandBuffer::Command::Color;
+    if (rtD->dsAttCount && wantsDsClear)
+        clearCmd.args.clear.mask |= QD3D11CommandBuffer::Command::Depth | QD3D11CommandBuffer::Command::Stencil;
+
+    clearCmd.args.clear.c[0] = float(colorClearValue.redF());
+    clearCmd.args.clear.c[1] = float(colorClearValue.greenF());
+    clearCmd.args.clear.c[2] = float(colorClearValue.blueF());
+    clearCmd.args.clear.c[3] = float(colorClearValue.alphaF());
+    clearCmd.args.clear.d = depthStencilClearValue.depthClearValue();
+    clearCmd.args.clear.s = depthStencilClearValue.stencilClearValue();
+
+    cbD->recordingPass = QD3D11CommandBuffer::RenderPass;
+    cbD->currentTarget = rt;
+
+    cbD->resetCachedState();
+}
+
+void QRhiD3D11::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::RenderPass);
+
+    if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) {
+        QD3D11TextureRenderTarget *rtTex = QRHI_RES(QD3D11TextureRenderTarget, cbD->currentTarget);
+        for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
+             it != itEnd; ++it)
+        {
+            const QRhiColorAttachment &colorAtt(*it);
+            if (!colorAtt.resolveTexture())
+                continue;
+
+            QD3D11Texture *dstTexD = QRHI_RES(QD3D11Texture, colorAtt.resolveTexture());
+            QD3D11Texture *srcTexD = QRHI_RES(QD3D11Texture, colorAtt.texture());
+            QD3D11RenderBuffer *srcRbD = QRHI_RES(QD3D11RenderBuffer, colorAtt.renderBuffer());
+            Q_ASSERT(srcTexD || srcRbD);
+            QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+            cmd.cmd = QD3D11CommandBuffer::Command::ResolveSubRes;
+            cmd.args.resolveSubRes.dst = dstTexD->textureResource();
+            cmd.args.resolveSubRes.dstSubRes = D3D11CalcSubresource(UINT(colorAtt.resolveLevel()),
+                                                                    UINT(colorAtt.resolveLayer()),
+                                                                    dstTexD->mipLevelCount);
+            if (srcTexD) {
+                cmd.args.resolveSubRes.src = srcTexD->textureResource();
+                if (srcTexD->dxgiFormat != dstTexD->dxgiFormat) {
+                    qWarning("Resolve source (%d) and destination (%d) formats do not match",
+                             int(srcTexD->dxgiFormat), int(dstTexD->dxgiFormat));
+                    cbD->commands.unget();
+                    continue;
+                }
+                if (srcTexD->sampleDesc.Count <= 1) {
+                    qWarning("Cannot resolve a non-multisample texture");
+                    cbD->commands.unget();
+                    continue;
+                }
+                if (srcTexD->m_pixelSize != dstTexD->m_pixelSize) {
+                    qWarning("Resolve source and destination sizes do not match");
+                    cbD->commands.unget();
+                    continue;
+                }
+            } else {
+                cmd.args.resolveSubRes.src = srcRbD->tex;
+                if (srcRbD->dxgiFormat != dstTexD->dxgiFormat) {
+                    qWarning("Resolve source (%d) and destination (%d) formats do not match",
+                             int(srcRbD->dxgiFormat), int(dstTexD->dxgiFormat));
+                    cbD->commands.unget();
+                    continue;
+                }
+                if (srcRbD->m_pixelSize != dstTexD->m_pixelSize) {
+                    qWarning("Resolve source and destination sizes do not match");
+                    cbD->commands.unget();
+                    continue;
+                }
+            }
+            cmd.args.resolveSubRes.srcSubRes = D3D11CalcSubresource(0, UINT(colorAtt.layer()), 1);
+            cmd.args.resolveSubRes.format = dstTexD->dxgiFormat;
+        }
+    }
+
+    cbD->recordingPass = QD3D11CommandBuffer::NoPass;
+    cbD->currentTarget = nullptr;
+
+    if (resourceUpdates)
+        enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiD3D11::beginComputePass(QRhiCommandBuffer *cb,
+                                 QRhiResourceUpdateBatch *resourceUpdates,
+                                 QRhiCommandBuffer::BeginPassFlags)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::NoPass);
+
+    if (resourceUpdates)
+        enqueueResourceUpdates(cb, resourceUpdates);
+
+    QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::ResetShaderResources;
+
+    cbD->recordingPass = QD3D11CommandBuffer::ComputePass;
+
+    cbD->resetCachedState();
+}
+
+void QRhiD3D11::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::ComputePass);
+
+    cbD->recordingPass = QD3D11CommandBuffer::NoPass;
+
+    if (resourceUpdates)
+        enqueueResourceUpdates(cb, resourceUpdates);
+}
+
+void QRhiD3D11::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::ComputePass);
+    QD3D11ComputePipeline *psD = QRHI_RES(QD3D11ComputePipeline, ps);
+    const bool pipelineChanged = cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation;
+
+    if (pipelineChanged) {
+        cbD->currentGraphicsPipeline = nullptr;
+        cbD->currentComputePipeline = psD;
+        cbD->currentPipelineGeneration = psD->generation;
+
+        QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+        cmd.cmd = QD3D11CommandBuffer::Command::BindComputePipeline;
+        cmd.args.bindComputePipeline.ps = psD;
+    }
+}
+
+void QRhiD3D11::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
+{
+    QD3D11CommandBuffer *cbD = QRHI_RES(QD3D11CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D11CommandBuffer::ComputePass);
+
+    QD3D11CommandBuffer::Command &cmd(cbD->commands.get());
+    cmd.cmd = QD3D11CommandBuffer::Command::Dispatch;
+    cmd.args.dispatch.x = UINT(x);
+    cmd.args.dispatch.y = UINT(y);
+    cmd.args.dispatch.z = UINT(z);
+}
+
+static inline QPair<int, int> mapBinding(int binding,
+                                         int stageIndex,
+                                         const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[])
+{
+    const QShader::NativeResourceBindingMap *map = nativeResourceBindingMaps[stageIndex];
+    if (!map || map->isEmpty())
+        return { binding, binding }; // assume 1:1 mapping
+
+    auto it = map->constFind(binding);
+    if (it != map->cend())
+        return *it;
+
+    // Hitting this path is normal too. It is not given that the resource is
+    // present in the shaders for all the stages specified by the visibility
+    // mask in the QRhiShaderResourceBinding.
+    return { -1, -1 };
+}
+
+void QRhiD3D11::updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD,
+                                             const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[])
+{
+    srbD->vsUniformBufferBatches.clear();
+    srbD->hsUniformBufferBatches.clear();
+    srbD->dsUniformBufferBatches.clear();
+    srbD->gsUniformBufferBatches.clear();
+    srbD->fsUniformBufferBatches.clear();
+    srbD->csUniformBufferBatches.clear();
+
+    srbD->vsSamplerBatches.clear();
+    srbD->hsSamplerBatches.clear();
+    srbD->dsSamplerBatches.clear();
+    srbD->gsSamplerBatches.clear();
+    srbD->fsSamplerBatches.clear();
+    srbD->csSamplerBatches.clear();
+
+    srbD->csUavBatches.clear();
+
+    struct Stage {
+        struct Buffer {
+            int binding; // stored and sent along in XXorigbindings just for applyDynamicOffsets()
+            int breg; // b0, b1, ...
+            ID3D11Buffer *buffer;
+            uint offsetInConstants;
+            uint sizeInConstants;
+        };
+        struct Texture {
+            int treg; // t0, t1, ...
+            ID3D11ShaderResourceView *srv;
+        };
+        struct Sampler {
+            int sreg; // s0, s1, ...
+            ID3D11SamplerState *sampler;
+        };
+        struct Uav {
+            int ureg;
+            ID3D11UnorderedAccessView *uav;
+        };
+        QVarLengthArray<Buffer, 8> buffers;
+        QVarLengthArray<Texture, 8> textures;
+        QVarLengthArray<Sampler, 8> samplers;
+        QVarLengthArray<Uav, 8> uavs;
+        void buildBufferBatches(QD3D11ShaderResourceBindings::StageUniformBufferBatches &batches) const
+        {
+            for (const Buffer &buf : buffers) {
+                batches.ubufs.feed(buf.breg, buf.buffer);
+                batches.ubuforigbindings.feed(buf.breg, UINT(buf.binding));
+                batches.ubufoffsets.feed(buf.breg, buf.offsetInConstants);
+                batches.ubufsizes.feed(buf.breg, buf.sizeInConstants);
+            }
+            batches.finish();
+        }
+        void buildSamplerBatches(QD3D11ShaderResourceBindings::StageSamplerBatches &batches) const
+        {
+            for (const Texture &t : textures)
+                batches.shaderresources.feed(t.treg, t.srv);
+            for (const Sampler &s : samplers)
+                batches.samplers.feed(s.sreg, s.sampler);
+            batches.finish();
+        }
+        void buildUavBatches(QD3D11ShaderResourceBindings::StageUavBatches &batches) const
+        {
+            for (const Stage::Uav &u : uavs)
+                batches.uavs.feed(u.ureg, u.uav);
+            batches.finish();
+        }
+    } res[RBM_SUPPORTED_STAGES];
+
+    for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) {
+        const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings.at(i));
+        QD3D11ShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
+        switch (b->type) {
+        case QRhiShaderResourceBinding::UniformBuffer:
+        {
+            QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, b->u.ubuf.buf);
+            Q_ASSERT(aligned(b->u.ubuf.offset, 256u) == b->u.ubuf.offset);
+            bd.ubuf.id = bufD->m_id;
+            bd.ubuf.generation = bufD->generation;
+            // Dynamic ubuf offsets are not considered here, those are baked in
+            // at a later stage, which is good as vsubufoffsets and friends are
+            // per-srb, not per-setShaderResources call. Other backends (GL,
+            // Metal) are different in this respect since those do not store
+            // per-srb vsubufoffsets etc. data so life's a bit easier for them.
+            // But here we have to defer baking in the dynamic offset.
+            const quint32 offsetInConstants = b->u.ubuf.offset / 16;
+            // size must be 16 mult. (in constants, i.e. multiple of 256 bytes).
+            // We can round up if needed since the buffers's actual size
+            // (ByteWidth) is always a multiple of 256.
+            const quint32 sizeInConstants = aligned(b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size, 256u) / 16;
+            if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+                QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_VERTEX, nativeResourceBindingMaps);
+                if (nativeBinding.first >= 0)
+                    res[RBM_VERTEX].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants });
+            }
+            if (b->stage.testFlag(QRhiShaderResourceBinding::TessellationControlStage)) {
+                QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_HULL, nativeResourceBindingMaps);
+                if (nativeBinding.first >= 0)
+                    res[RBM_HULL].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants });
+            }
+            if (b->stage.testFlag(QRhiShaderResourceBinding::TessellationEvaluationStage)) {
+                QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_DOMAIN, nativeResourceBindingMaps);
+                if (nativeBinding.first >= 0)
+                    res[RBM_DOMAIN].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants });
+            }
+            if (b->stage.testFlag(QRhiShaderResourceBinding::GeometryStage)) {
+                QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_GEOMETRY, nativeResourceBindingMaps);
+                if (nativeBinding.first >= 0)
+                    res[RBM_GEOMETRY].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants });
+            }
+            if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+                QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_FRAGMENT, nativeResourceBindingMaps);
+                if (nativeBinding.first >= 0)
+                    res[RBM_FRAGMENT].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants });
+            }
+            if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+                QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps);
+                if (nativeBinding.first >= 0)
+                    res[RBM_COMPUTE].buffers.append({ b->binding, nativeBinding.first, bufD->buffer, offsetInConstants, sizeInConstants });
+            }
+        }
+            break;
+        case QRhiShaderResourceBinding::SampledTexture:
+        case QRhiShaderResourceBinding::Texture:
+        case QRhiShaderResourceBinding::Sampler:
+        {
+            const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
+            bd.stex.count = data->count;
+            const QPair<int, int> nativeBindingVert = mapBinding(b->binding, RBM_VERTEX, nativeResourceBindingMaps);
+            const QPair<int, int> nativeBindingHull = mapBinding(b->binding, RBM_HULL, nativeResourceBindingMaps);
+            const QPair<int, int> nativeBindingDomain = mapBinding(b->binding, RBM_DOMAIN, nativeResourceBindingMaps);
+            const QPair<int, int> nativeBindingGeom = mapBinding(b->binding, RBM_GEOMETRY, nativeResourceBindingMaps);
+            const QPair<int, int> nativeBindingFrag = mapBinding(b->binding, RBM_FRAGMENT, nativeResourceBindingMaps);
+            const QPair<int, int> nativeBindingComp = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps);
+            // if SPIR-V binding b is mapped to tN and sN in HLSL, and it
+            // is an array, then it will use tN, tN+1, tN+2, ..., and sN,
+            // sN+1, sN+2, ...
+            for (int elem = 0; elem < data->count; ++elem) {
+                QD3D11Texture *texD = QRHI_RES(QD3D11Texture, data->texSamplers[elem].tex);
+                QD3D11Sampler *samplerD = QRHI_RES(QD3D11Sampler, data->texSamplers[elem].sampler);
+                bd.stex.d[elem].texId = texD ? texD->m_id : 0;
+                bd.stex.d[elem].texGeneration = texD ? texD->generation : 0;
+                bd.stex.d[elem].samplerId = samplerD ? samplerD->m_id : 0;
+                bd.stex.d[elem].samplerGeneration = samplerD ? samplerD->generation : 0;
+                // Must handle all three cases (combined, separate, separate):
+                //   first = texture binding, second = sampler binding
+                //   first = texture binding
+                //   first = sampler binding
+                if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) {
+                    const int samplerBinding = texD && samplerD ? nativeBindingVert.second
+                                                                : (samplerD ? nativeBindingVert.first : -1);
+                    if (nativeBindingVert.first >= 0 && texD)
+                        res[RBM_VERTEX].textures.append({ nativeBindingVert.first + elem, texD->srv });
+                    if (samplerBinding >= 0)
+                        res[RBM_VERTEX].samplers.append({ samplerBinding + elem, samplerD->samplerState });
+                }
+                if (b->stage.testFlag(QRhiShaderResourceBinding::TessellationControlStage)) {
+                    const int samplerBinding = texD && samplerD ? nativeBindingHull.second
+                                                                : (samplerD ? nativeBindingHull.first : -1);
+                    if (nativeBindingHull.first >= 0 && texD)
+                        res[RBM_HULL].textures.append({ nativeBindingHull.first + elem, texD->srv });
+                    if (samplerBinding >= 0)
+                        res[RBM_HULL].samplers.append({ samplerBinding + elem, samplerD->samplerState });
+                }
+                if (b->stage.testFlag(QRhiShaderResourceBinding::TessellationEvaluationStage)) {
+                    const int samplerBinding = texD && samplerD ? nativeBindingDomain.second
+                                                                : (samplerD ? nativeBindingDomain.first : -1);
+                    if (nativeBindingDomain.first >= 0 && texD)
+                        res[RBM_DOMAIN].textures.append({ nativeBindingDomain.first + elem, texD->srv });
+                    if (samplerBinding >= 0)
+                        res[RBM_DOMAIN].samplers.append({ samplerBinding + elem, samplerD->samplerState });
+                }
+                if (b->stage.testFlag(QRhiShaderResourceBinding::GeometryStage)) {
+                    const int samplerBinding = texD && samplerD ? nativeBindingGeom.second
+                                                                : (samplerD ? nativeBindingGeom.first : -1);
+                    if (nativeBindingGeom.first >= 0 && texD)
+                        res[RBM_GEOMETRY].textures.append({ nativeBindingGeom.first + elem, texD->srv });
+                    if (samplerBinding >= 0)
+                        res[RBM_GEOMETRY].samplers.append({ samplerBinding + elem, samplerD->samplerState });
+                }
+                if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+                    const int samplerBinding = texD && samplerD ? nativeBindingFrag.second
+                                                                : (samplerD ? nativeBindingFrag.first : -1);
+                    if (nativeBindingFrag.first >= 0 && texD)
+                        res[RBM_FRAGMENT].textures.append({ nativeBindingFrag.first + elem, texD->srv });
+                    if (samplerBinding >= 0)
+                        res[RBM_FRAGMENT].samplers.append({ samplerBinding + elem, samplerD->samplerState });
+                }
+                if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+                    const int samplerBinding = texD && samplerD ? nativeBindingComp.second
+                                                                : (samplerD ? nativeBindingComp.first : -1);
+                    if (nativeBindingComp.first >= 0 && texD)
+                        res[RBM_COMPUTE].textures.append({ nativeBindingComp.first + elem, texD->srv });
+                    if (samplerBinding >= 0)
+                        res[RBM_COMPUTE].samplers.append({ samplerBinding + elem, samplerD->samplerState });
+                }
+            }
+        }
+            break;
+        case QRhiShaderResourceBinding::ImageLoad:
+        case QRhiShaderResourceBinding::ImageStore:
+        case QRhiShaderResourceBinding::ImageLoadStore:
+        {
+            QD3D11Texture *texD = QRHI_RES(QD3D11Texture, b->u.simage.tex);
+            bd.simage.id = texD->m_id;
+            bd.simage.generation = texD->generation;
+            if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+                QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps);
+                if (nativeBinding.first >= 0) {
+                    ID3D11UnorderedAccessView *uav = texD->unorderedAccessViewForLevel(b->u.simage.level);
+                    if (uav)
+                        res[RBM_COMPUTE].uavs.append({ nativeBinding.first, uav });
+                }
+            } else {
+                qWarning("Unordered access only supported at compute stage");
+            }
+        }
+            break;
+        case QRhiShaderResourceBinding::BufferLoad:
+        case QRhiShaderResourceBinding::BufferStore:
+        case QRhiShaderResourceBinding::BufferLoadStore:
+        {
+            QD3D11Buffer *bufD = QRHI_RES(QD3D11Buffer, b->u.sbuf.buf);
+            bd.sbuf.id = bufD->m_id;
+            bd.sbuf.generation = bufD->generation;
+            if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) {
+                QPair<int, int> nativeBinding = mapBinding(b->binding, RBM_COMPUTE, nativeResourceBindingMaps);
+                if (nativeBinding.first >= 0) {
+                    ID3D11UnorderedAccessView *uav = bufD->unorderedAccessView(b->u.sbuf.offset);
+                    if (uav)
+                        res[RBM_COMPUTE].uavs.append({ nativeBinding.first, uav });
+                }
+            } else {
+                qWarning("Unordered access only supported at compute stage");
+            }
+        }
+            break;
+        default:
+            Q_UNREACHABLE();
+            break;
+        }
+    }
+
+    // QRhiBatchedBindings works with the native bindings and expects
+    // sorted input. The pre-sorted QRhiShaderResourceBinding list (based
+    // on the QRhi (SPIR-V) binding) is not helpful in this regard, so we
+    // have to sort here every time.
+    for (int stage = 0; stage < RBM_SUPPORTED_STAGES; ++stage) {
+        std::sort(res[stage].buffers.begin(), res[stage].buffers.end(), [](const Stage::Buffer &a, const Stage::Buffer &b) {
+            return a.breg < b.breg;
+        });
+        std::sort(res[stage].textures.begin(), res[stage].textures.end(), [](const Stage::Texture &a, const Stage::Texture &b) {
+            return a.treg < b.treg;
+        });
+        std::sort(res[stage].samplers.begin(), res[stage].samplers.end(), [](const Stage::Sampler &a, const Stage::Sampler &b) {
+            return a.sreg < b.sreg;
+        });
+        std::sort(res[stage].uavs.begin(), res[stage].uavs.end(), [](const Stage::Uav &a, const Stage::Uav &b) {
+            return a.ureg < b.ureg;
+        });
+    }
+
+    res[RBM_VERTEX].buildBufferBatches(srbD->vsUniformBufferBatches);
+    res[RBM_HULL].buildBufferBatches(srbD->hsUniformBufferBatches);
+    res[RBM_DOMAIN].buildBufferBatches(srbD->dsUniformBufferBatches);
+    res[RBM_GEOMETRY].buildBufferBatches(srbD->gsUniformBufferBatches);
+    res[RBM_FRAGMENT].buildBufferBatches(srbD->fsUniformBufferBatches);
+    res[RBM_COMPUTE].buildBufferBatches(srbD->csUniformBufferBatches);
+
+    res[RBM_VERTEX].buildSamplerBatches(srbD->vsSamplerBatches);
+    res[RBM_HULL].buildSamplerBatches(srbD->hsSamplerBatches);
+    res[RBM_DOMAIN].buildSamplerBatches(srbD->dsSamplerBatches);
+    res[RBM_GEOMETRY].buildSamplerBatches(srbD->gsSamplerBatches);
+    res[RBM_FRAGMENT].buildSamplerBatches(srbD->fsSamplerBatches);
+    res[RBM_COMPUTE].buildSamplerBatches(srbD->csSamplerBatches);
+
+    res[RBM_COMPUTE].buildUavBatches(srbD->csUavBatches);
+}
+
+void QRhiD3D11::executeBufferHostWrites(QD3D11Buffer *bufD)
+{
+    if (!bufD->hasPendingDynamicUpdates || bufD->m_size < 1)
+        return;
+
+    Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+    bufD->hasPendingDynamicUpdates = false;
+    D3D11_MAPPED_SUBRESOURCE mp;
+    HRESULT hr = context->Map(bufD->buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mp);
+    if (SUCCEEDED(hr)) {
+        memcpy(mp.pData, bufD->dynBuf, bufD->m_size);
+        context->Unmap(bufD->buffer, 0);
+    } else {
+        qWarning("Failed to map buffer: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+    }
+}
+
+static void applyDynamicOffsets(UINT *offsets,
+                                int batchIndex,
+                                const QRhiBatchedBindings<UINT> *originalBindings,
+                                const QRhiBatchedBindings<UINT> *staticOffsets,
+                                const uint *dynOfsPairs, int dynOfsPairCount)
+{
+    const int count = staticOffsets->batches[batchIndex].resources.count();
+    // Make a copy of the offset list, the entries that have no corresponding
+    // dynamic offset will continue to use the existing offset value.
+    for (int b = 0; b < count; ++b) {
+        offsets[b] = staticOffsets->batches[batchIndex].resources[b];
+        for (int di = 0; di < dynOfsPairCount; ++di) {
+            const uint binding = dynOfsPairs[2 * di];
+            // binding is the SPIR-V style binding point here, nothing to do
+            // with the native one.
+            if (binding == originalBindings->batches[batchIndex].resources[b]) {
+                const uint offsetInConstants = dynOfsPairs[2 * di + 1];
+                offsets[b] = offsetInConstants;
+                break;
+            }
+        }
+    }
+}
+
+static inline uint clampedResourceCount(uint startSlot, int countSlots, uint maxSlots, const char *resType)
+{
+    if (startSlot + countSlots > maxSlots) {
+        qWarning("Not enough D3D11 %s slots to bind %d resources starting at slot %d, max slots is %d",
+                 resType, countSlots, startSlot, maxSlots);
+        countSlots = maxSlots > startSlot ? maxSlots - startSlot : 0;
+    }
+    return countSlots;
+}
+
+#define SETUBUFBATCH(stagePrefixL, stagePrefixU) \
+    if (srbD->stagePrefixL##UniformBufferBatches.present) { \
+        const QD3D11ShaderResourceBindings::StageUniformBufferBatches &batches(srbD->stagePrefixL##UniformBufferBatches); \
+        for (int i = 0, ie = batches.ubufs.batches.count(); i != ie; ++i) { \
+            const uint count = clampedResourceCount(batches.ubufs.batches[i].startBinding, \
+                                                    batches.ubufs.batches[i].resources.count(), \
+                                                    D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT, \
+                                                    #stagePrefixU " cbuf"); \
+            if (count) { \
+                if (!dynOfsPairCount) { \
+                    context->stagePrefixU##SetConstantBuffers1(batches.ubufs.batches[i].startBinding, \
+                                                   count, \
+                                                   batches.ubufs.batches[i].resources.constData(), \
+                                                   batches.ubufoffsets.batches[i].resources.constData(), \
+                                                   batches.ubufsizes.batches[i].resources.constData()); \
+                } else { \
+                    applyDynamicOffsets(offsets, i, \
+                                        &batches.ubuforigbindings, &batches.ubufoffsets, \
+                                        dynOfsPairs, dynOfsPairCount); \
+                    context->stagePrefixU##SetConstantBuffers1(batches.ubufs.batches[i].startBinding, \
+                                                   count, \
+                                                   batches.ubufs.batches[i].resources.constData(), \
+                                                   offsets, \
+                                                   batches.ubufsizes.batches[i].resources.constData()); \
+                } \
+            } \
+        } \
+    }
+
+#define SETSAMPLERBATCH(stagePrefixL, stagePrefixU) \
+    if (srbD->stagePrefixL##SamplerBatches.present) { \
+        for (const auto &batch : srbD->stagePrefixL##SamplerBatches.samplers.batches) { \
+            const uint count = clampedResourceCount(batch.startBinding, batch.resources.count(), \
+                                                    D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT, #stagePrefixU " sampler"); \
+            if (count) \
+                context->stagePrefixU##SetSamplers(batch.startBinding, count, batch.resources.constData()); \
+        } \
+        for (const auto &batch : srbD->stagePrefixL##SamplerBatches.shaderresources.batches) { \
+            const uint count = clampedResourceCount(batch.startBinding, batch.resources.count(), \
+                                                    D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT, #stagePrefixU " SRV"); \
+            if (count) { \
+                context->stagePrefixU##SetShaderResources(batch.startBinding, count, batch.resources.constData()); \
+                contextState.stagePrefixL##HighestActiveSrvBinding = qMax(contextState.stagePrefixL##HighestActiveSrvBinding, \
+                                                              int(batch.startBinding + count) - 1); \
+            } \
+        } \
+    }
+
+#define SETUAVBATCH(stagePrefixL, stagePrefixU) \
+    if (srbD->stagePrefixL##UavBatches.present) { \
+        for (const auto &batch : srbD->stagePrefixL##UavBatches.uavs.batches) { \
+            const uint count = clampedResourceCount(batch.startBinding, batch.resources.count(), \
+                                                    D3D11_1_UAV_SLOT_COUNT, #stagePrefixU " UAV"); \
+            if (count) { \
+                context->stagePrefixU##SetUnorderedAccessViews(batch.startBinding, \
+                                                   count, \
+                                                   batch.resources.constData(), \
+                                                   nullptr); \
+                contextState.stagePrefixL##HighestActiveUavBinding = qMax(contextState.stagePrefixL##HighestActiveUavBinding, \
+                                                              int(batch.startBinding + count) - 1); \
+            } \
+        } \
+    }
+
+void QRhiD3D11::bindShaderResources(QD3D11ShaderResourceBindings *srbD,
+                                    const uint *dynOfsPairs, int dynOfsPairCount,
+                                    bool offsetOnlyChange)
+{
+    UINT offsets[QD3D11CommandBuffer::MAX_DYNAMIC_OFFSET_COUNT];
+
+    SETUBUFBATCH(vs, VS)
+    SETUBUFBATCH(hs, HS)
+    SETUBUFBATCH(ds, DS)
+    SETUBUFBATCH(gs, GS)
+    SETUBUFBATCH(fs, PS)
+    SETUBUFBATCH(cs, CS)
+
+    if (!offsetOnlyChange) {
+        SETSAMPLERBATCH(vs, VS)
+        SETSAMPLERBATCH(hs, HS)
+        SETSAMPLERBATCH(ds, DS)
+        SETSAMPLERBATCH(gs, GS)
+        SETSAMPLERBATCH(fs, PS)
+        SETSAMPLERBATCH(cs, CS)
+
+        SETUAVBATCH(cs, CS)
+    }
+}
+
+void QRhiD3D11::resetShaderResources()
+{
+    // Output cannot be bound on input etc.
+
+    if (contextState.vsHasIndexBufferBound) {
+        context->IASetIndexBuffer(nullptr, DXGI_FORMAT_R16_UINT, 0);
+        contextState.vsHasIndexBufferBound = false;
+    }
+
+    if (contextState.vsHighestActiveVertexBufferBinding >= 0) {
+        const int count = contextState.vsHighestActiveVertexBufferBinding + 1;
+        QVarLengthArray<ID3D11Buffer *, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> nullbufs(count);
+        for (int i = 0; i < count; ++i)
+            nullbufs[i] = nullptr;
+        QVarLengthArray<UINT, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> nullstrides(count);
+        for (int i = 0; i < count; ++i)
+            nullstrides[i] = 0;
+        QVarLengthArray<UINT, D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT> nulloffsets(count);
+        for (int i = 0; i < count; ++i)
+            nulloffsets[i] = 0;
+        context->IASetVertexBuffers(0, UINT(count), nullbufs.constData(), nullstrides.constData(), nulloffsets.constData());
+        contextState.vsHighestActiveVertexBufferBinding = -1;
+    }
+
+    int nullsrvCount = qMax(contextState.vsHighestActiveSrvBinding, contextState.fsHighestActiveSrvBinding);
+    nullsrvCount = qMax(nullsrvCount, contextState.hsHighestActiveSrvBinding);
+    nullsrvCount = qMax(nullsrvCount, contextState.dsHighestActiveSrvBinding);
+    nullsrvCount = qMax(nullsrvCount, contextState.gsHighestActiveSrvBinding);
+    nullsrvCount = qMax(nullsrvCount, contextState.csHighestActiveSrvBinding);
+    nullsrvCount += 1;
+    if (nullsrvCount > 0) {
+        QVarLengthArray<ID3D11ShaderResourceView *,
+                D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT> nullsrvs(nullsrvCount);
+        for (int i = 0; i < nullsrvs.count(); ++i)
+            nullsrvs[i] = nullptr;
+        if (contextState.vsHighestActiveSrvBinding >= 0) {
+            context->VSSetShaderResources(0, UINT(contextState.vsHighestActiveSrvBinding + 1), nullsrvs.constData());
+            contextState.vsHighestActiveSrvBinding = -1;
+        }
+        if (contextState.hsHighestActiveSrvBinding >= 0) {
+            context->HSSetShaderResources(0, UINT(contextState.hsHighestActiveSrvBinding + 1), nullsrvs.constData());
+            contextState.hsHighestActiveSrvBinding = -1;
+        }
+        if (contextState.dsHighestActiveSrvBinding >= 0) {
+            context->DSSetShaderResources(0, UINT(contextState.dsHighestActiveSrvBinding + 1), nullsrvs.constData());
+            contextState.dsHighestActiveSrvBinding = -1;
+        }
+        if (contextState.gsHighestActiveSrvBinding >= 0) {
+            context->GSSetShaderResources(0, UINT(contextState.gsHighestActiveSrvBinding + 1), nullsrvs.constData());
+            contextState.gsHighestActiveSrvBinding = -1;
+        }
+        if (contextState.fsHighestActiveSrvBinding >= 0) {
+            context->PSSetShaderResources(0, UINT(contextState.fsHighestActiveSrvBinding + 1), nullsrvs.constData());
+            contextState.fsHighestActiveSrvBinding = -1;
+        }
+        if (contextState.csHighestActiveSrvBinding >= 0) {
+            context->CSSetShaderResources(0, UINT(contextState.csHighestActiveSrvBinding + 1), nullsrvs.constData());
+            contextState.csHighestActiveSrvBinding = -1;
+        }
+    }
+
+    if (contextState.csHighestActiveUavBinding >= 0) {
+        const int nulluavCount = contextState.csHighestActiveUavBinding + 1;
+        QVarLengthArray<ID3D11UnorderedAccessView *,
+                D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT> nulluavs(nulluavCount);
+        for (int i = 0; i < nulluavCount; ++i)
+            nulluavs[i] = nullptr;
+        context->CSSetUnorderedAccessViews(0, UINT(nulluavCount), nulluavs.constData(), nullptr);
+        contextState.csHighestActiveUavBinding = -1;
+    }
+}
+
+#define SETSHADER(StageL, StageU) \
+    if (psD->StageL.shader) { \
+        context->StageU##SetShader(psD->StageL.shader, nullptr, 0); \
+        currentShaderMask |= StageU##MaskBit; \
+    } else if (currentShaderMask & StageU##MaskBit) { \
+        context->StageU##SetShader(nullptr, nullptr, 0); \
+        currentShaderMask &= ~StageU##MaskBit; \
+    }
+
+void QRhiD3D11::executeCommandBuffer(QD3D11CommandBuffer *cbD)
+{
+    quint32 stencilRef = 0;
+    float blendConstants[] = { 1, 1, 1, 1 };
+    enum ActiveShaderMask {
+        VSMaskBit = 0x01,
+        HSMaskBit = 0x02,
+        DSMaskBit = 0x04,
+        GSMaskBit = 0x08,
+        PSMaskBit = 0x10
+    };
+    int currentShaderMask = 0xFF;
+
+    for (auto it = cbD->commands.cbegin(), end = cbD->commands.cend(); it != end; ++it) {
+        const QD3D11CommandBuffer::Command &cmd(*it);
+        switch (cmd.cmd) {
+        case QD3D11CommandBuffer::Command::BeginFrame:
+            if (cmd.args.beginFrame.tsDisjointQuery)
+                context->Begin(cmd.args.beginFrame.tsDisjointQuery);
+            if (cmd.args.beginFrame.tsQuery) {
+                if (cmd.args.beginFrame.swapchainData) {
+                    // The timestamps seem to include vsync time with Present(1), except
+                    // when running on a non-primary gpu. This is not ideal. So try working
+                    // it around by issuing a semi-fake OMSetRenderTargets early and
+                    // writing the first timestamp only afterwards.
+                    QD3D11RenderTargetData *rtD = cmd.args.beginFrame.swapchainData;
+                    context->OMSetRenderTargets(UINT(rtD->colorAttCount), rtD->colorAttCount ? rtD->rtv : nullptr, rtD->dsv);
+                }
+                context->End(cmd.args.beginFrame.tsQuery); // no Begin() for D3D11_QUERY_TIMESTAMP
+            }
+            break;
+        case QD3D11CommandBuffer::Command::EndFrame:
+            if (cmd.args.endFrame.tsQuery)
+                context->End(cmd.args.endFrame.tsQuery);
+            if (cmd.args.endFrame.tsDisjointQuery)
+                context->End(cmd.args.endFrame.tsDisjointQuery);
+            break;
+        case QD3D11CommandBuffer::Command::ResetShaderResources:
+            resetShaderResources();
+            break;
+        case QD3D11CommandBuffer::Command::SetRenderTarget:
+        {
+            QD3D11RenderTargetData *rtD = rtData(cmd.args.setRenderTarget.rt);
+            context->OMSetRenderTargets(UINT(rtD->colorAttCount), rtD->colorAttCount ? rtD->rtv : nullptr, rtD->dsv);
+        }
+            break;
+        case QD3D11CommandBuffer::Command::Clear:
+        {
+            QD3D11RenderTargetData *rtD = rtData(cmd.args.clear.rt);
+            if (cmd.args.clear.mask & QD3D11CommandBuffer::Command::Color) {
+                for (int i = 0; i < rtD->colorAttCount; ++i)
+                    context->ClearRenderTargetView(rtD->rtv[i], cmd.args.clear.c);
+            }
+            uint ds = 0;
+            if (cmd.args.clear.mask & QD3D11CommandBuffer::Command::Depth)
+                ds |= D3D11_CLEAR_DEPTH;
+            if (cmd.args.clear.mask & QD3D11CommandBuffer::Command::Stencil)
+                ds |= D3D11_CLEAR_STENCIL;
+            if (ds)
+                context->ClearDepthStencilView(rtD->dsv, ds, cmd.args.clear.d, UINT8(cmd.args.clear.s));
+        }
+            break;
+        case QD3D11CommandBuffer::Command::Viewport:
+        {
+            D3D11_VIEWPORT v;
+            v.TopLeftX = cmd.args.viewport.x;
+            v.TopLeftY = cmd.args.viewport.y;
+            v.Width = cmd.args.viewport.w;
+            v.Height = cmd.args.viewport.h;
+            v.MinDepth = cmd.args.viewport.d0;
+            v.MaxDepth = cmd.args.viewport.d1;
+            context->RSSetViewports(1, &v);
+        }
+            break;
+        case QD3D11CommandBuffer::Command::Scissor:
+        {
+            D3D11_RECT r;
+            r.left = cmd.args.scissor.x;
+            r.top = cmd.args.scissor.y;
+            // right and bottom are exclusive
+            r.right = cmd.args.scissor.x + cmd.args.scissor.w;
+            r.bottom = cmd.args.scissor.y + cmd.args.scissor.h;
+            context->RSSetScissorRects(1, &r);
+        }
+            break;
+        case QD3D11CommandBuffer::Command::BindVertexBuffers:
+            contextState.vsHighestActiveVertexBufferBinding = qMax<int>(
+                        contextState.vsHighestActiveVertexBufferBinding,
+                        cmd.args.bindVertexBuffers.startSlot + cmd.args.bindVertexBuffers.slotCount - 1);
+            context->IASetVertexBuffers(UINT(cmd.args.bindVertexBuffers.startSlot),
+                                        UINT(cmd.args.bindVertexBuffers.slotCount),
+                                        cmd.args.bindVertexBuffers.buffers,
+                                        cmd.args.bindVertexBuffers.strides,
+                                        cmd.args.bindVertexBuffers.offsets);
+            break;
+        case QD3D11CommandBuffer::Command::BindIndexBuffer:
+            contextState.vsHasIndexBufferBound = true;
+            context->IASetIndexBuffer(cmd.args.bindIndexBuffer.buffer,
+                                      cmd.args.bindIndexBuffer.format,
+                                      cmd.args.bindIndexBuffer.offset);
+            break;
+        case QD3D11CommandBuffer::Command::BindGraphicsPipeline:
+        {
+            QD3D11GraphicsPipeline *psD = cmd.args.bindGraphicsPipeline.ps;
+            SETSHADER(vs, VS)
+            SETSHADER(hs, HS)
+            SETSHADER(ds, DS)
+            SETSHADER(gs, GS)
+            SETSHADER(fs, PS)
+            context->IASetPrimitiveTopology(psD->d3dTopology);
+            context->IASetInputLayout(psD->inputLayout); // may be null, that's ok
+            context->OMSetDepthStencilState(psD->dsState, stencilRef);
+            context->OMSetBlendState(psD->blendState, blendConstants, 0xffffffff);
+            context->RSSetState(psD->rastState);
+        }
+            break;
+        case QD3D11CommandBuffer::Command::BindShaderResources:
+            bindShaderResources(cmd.args.bindShaderResources.srb,
+                                cmd.args.bindShaderResources.dynamicOffsetPairs,
+                                cmd.args.bindShaderResources.dynamicOffsetCount,
+                                cmd.args.bindShaderResources.offsetOnlyChange);
+            break;
+        case QD3D11CommandBuffer::Command::StencilRef:
+            stencilRef = cmd.args.stencilRef.ref;
+            context->OMSetDepthStencilState(cmd.args.stencilRef.ps->dsState, stencilRef);
+            break;
+        case QD3D11CommandBuffer::Command::BlendConstants:
+            memcpy(blendConstants, cmd.args.blendConstants.c, 4 * sizeof(float));
+            context->OMSetBlendState(cmd.args.blendConstants.ps->blendState, blendConstants, 0xffffffff);
+            break;
+        case QD3D11CommandBuffer::Command::Draw:
+            if (cmd.args.draw.ps) {
+                if (cmd.args.draw.instanceCount == 1)
+                    context->Draw(cmd.args.draw.vertexCount, cmd.args.draw.firstVertex);
+                else
+                    context->DrawInstanced(cmd.args.draw.vertexCount, cmd.args.draw.instanceCount,
+                                           cmd.args.draw.firstVertex, cmd.args.draw.firstInstance);
+            } else {
+                qWarning("No graphics pipeline active for draw; ignored");
+            }
+            break;
+        case QD3D11CommandBuffer::Command::DrawIndexed:
+            if (cmd.args.drawIndexed.ps) {
+                if (cmd.args.drawIndexed.instanceCount == 1)
+                    context->DrawIndexed(cmd.args.drawIndexed.indexCount, cmd.args.drawIndexed.firstIndex,
+                                         cmd.args.drawIndexed.vertexOffset);
+                else
+                    context->DrawIndexedInstanced(cmd.args.drawIndexed.indexCount, cmd.args.drawIndexed.instanceCount,
+                                                  cmd.args.drawIndexed.firstIndex, cmd.args.drawIndexed.vertexOffset,
+                                                  cmd.args.drawIndexed.firstInstance);
+            } else {
+                qWarning("No graphics pipeline active for drawIndexed; ignored");
+            }
+            break;
+        case QD3D11CommandBuffer::Command::UpdateSubRes:
+            context->UpdateSubresource(cmd.args.updateSubRes.dst, cmd.args.updateSubRes.dstSubRes,
+                                       cmd.args.updateSubRes.hasDstBox ? &cmd.args.updateSubRes.dstBox : nullptr,
+                                       cmd.args.updateSubRes.src, cmd.args.updateSubRes.srcRowPitch, 0);
+            break;
+        case QD3D11CommandBuffer::Command::CopySubRes:
+            context->CopySubresourceRegion(cmd.args.copySubRes.dst, cmd.args.copySubRes.dstSubRes,
+                                           cmd.args.copySubRes.dstX, cmd.args.copySubRes.dstY, cmd.args.copySubRes.dstZ,
+                                           cmd.args.copySubRes.src, cmd.args.copySubRes.srcSubRes,
+                                           cmd.args.copySubRes.hasSrcBox ? &cmd.args.copySubRes.srcBox : nullptr);
+            break;
+        case QD3D11CommandBuffer::Command::ResolveSubRes:
+            context->ResolveSubresource(cmd.args.resolveSubRes.dst, cmd.args.resolveSubRes.dstSubRes,
+                                        cmd.args.resolveSubRes.src, cmd.args.resolveSubRes.srcSubRes,
+                                        cmd.args.resolveSubRes.format);
+            break;
+        case QD3D11CommandBuffer::Command::GenMip:
+            context->GenerateMips(cmd.args.genMip.srv);
+            break;
+        case QD3D11CommandBuffer::Command::DebugMarkBegin:
+            annotations->BeginEvent(reinterpret_cast<LPCWSTR>(QString::fromLatin1(cmd.args.debugMark.s).utf16()));
+            break;
+        case QD3D11CommandBuffer::Command::DebugMarkEnd:
+            annotations->EndEvent();
+            break;
+        case QD3D11CommandBuffer::Command::DebugMarkMsg:
+            annotations->SetMarker(reinterpret_cast<LPCWSTR>(QString::fromLatin1(cmd.args.debugMark.s).utf16()));
+            break;
+        case QD3D11CommandBuffer::Command::BindComputePipeline:
+            context->CSSetShader(cmd.args.bindComputePipeline.ps->cs.shader, nullptr, 0);
+            break;
+        case QD3D11CommandBuffer::Command::Dispatch:
+            context->Dispatch(cmd.args.dispatch.x, cmd.args.dispatch.y, cmd.args.dispatch.z);
+            break;
+        default:
+            break;
+        }
+    }
+}
+
+QD3D11Buffer::QD3D11Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size)
+    : QRhiBuffer(rhi, type, usage, size)
+{
+}
+
+QD3D11Buffer::~QD3D11Buffer()
+{
+    destroy();
+}
+
+void QD3D11Buffer::destroy()
+{
+    if (!buffer)
+        return;
+
+    buffer->Release();
+    buffer = nullptr;
+
+    delete[] dynBuf;
+    dynBuf = nullptr;
+
+    for (auto it = uavs.begin(), end = uavs.end(); it != end; ++it)
+        it.value()->Release();
+    uavs.clear();
+
+    QRHI_RES_RHI(QRhiD3D11);
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+static inline uint toD3DBufferUsage(QRhiBuffer::UsageFlags usage)
+{
+    int u = 0;
+    if (usage.testFlag(QRhiBuffer::VertexBuffer))
+        u |= D3D11_BIND_VERTEX_BUFFER;
+    if (usage.testFlag(QRhiBuffer::IndexBuffer))
+        u |= D3D11_BIND_INDEX_BUFFER;
+    if (usage.testFlag(QRhiBuffer::UniformBuffer))
+        u |= D3D11_BIND_CONSTANT_BUFFER;
+    if (usage.testFlag(QRhiBuffer::StorageBuffer))
+        u |= D3D11_BIND_UNORDERED_ACCESS;
+    return uint(u);
+}
+
+bool QD3D11Buffer::create()
+{
+    if (buffer)
+        destroy();
+
+    if (m_usage.testFlag(QRhiBuffer::UniformBuffer) && m_type != Dynamic) {
+        qWarning("UniformBuffer must always be combined with Dynamic on D3D11");
+        return false;
+    }
+
+    if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
+        qWarning("StorageBuffer cannot be combined with Dynamic");
+        return false;
+    }
+
+    const quint32 nonZeroSize = m_size <= 0 ? 256 : m_size;
+    const quint32 roundedSize = aligned(nonZeroSize, m_usage.testFlag(QRhiBuffer::UniformBuffer) ? 256u : 4u);
+
+    D3D11_BUFFER_DESC desc = {};
+    desc.ByteWidth = roundedSize;
+    desc.Usage = m_type == Dynamic ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_DEFAULT;
+    desc.BindFlags = toD3DBufferUsage(m_usage);
+    desc.CPUAccessFlags = m_type == Dynamic ? D3D11_CPU_ACCESS_WRITE : 0;
+    desc.MiscFlags = m_usage.testFlag(QRhiBuffer::StorageBuffer) ? D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS : 0;
+
+    QRHI_RES_RHI(QRhiD3D11);
+    HRESULT hr = rhiD->dev->CreateBuffer(&desc, nullptr, &buffer);
+    if (FAILED(hr)) {
+        qWarning("Failed to create buffer: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+
+    if (m_type == Dynamic) {
+        dynBuf = new char[nonZeroSize];
+        hasPendingDynamicUpdates = false;
+    }
+
+    if (!m_objectName.isEmpty())
+        buffer->SetPrivateData(WKPDID_D3DDebugObjectName, UINT(m_objectName.size()), m_objectName.constData());
+
+    generation += 1;
+    rhiD->registerResource(this);
+    return true;
+}
+
+QRhiBuffer::NativeBuffer QD3D11Buffer::nativeBuffer()
+{
+    if (m_type == Dynamic) {
+        QRHI_RES_RHI(QRhiD3D11);
+        rhiD->executeBufferHostWrites(this);
+    }
+    return { { &buffer }, 1 };
+}
+
+char *QD3D11Buffer::beginFullDynamicBufferUpdateForCurrentFrame()
+{
+    // Shortcut the entire buffer update mechanism and allow the client to do
+    // the host writes directly to the buffer. This will lead to unexpected
+    // results when combined with QRhiResourceUpdateBatch-based updates for the
+    // buffer, since dynBuf is left untouched and out of sync, but provides a
+    // fast path for dynamic buffers that have all their content changed in
+    // every frame.
+    Q_ASSERT(m_type == Dynamic);
+    D3D11_MAPPED_SUBRESOURCE mp;
+    QRHI_RES_RHI(QRhiD3D11);
+    HRESULT hr = rhiD->context->Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mp);
+    if (FAILED(hr)) {
+        qWarning("Failed to map buffer: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        return nullptr;
+    }
+    return static_cast<char *>(mp.pData);
+}
+
+void QD3D11Buffer::endFullDynamicBufferUpdateForCurrentFrame()
+{
+    QRHI_RES_RHI(QRhiD3D11);
+    rhiD->context->Unmap(buffer, 0);
+}
+
+ID3D11UnorderedAccessView *QD3D11Buffer::unorderedAccessView(quint32 offset)
+{
+    auto it = uavs.find(offset);
+    if (it != uavs.end())
+        return it.value();
+
+    // SPIRV-Cross generated HLSL uses RWByteAddressBuffer
+    D3D11_UNORDERED_ACCESS_VIEW_DESC desc = {};
+    desc.Format = DXGI_FORMAT_R32_TYPELESS;
+    desc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
+    desc.Buffer.FirstElement = offset / 4u;
+    desc.Buffer.NumElements = aligned(m_size - offset, 4u) / 4u;
+    desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW;
+
+    QRHI_RES_RHI(QRhiD3D11);
+    ID3D11UnorderedAccessView *uav = nullptr;
+    HRESULT hr = rhiD->dev->CreateUnorderedAccessView(buffer, &desc, &uav);
+    if (FAILED(hr)) {
+        qWarning("Failed to create UAV: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        return nullptr;
+    }
+
+    uavs[offset] = uav;
+    return uav;
+}
+
+QD3D11RenderBuffer::QD3D11RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+                                       int sampleCount, QRhiRenderBuffer::Flags flags,
+                                       QRhiTexture::Format backingFormatHint)
+    : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags, backingFormatHint)
+{
+}
+
+QD3D11RenderBuffer::~QD3D11RenderBuffer()
+{
+    destroy();
+}
+
+void QD3D11RenderBuffer::destroy()
+{
+    if (!tex)
+        return;
+
+    if (dsv) {
+        dsv->Release();
+        dsv = nullptr;
+    }
+
+    if (rtv) {
+        rtv->Release();
+        rtv = nullptr;
+    }
+
+    tex->Release();
+    tex = nullptr;
+
+    QRHI_RES_RHI(QRhiD3D11);
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+bool QD3D11RenderBuffer::create()
+{
+    if (tex)
+        destroy();
+
+    if (m_pixelSize.isEmpty())
+        return false;
+
+    QRHI_RES_RHI(QRhiD3D11);
+    sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount);
+
+    D3D11_TEXTURE2D_DESC desc = {};
+    desc.Width = UINT(m_pixelSize.width());
+    desc.Height = UINT(m_pixelSize.height());
+    desc.MipLevels = 1;
+    desc.ArraySize = 1;
+    desc.SampleDesc = sampleDesc;
+    desc.Usage = D3D11_USAGE_DEFAULT;
+
+    if (m_type == Color) {
+        dxgiFormat = m_backingFormatHint == QRhiTexture::UnknownFormat ? DXGI_FORMAT_R8G8B8A8_UNORM
+                                                                       : toD3DTextureFormat(m_backingFormatHint, {});
+        desc.Format = dxgiFormat;
+        desc.BindFlags = D3D11_BIND_RENDER_TARGET;
+        HRESULT hr = rhiD->dev->CreateTexture2D(&desc, nullptr, &tex);
+        if (FAILED(hr)) {
+            qWarning("Failed to create color renderbuffer: %s",
+                qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+        D3D11_RENDER_TARGET_VIEW_DESC rtvDesc = {};
+        rtvDesc.Format = dxgiFormat;
+        rtvDesc.ViewDimension = desc.SampleDesc.Count > 1 ? D3D11_RTV_DIMENSION_TEXTURE2DMS
+                                                          : D3D11_RTV_DIMENSION_TEXTURE2D;
+        hr = rhiD->dev->CreateRenderTargetView(tex, &rtvDesc, &rtv);
+        if (FAILED(hr)) {
+            qWarning("Failed to create rtv: %s",
+                qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+    } else if (m_type == DepthStencil) {
+        dxgiFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
+        desc.Format = dxgiFormat;
+        desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
+        HRESULT hr = rhiD->dev->CreateTexture2D(&desc, nullptr, &tex);
+        if (FAILED(hr)) {
+            qWarning("Failed to create depth-stencil buffer: %s",
+                qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+        D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
+        dsvDesc.Format = dxgiFormat;
+        dsvDesc.ViewDimension = desc.SampleDesc.Count > 1 ? D3D11_DSV_DIMENSION_TEXTURE2DMS
+                                                          : D3D11_DSV_DIMENSION_TEXTURE2D;
+        hr = rhiD->dev->CreateDepthStencilView(tex, &dsvDesc, &dsv);
+        if (FAILED(hr)) {
+            qWarning("Failed to create dsv: %s",
+                qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+    } else {
+        return false;
+    }
+
+    if (!m_objectName.isEmpty())
+        tex->SetPrivateData(WKPDID_D3DDebugObjectName, UINT(m_objectName.size()), m_objectName.constData());
+
+    generation += 1;
+    rhiD->registerResource(this);
+    return true;
+}
+
+QRhiTexture::Format QD3D11RenderBuffer::backingFormat() const
+{
+    if (m_backingFormatHint != QRhiTexture::UnknownFormat)
+        return m_backingFormatHint;
+    else
+        return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QD3D11Texture::QD3D11Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
+                             int arraySize, int sampleCount, Flags flags)
+    : QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags)
+{
+    for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i)
+        perLevelViews[i] = nullptr;
+}
+
+QD3D11Texture::~QD3D11Texture()
+{
+    destroy();
+}
+
+void QD3D11Texture::destroy()
+{
+    if (!tex && !tex3D && !tex1D)
+        return;
+
+    if (srv) {
+        srv->Release();
+        srv = nullptr;
+    }
+
+    for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i) {
+        if (perLevelViews[i]) {
+            perLevelViews[i]->Release();
+            perLevelViews[i] = nullptr;
+        }
+    }
+
+    if (owns) {
+        if (tex)
+            tex->Release();
+        if (tex3D)
+            tex3D->Release();
+        if (tex1D)
+            tex1D->Release();
+    }
+
+    tex = nullptr;
+    tex3D = nullptr;
+    tex1D = nullptr;
+
+    QRHI_RES_RHI(QRhiD3D11);
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+static inline DXGI_FORMAT toD3DDepthTextureSRVFormat(QRhiTexture::Format format)
+{
+    switch (format) {
+    case QRhiTexture::Format::D16:
+        return DXGI_FORMAT_R16_FLOAT;
+    case QRhiTexture::Format::D24:
+        return DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
+    case QRhiTexture::Format::D24S8:
+        return DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
+    case QRhiTexture::Format::D32F:
+        return DXGI_FORMAT_R32_FLOAT;
+    default:
+        Q_UNREACHABLE();
+        return DXGI_FORMAT_R32_FLOAT;
+    }
+}
+
+static inline DXGI_FORMAT toD3DDepthTextureDSVFormat(QRhiTexture::Format format)
+{
+    switch (format) {
+    case QRhiTexture::Format::D16:
+        return DXGI_FORMAT_D16_UNORM;
+    case QRhiTexture::Format::D24:
+        return DXGI_FORMAT_D24_UNORM_S8_UINT;
+    case QRhiTexture::Format::D24S8:
+        return DXGI_FORMAT_D24_UNORM_S8_UINT;
+    case QRhiTexture::Format::D32F:
+        return DXGI_FORMAT_D32_FLOAT;
+    default:
+        Q_UNREACHABLE();
+        return DXGI_FORMAT_D32_FLOAT;
+    }
+}
+
+bool QD3D11Texture::prepareCreate(QSize *adjustedSize)
+{
+    if (tex || tex3D || tex1D)
+        destroy();
+
+    const bool isDepth = isDepthTextureFormat(m_format);
+    const bool isCube = m_flags.testFlag(CubeMap);
+    const bool is3D = m_flags.testFlag(ThreeDimensional);
+    const bool isArray = m_flags.testFlag(TextureArray);
+    const bool hasMipMaps = m_flags.testFlag(MipMapped);
+    const bool is1D = m_flags.testFlag(OneDimensional);
+
+    const QSize size = is1D ? QSize(qMax(1, m_pixelSize.width()), 1)
+                            : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize);
+
+    QRHI_RES_RHI(QRhiD3D11);
+    dxgiFormat = toD3DTextureFormat(m_format, m_flags);
+    mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1);
+    sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount);
+    if (sampleDesc.Count > 1) {
+        if (isCube) {
+            qWarning("Cubemap texture cannot be multisample");
+            return false;
+        }
+        if (is3D) {
+            qWarning("3D texture cannot be multisample");
+            return false;
+        }
+        if (hasMipMaps) {
+            qWarning("Multisample texture cannot have mipmaps");
+            return false;
+        }
+    }
+    if (isDepth && hasMipMaps) {
+        qWarning("Depth texture cannot have mipmaps");
+        return false;
+    }
+    if (isCube && is3D) {
+        qWarning("Texture cannot be both cube and 3D");
+        return false;
+    }
+    if (isArray && is3D) {
+        qWarning("Texture cannot be both array and 3D");
+        return false;
+    }
+    if (isCube && is1D) {
+        qWarning("Texture cannot be both cube and 1D");
+        return false;
+    }
+    if (is1D && is3D) {
+        qWarning("Texture cannot be both 1D and 3D");
+        return false;
+    }
+    if (m_depth > 1 && !is3D) {
+        qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth);
+        return false;
+    }
+    if (m_arraySize > 0 && !isArray) {
+        qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize);
+        return false;
+    }
+    if (m_arraySize < 1 && isArray) {
+        qWarning("Texture is an array but array size is %d", m_arraySize);
+        return false;
+    }
+
+    if (adjustedSize)
+        *adjustedSize = size;
+
+    return true;
+}
+
+bool QD3D11Texture::finishCreate()
+{
+    QRHI_RES_RHI(QRhiD3D11);
+    const bool isDepth = isDepthTextureFormat(m_format);
+    const bool isCube = m_flags.testFlag(CubeMap);
+    const bool is3D = m_flags.testFlag(ThreeDimensional);
+    const bool isArray = m_flags.testFlag(TextureArray);
+    const bool is1D = m_flags.testFlag(OneDimensional);
+
+    D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
+    srvDesc.Format = isDepth ? toD3DDepthTextureSRVFormat(m_format) : dxgiFormat;
+    if (isCube) {
+        srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
+        srvDesc.TextureCube.MipLevels = mipLevelCount;
+    } else {
+        if (is1D) {
+            if (isArray) {
+                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1DARRAY;
+                srvDesc.Texture1DArray.MipLevels = mipLevelCount;
+                if (m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) {
+                    srvDesc.Texture1DArray.FirstArraySlice = UINT(m_arrayRangeStart);
+                    srvDesc.Texture1DArray.ArraySize = UINT(m_arrayRangeLength);
+                } else {
+                    srvDesc.Texture1DArray.FirstArraySlice = 0;
+                    srvDesc.Texture1DArray.ArraySize = UINT(qMax(0, m_arraySize));
+                }
+            } else {
+                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE1D;
+                srvDesc.Texture1D.MipLevels = mipLevelCount;
+            }
+        } else if (isArray) {
+            if (sampleDesc.Count > 1) {
+                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMSARRAY;
+                if (m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) {
+                    srvDesc.Texture2DMSArray.FirstArraySlice = UINT(m_arrayRangeStart);
+                    srvDesc.Texture2DMSArray.ArraySize = UINT(m_arrayRangeLength);
+                } else {
+                    srvDesc.Texture2DMSArray.FirstArraySlice = 0;
+                    srvDesc.Texture2DMSArray.ArraySize = UINT(qMax(0, m_arraySize));
+                }
+            } else {
+                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
+                srvDesc.Texture2DArray.MipLevels = mipLevelCount;
+                if (m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) {
+                    srvDesc.Texture2DArray.FirstArraySlice = UINT(m_arrayRangeStart);
+                    srvDesc.Texture2DArray.ArraySize = UINT(m_arrayRangeLength);
+                } else {
+                    srvDesc.Texture2DArray.FirstArraySlice = 0;
+                    srvDesc.Texture2DArray.ArraySize = UINT(qMax(0, m_arraySize));
+                }
+            }
+        } else {
+            if (sampleDesc.Count > 1) {
+                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DMS;
+            } else if (is3D) {
+                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE3D;
+                srvDesc.Texture3D.MipLevels = mipLevelCount;
+            } else {
+                srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
+                srvDesc.Texture2D.MipLevels = mipLevelCount;
+            }
+        }
+    }
+
+    HRESULT hr = rhiD->dev->CreateShaderResourceView(textureResource(), &srvDesc, &srv);
+    if (FAILED(hr)) {
+        qWarning("Failed to create srv: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+
+    generation += 1;
+    return true;
+}
+
+bool QD3D11Texture::create()
+{
+    QSize size;
+    if (!prepareCreate(&size))
+        return false;
+
+    const bool isDepth = isDepthTextureFormat(m_format);
+    const bool isCube = m_flags.testFlag(CubeMap);
+    const bool is3D = m_flags.testFlag(ThreeDimensional);
+    const bool isArray = m_flags.testFlag(TextureArray);
+    const bool is1D = m_flags.testFlag(OneDimensional);
+
+    uint bindFlags = D3D11_BIND_SHADER_RESOURCE;
+    uint miscFlags = isCube ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0;
+    if (m_flags.testFlag(RenderTarget)) {
+        if (isDepth)
+            bindFlags |= D3D11_BIND_DEPTH_STENCIL;
+        else
+            bindFlags |= D3D11_BIND_RENDER_TARGET;
+    }
+    if (m_flags.testFlag(UsedWithGenerateMips)) {
+        if (isDepth) {
+            qWarning("Depth texture cannot have mipmaps generated");
+            return false;
+        }
+        bindFlags |= D3D11_BIND_RENDER_TARGET;
+        miscFlags |= D3D11_RESOURCE_MISC_GENERATE_MIPS;
+    }
+    if (m_flags.testFlag(UsedWithLoadStore))
+        bindFlags |= D3D11_BIND_UNORDERED_ACCESS;
+
+    QRHI_RES_RHI(QRhiD3D11);
+    if (is1D) {
+        D3D11_TEXTURE1D_DESC desc = {};
+        desc.Width = UINT(size.width());
+        desc.MipLevels = mipLevelCount;
+        desc.ArraySize = isArray ? UINT(qMax(0, m_arraySize)) : 1;
+        desc.Format = dxgiFormat;
+        desc.Usage = D3D11_USAGE_DEFAULT;
+        desc.BindFlags = bindFlags;
+        desc.MiscFlags = miscFlags;
+
+        HRESULT hr = rhiD->dev->CreateTexture1D(&desc, nullptr, &tex1D);
+        if (FAILED(hr)) {
+            qWarning("Failed to create 1D texture: %s",
+                qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+        if (!m_objectName.isEmpty())
+            tex->SetPrivateData(WKPDID_D3DDebugObjectName, UINT(m_objectName.size()),
+                                m_objectName.constData());
+    } else if (!is3D) {
+        D3D11_TEXTURE2D_DESC desc = {};
+        desc.Width = UINT(size.width());
+        desc.Height = UINT(size.height());
+        desc.MipLevels = mipLevelCount;
+        desc.ArraySize = isCube ? 6 : (isArray ? UINT(qMax(0, m_arraySize)) : 1);
+        desc.Format = dxgiFormat;
+        desc.SampleDesc = sampleDesc;
+        desc.Usage = D3D11_USAGE_DEFAULT;
+        desc.BindFlags = bindFlags;
+        desc.MiscFlags = miscFlags;
+
+        HRESULT hr = rhiD->dev->CreateTexture2D(&desc, nullptr, &tex);
+        if (FAILED(hr)) {
+            qWarning("Failed to create 2D texture: %s",
+                qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+        if (!m_objectName.isEmpty())
+            tex->SetPrivateData(WKPDID_D3DDebugObjectName, UINT(m_objectName.size()), m_objectName.constData());
+    } else {
+        D3D11_TEXTURE3D_DESC desc = {};
+        desc.Width = UINT(size.width());
+        desc.Height = UINT(size.height());
+        desc.Depth = UINT(qMax(1, m_depth));
+        desc.MipLevels = mipLevelCount;
+        desc.Format = dxgiFormat;
+        desc.Usage = D3D11_USAGE_DEFAULT;
+        desc.BindFlags = bindFlags;
+        desc.MiscFlags = miscFlags;
+
+        HRESULT hr = rhiD->dev->CreateTexture3D(&desc, nullptr, &tex3D);
+        if (FAILED(hr)) {
+            qWarning("Failed to create 3D texture: %s",
+                qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+        if (!m_objectName.isEmpty())
+            tex3D->SetPrivateData(WKPDID_D3DDebugObjectName, UINT(m_objectName.size()), m_objectName.constData());
+    }
+
+    if (!finishCreate())
+        return false;
+
+    owns = true;
+    rhiD->registerResource(this);
+    return true;
+}
+
+bool QD3D11Texture::createFrom(QRhiTexture::NativeTexture src)
+{
+    if (!src.object)
+        return false;
+
+    if (!prepareCreate())
+        return false;
+
+    if (m_flags.testFlag(ThreeDimensional))
+        tex3D = reinterpret_cast<ID3D11Texture3D *>(src.object);
+    else if (m_flags.testFlags(OneDimensional))
+        tex1D = reinterpret_cast<ID3D11Texture1D *>(src.object);
+    else
+        tex = reinterpret_cast<ID3D11Texture2D *>(src.object);
+
+    if (!finishCreate())
+        return false;
+
+    owns = false;
+    QRHI_RES_RHI(QRhiD3D11);
+    rhiD->registerResource(this);
+    return true;
+}
+
+QRhiTexture::NativeTexture QD3D11Texture::nativeTexture()
+{
+    return { quint64(textureResource()), 0 };
+}
+
+ID3D11UnorderedAccessView *QD3D11Texture::unorderedAccessViewForLevel(int level)
+{
+    if (perLevelViews[level])
+        return perLevelViews[level];
+
+    const bool isCube = m_flags.testFlag(CubeMap);
+    const bool isArray = m_flags.testFlag(TextureArray);
+    const bool is3D = m_flags.testFlag(ThreeDimensional);
+    D3D11_UNORDERED_ACCESS_VIEW_DESC desc = {};
+    desc.Format = dxgiFormat;
+    if (isCube) {
+        desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY;
+        desc.Texture2DArray.MipSlice = UINT(level);
+        desc.Texture2DArray.FirstArraySlice = 0;
+        desc.Texture2DArray.ArraySize = 6;
+    } else if (isArray) {
+        desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2DARRAY;
+        desc.Texture2DArray.MipSlice = UINT(level);
+        desc.Texture2DArray.FirstArraySlice = 0;
+        desc.Texture2DArray.ArraySize = UINT(qMax(0, m_arraySize));
+    } else if (is3D) {
+        desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE3D;
+        desc.Texture3D.MipSlice = UINT(level);
+    } else {
+        desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D;
+        desc.Texture2D.MipSlice = UINT(level);
+    }
+
+    QRHI_RES_RHI(QRhiD3D11);
+    ID3D11UnorderedAccessView *uav = nullptr;
+    HRESULT hr = rhiD->dev->CreateUnorderedAccessView(textureResource(), &desc, &uav);
+    if (FAILED(hr)) {
+        qWarning("Failed to create UAV: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        return nullptr;
+    }
+
+    perLevelViews[level] = uav;
+    return uav;
+}
+
+QD3D11Sampler::QD3D11Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+                             AddressMode u, AddressMode v, AddressMode w)
+    : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w)
+{
+}
+
+QD3D11Sampler::~QD3D11Sampler()
+{
+    destroy();
+}
+
+void QD3D11Sampler::destroy()
+{
+    if (!samplerState)
+        return;
+
+    samplerState->Release();
+    samplerState = nullptr;
+
+    QRHI_RES_RHI(QRhiD3D11);
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+static inline D3D11_FILTER toD3DFilter(QRhiSampler::Filter minFilter, QRhiSampler::Filter magFilter, QRhiSampler::Filter mipFilter)
+{
+    if (minFilter == QRhiSampler::Nearest) {
+        if (magFilter == QRhiSampler::Nearest) {
+            if (mipFilter == QRhiSampler::Linear)
+                return D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR;
+            else
+                return D3D11_FILTER_MIN_MAG_MIP_POINT;
+        } else {
+            if (mipFilter == QRhiSampler::Linear)
+                return D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR;
+            else
+                return D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT;
+        }
+    } else {
+        if (magFilter == QRhiSampler::Nearest) {
+            if (mipFilter == QRhiSampler::Linear)
+                return D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR;
+            else
+                return D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT;
+        } else {
+            if (mipFilter == QRhiSampler::Linear)
+                return D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+            else
+                return D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT;
+        }
+    }
+
+    Q_UNREACHABLE();
+    return D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+}
+
+static inline D3D11_TEXTURE_ADDRESS_MODE toD3DAddressMode(QRhiSampler::AddressMode m)
+{
+    switch (m) {
+    case QRhiSampler::Repeat:
+        return D3D11_TEXTURE_ADDRESS_WRAP;
+    case QRhiSampler::ClampToEdge:
+        return D3D11_TEXTURE_ADDRESS_CLAMP;
+    case QRhiSampler::Mirror:
+        return D3D11_TEXTURE_ADDRESS_MIRROR;
+    default:
+        Q_UNREACHABLE();
+        return D3D11_TEXTURE_ADDRESS_CLAMP;
+    }
+}
+
+static inline D3D11_COMPARISON_FUNC toD3DTextureComparisonFunc(QRhiSampler::CompareOp op)
+{
+    switch (op) {
+    case QRhiSampler::Never:
+        return D3D11_COMPARISON_NEVER;
+    case QRhiSampler::Less:
+        return D3D11_COMPARISON_LESS;
+    case QRhiSampler::Equal:
+        return D3D11_COMPARISON_EQUAL;
+    case QRhiSampler::LessOrEqual:
+        return D3D11_COMPARISON_LESS_EQUAL;
+    case QRhiSampler::Greater:
+        return D3D11_COMPARISON_GREATER;
+    case QRhiSampler::NotEqual:
+        return D3D11_COMPARISON_NOT_EQUAL;
+    case QRhiSampler::GreaterOrEqual:
+        return D3D11_COMPARISON_GREATER_EQUAL;
+    case QRhiSampler::Always:
+        return D3D11_COMPARISON_ALWAYS;
+    default:
+        Q_UNREACHABLE();
+        return D3D11_COMPARISON_NEVER;
+    }
+}
+
+bool QD3D11Sampler::create()
+{
+    if (samplerState)
+        destroy();
+
+    D3D11_SAMPLER_DESC desc = {};
+    desc.Filter = toD3DFilter(m_minFilter, m_magFilter, m_mipmapMode);
+    if (m_compareOp != Never)
+        desc.Filter = D3D11_FILTER(desc.Filter | 0x80);
+    desc.AddressU = toD3DAddressMode(m_addressU);
+    desc.AddressV = toD3DAddressMode(m_addressV);
+    desc.AddressW = toD3DAddressMode(m_addressW);
+    desc.MaxAnisotropy = 1.0f;
+    desc.ComparisonFunc = toD3DTextureComparisonFunc(m_compareOp);
+    desc.MaxLOD = m_mipmapMode == None ? 0.0f : 1000.0f;
+
+    QRHI_RES_RHI(QRhiD3D11);
+    HRESULT hr = rhiD->dev->CreateSamplerState(&desc, &samplerState);
+    if (FAILED(hr)) {
+        qWarning("Failed to create sampler state: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+
+    generation += 1;
+    rhiD->registerResource(this);
+    return true;
+}
+
+// dummy, no Vulkan-style RenderPass+Framebuffer concept here
+QD3D11RenderPassDescriptor::QD3D11RenderPassDescriptor(QRhiImplementation *rhi)
+    : QRhiRenderPassDescriptor(rhi)
+{
+}
+
+QD3D11RenderPassDescriptor::~QD3D11RenderPassDescriptor()
+{
+    destroy();
+}
+
+void QD3D11RenderPassDescriptor::destroy()
+{
+    QRHI_RES_RHI(QRhiD3D11);
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+bool QD3D11RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
+{
+    Q_UNUSED(other);
+    return true;
+}
+
+QRhiRenderPassDescriptor *QD3D11RenderPassDescriptor::newCompatibleRenderPassDescriptor() const
+{
+    QD3D11RenderPassDescriptor *rpD = new QD3D11RenderPassDescriptor(m_rhi);
+    QRHI_RES_RHI(QRhiD3D11);
+    rhiD->registerResource(rpD, false);
+    return rpD;
+}
+
+QVector<quint32> QD3D11RenderPassDescriptor::serializedFormat() const
+{
+    return {};
+}
+
+QD3D11SwapChainRenderTarget::QD3D11SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain)
+    : QRhiSwapChainRenderTarget(rhi, swapchain),
+      d(rhi)
+{
+}
+
+QD3D11SwapChainRenderTarget::~QD3D11SwapChainRenderTarget()
+{
+    destroy();
+}
+
+void QD3D11SwapChainRenderTarget::destroy()
+{
+    // nothing to do here
+}
+
+QSize QD3D11SwapChainRenderTarget::pixelSize() const
+{
+    return d.pixelSize;
+}
+
+float QD3D11SwapChainRenderTarget::devicePixelRatio() const
+{
+    return d.dpr;
+}
+
+int QD3D11SwapChainRenderTarget::sampleCount() const
+{
+    return d.sampleCount;
+}
+
+QD3D11TextureRenderTarget::QD3D11TextureRenderTarget(QRhiImplementation *rhi,
+                                                     const QRhiTextureRenderTargetDescription &desc,
+                                                     Flags flags)
+    : QRhiTextureRenderTarget(rhi, desc, flags),
+      d(rhi)
+{
+    for (int i = 0; i < QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i) {
+        ownsRtv[i] = false;
+        rtv[i] = nullptr;
+    }
+}
+
+QD3D11TextureRenderTarget::~QD3D11TextureRenderTarget()
+{
+    destroy();
+}
+
+void QD3D11TextureRenderTarget::destroy()
+{
+    if (!rtv[0] && !dsv)
+        return;
+
+    if (dsv) {
+        if (ownsDsv)
+            dsv->Release();
+        dsv = nullptr;
+    }
+
+    for (int i = 0; i < QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i) {
+        if (rtv[i]) {
+            if (ownsRtv[i])
+                rtv[i]->Release();
+            rtv[i] = nullptr;
+        }
+    }
+
+    QRHI_RES_RHI(QRhiD3D11);
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+QRhiRenderPassDescriptor *QD3D11TextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+    QD3D11RenderPassDescriptor *rpD = new QD3D11RenderPassDescriptor(m_rhi);
+    QRHI_RES_RHI(QRhiD3D11);
+    rhiD->registerResource(rpD, false);
+    return rpD;
+}
+
+bool QD3D11TextureRenderTarget::create()
+{
+    if (rtv[0] || dsv)
+        destroy();
+
+    Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture());
+    Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
+    const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+
+    QRHI_RES_RHI(QRhiD3D11);
+
+    d.colorAttCount = 0;
+    int attIndex = 0;
+    for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
+        d.colorAttCount += 1;
+        const QRhiColorAttachment &colorAtt(*it);
+        QRhiTexture *texture = colorAtt.texture();
+        QRhiRenderBuffer *rb = colorAtt.renderBuffer();
+        Q_ASSERT(texture || rb);
+        if (texture) {
+            QD3D11Texture *texD = QRHI_RES(QD3D11Texture, texture);
+            D3D11_RENDER_TARGET_VIEW_DESC rtvDesc = {};
+            rtvDesc.Format = toD3DTextureFormat(texD->format(), texD->flags());
+            if (texD->flags().testFlag(QRhiTexture::CubeMap)) {
+                rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
+                rtvDesc.Texture2DArray.MipSlice = UINT(colorAtt.level());
+                rtvDesc.Texture2DArray.FirstArraySlice = UINT(colorAtt.layer());
+                rtvDesc.Texture2DArray.ArraySize = 1;
+            } else if (texD->flags().testFlag(QRhiTexture::OneDimensional)) {
+                if (texD->flags().testFlag(QRhiTexture::TextureArray)) {
+                    rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE1DARRAY;
+                    rtvDesc.Texture1DArray.MipSlice = UINT(colorAtt.level());
+                    rtvDesc.Texture1DArray.FirstArraySlice = UINT(colorAtt.layer());
+                    rtvDesc.Texture1DArray.ArraySize = 1;
+                } else {
+                    rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE1D;
+                    rtvDesc.Texture1D.MipSlice = UINT(colorAtt.level());
+                }
+            } else if (texD->flags().testFlag(QRhiTexture::TextureArray)) {
+                if (texD->sampleDesc.Count > 1) {
+                    rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMSARRAY;
+                    rtvDesc.Texture2DMSArray.FirstArraySlice = UINT(colorAtt.layer());
+                    rtvDesc.Texture2DMSArray.ArraySize = 1;
+                } else {
+                    rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
+                    rtvDesc.Texture2DArray.MipSlice = UINT(colorAtt.level());
+                    rtvDesc.Texture2DArray.FirstArraySlice = UINT(colorAtt.layer());
+                    rtvDesc.Texture2DArray.ArraySize = 1;
+                }
+            } else if (texD->flags().testFlag(QRhiTexture::ThreeDimensional)) {
+                rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE3D;
+                rtvDesc.Texture3D.MipSlice = UINT(colorAtt.level());
+                rtvDesc.Texture3D.FirstWSlice = UINT(colorAtt.layer());
+                rtvDesc.Texture3D.WSize = 1;
+            } else {
+                if (texD->sampleDesc.Count > 1) {
+                    rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DMS;
+                } else {
+                    rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+                    rtvDesc.Texture2D.MipSlice = UINT(colorAtt.level());
+                }
+            }
+            HRESULT hr = rhiD->dev->CreateRenderTargetView(texD->textureResource(), &rtvDesc, &rtv[attIndex]);
+            if (FAILED(hr)) {
+                qWarning("Failed to create rtv: %s",
+                    qPrintable(QSystemError::windowsComString(hr)));
+                return false;
+            }
+            ownsRtv[attIndex] = true;
+            if (attIndex == 0) {
+                d.pixelSize = rhiD->q->sizeForMipLevel(colorAtt.level(), texD->pixelSize());
+                d.sampleCount = int(texD->sampleDesc.Count);
+            }
+        } else if (rb) {
+            QD3D11RenderBuffer *rbD = QRHI_RES(QD3D11RenderBuffer, rb);
+            ownsRtv[attIndex] = false;
+            rtv[attIndex] = rbD->rtv;
+            if (attIndex == 0) {
+                d.pixelSize = rbD->pixelSize();
+                d.sampleCount = int(rbD->sampleDesc.Count);
+            }
+        }
+    }
+    d.dpr = 1;
+
+    if (hasDepthStencil) {
+        if (m_desc.depthTexture()) {
+            ownsDsv = true;
+            QD3D11Texture *depthTexD = QRHI_RES(QD3D11Texture, m_desc.depthTexture());
+            D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
+            dsvDesc.Format = toD3DDepthTextureDSVFormat(depthTexD->format());
+            dsvDesc.ViewDimension = depthTexD->sampleDesc.Count > 1 ? D3D11_DSV_DIMENSION_TEXTURE2DMS
+                                                                    : D3D11_DSV_DIMENSION_TEXTURE2D;
+            if (depthTexD->flags().testFlag(QRhiTexture::TextureArray)) {
+                if (depthTexD->sampleDesc.Count > 1) {
+                    dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DMSARRAY;
+                    if (depthTexD->arrayRangeStart() >= 0 && depthTexD->arrayRangeLength() >= 0) {
+                        dsvDesc.Texture2DMSArray.FirstArraySlice = UINT(depthTexD->arrayRangeStart());
+                        dsvDesc.Texture2DMSArray.ArraySize = UINT(depthTexD->arrayRangeLength());
+                    } else {
+                        dsvDesc.Texture2DMSArray.FirstArraySlice = 0;
+                        dsvDesc.Texture2DMSArray.ArraySize = UINT(qMax(0, depthTexD->arraySize()));
+                    }
+                } else {
+                    dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY;
+                    if (depthTexD->arrayRangeStart() >= 0 && depthTexD->arrayRangeLength() >= 0) {
+                        dsvDesc.Texture2DArray.FirstArraySlice = UINT(depthTexD->arrayRangeStart());
+                        dsvDesc.Texture2DArray.ArraySize = UINT(depthTexD->arrayRangeLength());
+                    } else {
+                        dsvDesc.Texture2DArray.FirstArraySlice = 0;
+                        dsvDesc.Texture2DArray.ArraySize = UINT(qMax(0, depthTexD->arraySize()));
+                    }
+                }
+            }
+            HRESULT hr = rhiD->dev->CreateDepthStencilView(depthTexD->tex, &dsvDesc, &dsv);
+            if (FAILED(hr)) {
+                qWarning("Failed to create dsv: %s",
+                    qPrintable(QSystemError::windowsComString(hr)));
+                return false;
+            }
+            if (d.colorAttCount == 0) {
+                d.pixelSize = depthTexD->pixelSize();
+                d.sampleCount = int(depthTexD->sampleDesc.Count);
+            }
+        } else {
+            ownsDsv = false;
+            QD3D11RenderBuffer *depthRbD = QRHI_RES(QD3D11RenderBuffer, m_desc.depthStencilBuffer());
+            dsv = depthRbD->dsv;
+            if (d.colorAttCount == 0) {
+                d.pixelSize = m_desc.depthStencilBuffer()->pixelSize();
+                d.sampleCount = int(depthRbD->sampleDesc.Count);
+            }
+        }
+        d.dsAttCount = 1;
+    } else {
+        d.dsAttCount = 0;
+    }
+
+    for (int i = 0; i < QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i)
+        d.rtv[i] = i < d.colorAttCount ? rtv[i] : nullptr;
+
+    d.dsv = dsv;
+    d.rp = QRHI_RES(QD3D11RenderPassDescriptor, m_renderPassDesc);
+
+    QRhiRenderTargetAttachmentTracker::updateResIdList<QD3D11Texture, QD3D11RenderBuffer>(m_desc, &d.currentResIdList);
+
+    rhiD->registerResource(this);
+    return true;
+}
+
+QSize QD3D11TextureRenderTarget::pixelSize() const
+{
+    if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QD3D11Texture, QD3D11RenderBuffer>(m_desc, d.currentResIdList))
+        const_cast<QD3D11TextureRenderTarget *>(this)->create();
+
+    return d.pixelSize;
+}
+
+float QD3D11TextureRenderTarget::devicePixelRatio() const
+{
+    return d.dpr;
+}
+
+int QD3D11TextureRenderTarget::sampleCount() const
+{
+    return d.sampleCount;
+}
+
+QD3D11ShaderResourceBindings::QD3D11ShaderResourceBindings(QRhiImplementation *rhi)
+    : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QD3D11ShaderResourceBindings::~QD3D11ShaderResourceBindings()
+{
+    destroy();
+}
+
+void QD3D11ShaderResourceBindings::destroy()
+{
+    sortedBindings.clear();
+    boundResourceData.clear();
+
+    QRHI_RES_RHI(QRhiD3D11);
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+bool QD3D11ShaderResourceBindings::create()
+{
+    if (!sortedBindings.isEmpty())
+        destroy();
+
+    QRHI_RES_RHI(QRhiD3D11);
+    if (!rhiD->sanityCheckShaderResourceBindings(this))
+        return false;
+
+    rhiD->updateLayoutDesc(this);
+
+    std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
+    std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan);
+
+    boundResourceData.resize(sortedBindings.count());
+
+    for (BoundResourceData &bd : boundResourceData)
+        memset(&bd, 0, sizeof(BoundResourceData));
+
+    hasDynamicOffset = false;
+    for (const QRhiShaderResourceBinding &b : sortedBindings) {
+        const QRhiShaderResourceBinding::Data *bd = QRhiImplementation::shaderResourceBindingData(b);
+        if (bd->type == QRhiShaderResourceBinding::UniformBuffer && bd->u.ubuf.hasDynamicOffset) {
+            hasDynamicOffset = true;
+            break;
+        }
+    }
+
+    generation += 1;
+    rhiD->registerResource(this, false);
+    return true;
+}
+
+void QD3D11ShaderResourceBindings::updateResources(UpdateFlags flags)
+{
+    sortedBindings.clear();
+    std::copy(m_bindings.cbegin(), m_bindings.cend(), std::back_inserter(sortedBindings));
+    if (!flags.testFlag(BindingsAreSorted))
+        std::sort(sortedBindings.begin(), sortedBindings.end(), QRhiImplementation::sortedBindingLessThan);
+
+    Q_ASSERT(boundResourceData.count() == sortedBindings.count());
+    for (BoundResourceData &bd : boundResourceData)
+        memset(&bd, 0, sizeof(BoundResourceData));
+
+    generation += 1;
+}
+
+QD3D11GraphicsPipeline::QD3D11GraphicsPipeline(QRhiImplementation *rhi)
+    : QRhiGraphicsPipeline(rhi)
+{
+}
+
+QD3D11GraphicsPipeline::~QD3D11GraphicsPipeline()
+{
+    destroy();
+}
+
+template<typename T>
+inline void releasePipelineShader(T &s)
+{
+    if (s.shader) {
+        s.shader->Release();
+        s.shader = nullptr;
+    }
+    s.nativeResourceBindingMap.clear();
+}
+
+void QD3D11GraphicsPipeline::destroy()
+{
+    if (!dsState)
+        return;
+
+    dsState->Release();
+    dsState = nullptr;
+
+    if (blendState) {
+        blendState->Release();
+        blendState = nullptr;
+    }
+
+    if (inputLayout) {
+        inputLayout->Release();
+        inputLayout = nullptr;
+    }
+
+    if (rastState) {
+        rastState->Release();
+        rastState = nullptr;
+    }
+
+    releasePipelineShader(vs);
+    releasePipelineShader(hs);
+    releasePipelineShader(ds);
+    releasePipelineShader(gs);
+    releasePipelineShader(fs);
+
+    QRHI_RES_RHI(QRhiD3D11);
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+static inline D3D11_CULL_MODE toD3DCullMode(QRhiGraphicsPipeline::CullMode c)
+{
+    switch (c) {
+    case QRhiGraphicsPipeline::None:
+        return D3D11_CULL_NONE;
+    case QRhiGraphicsPipeline::Front:
+        return D3D11_CULL_FRONT;
+    case QRhiGraphicsPipeline::Back:
+        return D3D11_CULL_BACK;
+    default:
+        Q_UNREACHABLE();
+        return D3D11_CULL_NONE;
+    }
+}
+
+static inline D3D11_FILL_MODE toD3DFillMode(QRhiGraphicsPipeline::PolygonMode mode)
+{
+    switch (mode) {
+    case QRhiGraphicsPipeline::Fill:
+        return D3D11_FILL_SOLID;
+    case QRhiGraphicsPipeline::Line:
+        return D3D11_FILL_WIREFRAME;
+    default:
+        Q_UNREACHABLE();
+        return D3D11_FILL_SOLID;
+    }
+}
+
+static inline D3D11_COMPARISON_FUNC toD3DCompareOp(QRhiGraphicsPipeline::CompareOp op)
+{
+    switch (op) {
+    case QRhiGraphicsPipeline::Never:
+        return D3D11_COMPARISON_NEVER;
+    case QRhiGraphicsPipeline::Less:
+        return D3D11_COMPARISON_LESS;
+    case QRhiGraphicsPipeline::Equal:
+        return D3D11_COMPARISON_EQUAL;
+    case QRhiGraphicsPipeline::LessOrEqual:
+        return D3D11_COMPARISON_LESS_EQUAL;
+    case QRhiGraphicsPipeline::Greater:
+        return D3D11_COMPARISON_GREATER;
+    case QRhiGraphicsPipeline::NotEqual:
+        return D3D11_COMPARISON_NOT_EQUAL;
+    case QRhiGraphicsPipeline::GreaterOrEqual:
+        return D3D11_COMPARISON_GREATER_EQUAL;
+    case QRhiGraphicsPipeline::Always:
+        return D3D11_COMPARISON_ALWAYS;
+    default:
+        Q_UNREACHABLE();
+        return D3D11_COMPARISON_ALWAYS;
+    }
+}
+
+static inline D3D11_STENCIL_OP toD3DStencilOp(QRhiGraphicsPipeline::StencilOp op)
+{
+    switch (op) {
+    case QRhiGraphicsPipeline::StencilZero:
+        return D3D11_STENCIL_OP_ZERO;
+    case QRhiGraphicsPipeline::Keep:
+        return D3D11_STENCIL_OP_KEEP;
+    case QRhiGraphicsPipeline::Replace:
+        return D3D11_STENCIL_OP_REPLACE;
+    case QRhiGraphicsPipeline::IncrementAndClamp:
+        return D3D11_STENCIL_OP_INCR_SAT;
+    case QRhiGraphicsPipeline::DecrementAndClamp:
+        return D3D11_STENCIL_OP_DECR_SAT;
+    case QRhiGraphicsPipeline::Invert:
+        return D3D11_STENCIL_OP_INVERT;
+    case QRhiGraphicsPipeline::IncrementAndWrap:
+        return D3D11_STENCIL_OP_INCR;
+    case QRhiGraphicsPipeline::DecrementAndWrap:
+        return D3D11_STENCIL_OP_DECR;
+    default:
+        Q_UNREACHABLE();
+        return D3D11_STENCIL_OP_KEEP;
+    }
+}
+
+static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format format)
+{
+    switch (format) {
+    case QRhiVertexInputAttribute::Float4:
+        return DXGI_FORMAT_R32G32B32A32_FLOAT;
+    case QRhiVertexInputAttribute::Float3:
+        return DXGI_FORMAT_R32G32B32_FLOAT;
+    case QRhiVertexInputAttribute::Float2:
+        return DXGI_FORMAT_R32G32_FLOAT;
+    case QRhiVertexInputAttribute::Float:
+        return DXGI_FORMAT_R32_FLOAT;
+    case QRhiVertexInputAttribute::UNormByte4:
+        return DXGI_FORMAT_R8G8B8A8_UNORM;
+    case QRhiVertexInputAttribute::UNormByte2:
+        return DXGI_FORMAT_R8G8_UNORM;
+    case QRhiVertexInputAttribute::UNormByte:
+        return DXGI_FORMAT_R8_UNORM;
+    case QRhiVertexInputAttribute::UInt4:
+        return DXGI_FORMAT_R32G32B32A32_UINT;
+    case QRhiVertexInputAttribute::UInt3:
+        return DXGI_FORMAT_R32G32B32_UINT;
+    case QRhiVertexInputAttribute::UInt2:
+        return DXGI_FORMAT_R32G32_UINT;
+    case QRhiVertexInputAttribute::UInt:
+        return DXGI_FORMAT_R32_UINT;
+     case QRhiVertexInputAttribute::SInt4:
+        return DXGI_FORMAT_R32G32B32A32_SINT;
+    case QRhiVertexInputAttribute::SInt3:
+        return DXGI_FORMAT_R32G32B32_SINT;
+    case QRhiVertexInputAttribute::SInt2:
+        return DXGI_FORMAT_R32G32_SINT;
+    case QRhiVertexInputAttribute::SInt:
+        return DXGI_FORMAT_R32_SINT;
+    case QRhiVertexInputAttribute::Half4:
+    // Note: D3D does not support half3.  Pass through half3 as half4.
+    case QRhiVertexInputAttribute::Half3:
+        return DXGI_FORMAT_R16G16B16A16_FLOAT;
+    case QRhiVertexInputAttribute::Half2:
+        return DXGI_FORMAT_R16G16_FLOAT;
+    case QRhiVertexInputAttribute::Half:
+         return DXGI_FORMAT_R16_FLOAT;
+    default:
+        Q_UNREACHABLE();
+        return DXGI_FORMAT_R32G32B32A32_FLOAT;
+    }
+}
+
+static inline D3D11_PRIMITIVE_TOPOLOGY toD3DTopology(QRhiGraphicsPipeline::Topology t, int patchControlPointCount)
+{
+    switch (t) {
+    case QRhiGraphicsPipeline::Triangles:
+        return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+    case QRhiGraphicsPipeline::TriangleStrip:
+        return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
+    case QRhiGraphicsPipeline::Lines:
+        return D3D11_PRIMITIVE_TOPOLOGY_LINELIST;
+    case QRhiGraphicsPipeline::LineStrip:
+        return D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP;
+    case QRhiGraphicsPipeline::Points:
+        return D3D11_PRIMITIVE_TOPOLOGY_POINTLIST;
+    case QRhiGraphicsPipeline::Patches:
+        Q_ASSERT(patchControlPointCount >= 1 && patchControlPointCount <= 32);
+        return D3D11_PRIMITIVE_TOPOLOGY(D3D11_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + (patchControlPointCount - 1));
+    default:
+        Q_UNREACHABLE();
+        return D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+    }
+}
+
+static inline UINT8 toD3DColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
+{
+    UINT8 f = 0;
+    if (c.testFlag(QRhiGraphicsPipeline::R))
+        f |= D3D11_COLOR_WRITE_ENABLE_RED;
+    if (c.testFlag(QRhiGraphicsPipeline::G))
+        f |= D3D11_COLOR_WRITE_ENABLE_GREEN;
+    if (c.testFlag(QRhiGraphicsPipeline::B))
+        f |= D3D11_COLOR_WRITE_ENABLE_BLUE;
+    if (c.testFlag(QRhiGraphicsPipeline::A))
+        f |= D3D11_COLOR_WRITE_ENABLE_ALPHA;
+    return f;
+}
+
+static inline D3D11_BLEND toD3DBlendFactor(QRhiGraphicsPipeline::BlendFactor f, bool rgb)
+{
+    // SrcBlendAlpha and DstBlendAlpha do not accept *_COLOR. With other APIs
+    // this is handled internally (so that e.g. VK_BLEND_FACTOR_SRC_COLOR is
+    // accepted and is in effect equivalent to VK_BLEND_FACTOR_SRC_ALPHA when
+    // set as an alpha src/dest factor), but for D3D we have to take care of it
+    // ourselves. Hence the rgb argument.
+
+    switch (f) {
+    case QRhiGraphicsPipeline::Zero:
+        return D3D11_BLEND_ZERO;
+    case QRhiGraphicsPipeline::One:
+        return D3D11_BLEND_ONE;
+    case QRhiGraphicsPipeline::SrcColor:
+        return rgb ? D3D11_BLEND_SRC_COLOR : D3D11_BLEND_SRC_ALPHA;
+    case QRhiGraphicsPipeline::OneMinusSrcColor:
+        return rgb ? D3D11_BLEND_INV_SRC_COLOR : D3D11_BLEND_INV_SRC_ALPHA;
+    case QRhiGraphicsPipeline::DstColor:
+        return rgb ? D3D11_BLEND_DEST_COLOR : D3D11_BLEND_DEST_ALPHA;
+    case QRhiGraphicsPipeline::OneMinusDstColor:
+        return rgb ? D3D11_BLEND_INV_DEST_COLOR : D3D11_BLEND_INV_DEST_ALPHA;
+    case QRhiGraphicsPipeline::SrcAlpha:
+        return D3D11_BLEND_SRC_ALPHA;
+    case QRhiGraphicsPipeline::OneMinusSrcAlpha:
+        return D3D11_BLEND_INV_SRC_ALPHA;
+    case QRhiGraphicsPipeline::DstAlpha:
+        return D3D11_BLEND_DEST_ALPHA;
+    case QRhiGraphicsPipeline::OneMinusDstAlpha:
+        return D3D11_BLEND_INV_DEST_ALPHA;
+    case QRhiGraphicsPipeline::ConstantColor:
+    case QRhiGraphicsPipeline::ConstantAlpha:
+        return D3D11_BLEND_BLEND_FACTOR;
+    case QRhiGraphicsPipeline::OneMinusConstantColor:
+    case QRhiGraphicsPipeline::OneMinusConstantAlpha:
+        return D3D11_BLEND_INV_BLEND_FACTOR;
+    case QRhiGraphicsPipeline::SrcAlphaSaturate:
+        return D3D11_BLEND_SRC_ALPHA_SAT;
+    case QRhiGraphicsPipeline::Src1Color:
+        return rgb ? D3D11_BLEND_SRC1_COLOR : D3D11_BLEND_SRC1_ALPHA;
+    case QRhiGraphicsPipeline::OneMinusSrc1Color:
+        return rgb ? D3D11_BLEND_INV_SRC1_COLOR : D3D11_BLEND_INV_SRC1_ALPHA;
+    case QRhiGraphicsPipeline::Src1Alpha:
+        return D3D11_BLEND_SRC1_ALPHA;
+    case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
+        return D3D11_BLEND_INV_SRC1_ALPHA;
+    default:
+        Q_UNREACHABLE();
+        return D3D11_BLEND_ZERO;
+    }
+}
+
+static inline D3D11_BLEND_OP toD3DBlendOp(QRhiGraphicsPipeline::BlendOp op)
+{
+    switch (op) {
+    case QRhiGraphicsPipeline::Add:
+        return D3D11_BLEND_OP_ADD;
+    case QRhiGraphicsPipeline::Subtract:
+        return D3D11_BLEND_OP_SUBTRACT;
+    case QRhiGraphicsPipeline::ReverseSubtract:
+        return D3D11_BLEND_OP_REV_SUBTRACT;
+    case QRhiGraphicsPipeline::Min:
+        return D3D11_BLEND_OP_MIN;
+    case QRhiGraphicsPipeline::Max:
+        return D3D11_BLEND_OP_MAX;
+    default:
+        Q_UNREACHABLE();
+        return D3D11_BLEND_OP_ADD;
+    }
+}
+
+static inline QByteArray sourceHash(const QByteArray &source)
+{
+    // taken from the GL backend, use the same mechanism to get a key
+    QCryptographicHash keyBuilder(QCryptographicHash::Sha1);
+    keyBuilder.addData(source);
+    return keyBuilder.result().toHex();
+}
+
+QByteArray QRhiD3D11::compileHlslShaderSource(const QShader &shader, QShader::Variant shaderVariant, uint flags,
+                                              QString *error, QShaderKey *usedShaderKey)
+{
+    QShaderKey key = { QShader::DxbcShader, 50, shaderVariant };
+    QShaderCode dxbc = shader.shader(key);
+    if (!dxbc.shader().isEmpty()) {
+        if (usedShaderKey)
+            *usedShaderKey = key;
+        return dxbc.shader();
+    }
+
+    key = { QShader::HlslShader, 50, shaderVariant };
+    QShaderCode hlslSource = shader.shader(key);
+    if (hlslSource.shader().isEmpty()) {
+        qWarning() << "No HLSL (shader model 5.0) code found in baked shader" << shader;
+        return QByteArray();
+    }
+
+    if (usedShaderKey)
+        *usedShaderKey = key;
+
+    const char *target;
+    switch (shader.stage()) {
+    case QShader::VertexStage:
+        target = "vs_5_0";
+        break;
+    case QShader::TessellationControlStage:
+        target = "hs_5_0";
+        break;
+    case QShader::TessellationEvaluationStage:
+        target = "ds_5_0";
+        break;
+    case QShader::GeometryStage:
+        target = "gs_5_0";
+        break;
+    case QShader::FragmentStage:
+        target = "ps_5_0";
+        break;
+    case QShader::ComputeStage:
+        target = "cs_5_0";
+        break;
+    default:
+        Q_UNREACHABLE();
+        return QByteArray();
+    }
+
+    BytecodeCacheKey cacheKey;
+    if (rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave)) {
+        cacheKey.sourceHash = sourceHash(hlslSource.shader());
+        cacheKey.target = target;
+        cacheKey.entryPoint = hlslSource.entryPoint();
+        cacheKey.compileFlags = flags;
+        auto cacheIt = m_bytecodeCache.constFind(cacheKey);
+        if (cacheIt != m_bytecodeCache.constEnd())
+            return cacheIt.value();
+    }
+
+    static const pD3DCompile d3dCompile = QRhiD3D::resolveD3DCompile();
+    if (d3dCompile == nullptr) {
+        qWarning("Unable to resolve function D3DCompile()");
+        return QByteArray();
+    }
+
+    ID3DBlob *bytecode = nullptr;
+    ID3DBlob *errors = nullptr;
+    HRESULT hr = d3dCompile(hlslSource.shader().constData(), SIZE_T(hlslSource.shader().size()),
+                            nullptr, nullptr, nullptr,
+                            hlslSource.entryPoint().constData(), target, flags, 0, &bytecode, &errors);
+    if (FAILED(hr) || !bytecode) {
+        qWarning("HLSL shader compilation failed: 0x%x", uint(hr));
+        if (errors) {
+            *error = QString::fromUtf8(static_cast<const char *>(errors->GetBufferPointer()),
+                                       int(errors->GetBufferSize()));
+            errors->Release();
+        }
+        return QByteArray();
+    }
+
+    QByteArray result;
+    result.resize(int(bytecode->GetBufferSize()));
+    memcpy(result.data(), bytecode->GetBufferPointer(), size_t(result.size()));
+    bytecode->Release();
+
+    if (rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
+        m_bytecodeCache.insert(cacheKey, result);
+
+    return result;
+}
+
+bool QD3D11GraphicsPipeline::create()
+{
+    if (dsState)
+        destroy();
+
+    QRHI_RES_RHI(QRhiD3D11);
+    rhiD->pipelineCreationStart();
+    if (!rhiD->sanityCheckGraphicsPipeline(this))
+        return false;
+
+    D3D11_RASTERIZER_DESC rastDesc = {};
+    rastDesc.FillMode = toD3DFillMode(m_polygonMode);
+    rastDesc.CullMode = toD3DCullMode(m_cullMode);
+    rastDesc.FrontCounterClockwise = m_frontFace == CCW;
+    rastDesc.DepthBias = m_depthBias;
+    rastDesc.SlopeScaledDepthBias = m_slopeScaledDepthBias;
+    rastDesc.DepthClipEnable = true;
+    rastDesc.ScissorEnable = m_flags.testFlag(UsesScissor);
+    rastDesc.MultisampleEnable = rhiD->effectiveSampleDesc(m_sampleCount).Count > 1;
+    HRESULT hr = rhiD->dev->CreateRasterizerState(&rastDesc, &rastState);
+    if (FAILED(hr)) {
+        qWarning("Failed to create rasterizer state: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+
+    D3D11_DEPTH_STENCIL_DESC dsDesc = {};
+    dsDesc.DepthEnable = m_depthTest;
+    dsDesc.DepthWriteMask = m_depthWrite ? D3D11_DEPTH_WRITE_MASK_ALL : D3D11_DEPTH_WRITE_MASK_ZERO;
+    dsDesc.DepthFunc = toD3DCompareOp(m_depthOp);
+    dsDesc.StencilEnable = m_stencilTest;
+    if (m_stencilTest) {
+        dsDesc.StencilReadMask = UINT8(m_stencilReadMask);
+        dsDesc.StencilWriteMask = UINT8(m_stencilWriteMask);
+        dsDesc.FrontFace.StencilFailOp = toD3DStencilOp(m_stencilFront.failOp);
+        dsDesc.FrontFace.StencilDepthFailOp = toD3DStencilOp(m_stencilFront.depthFailOp);
+        dsDesc.FrontFace.StencilPassOp = toD3DStencilOp(m_stencilFront.passOp);
+        dsDesc.FrontFace.StencilFunc = toD3DCompareOp(m_stencilFront.compareOp);
+        dsDesc.BackFace.StencilFailOp = toD3DStencilOp(m_stencilBack.failOp);
+        dsDesc.BackFace.StencilDepthFailOp = toD3DStencilOp(m_stencilBack.depthFailOp);
+        dsDesc.BackFace.StencilPassOp = toD3DStencilOp(m_stencilBack.passOp);
+        dsDesc.BackFace.StencilFunc = toD3DCompareOp(m_stencilBack.compareOp);
+    }
+    hr = rhiD->dev->CreateDepthStencilState(&dsDesc, &dsState);
+    if (FAILED(hr)) {
+        qWarning("Failed to create depth-stencil state: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+
+    D3D11_BLEND_DESC blendDesc = {};
+    blendDesc.IndependentBlendEnable = m_targetBlends.count() > 1;
+    for (int i = 0, ie = m_targetBlends.count(); i != ie; ++i) {
+        const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[i]);
+        D3D11_RENDER_TARGET_BLEND_DESC blend = {};
+        blend.BlendEnable = b.enable;
+        blend.SrcBlend = toD3DBlendFactor(b.srcColor, true);
+        blend.DestBlend = toD3DBlendFactor(b.dstColor, true);
+        blend.BlendOp = toD3DBlendOp(b.opColor);
+        blend.SrcBlendAlpha = toD3DBlendFactor(b.srcAlpha, false);
+        blend.DestBlendAlpha = toD3DBlendFactor(b.dstAlpha, false);
+        blend.BlendOpAlpha = toD3DBlendOp(b.opAlpha);
+        blend.RenderTargetWriteMask = toD3DColorWriteMask(b.colorWrite);
+        blendDesc.RenderTarget[i] = blend;
+    }
+    if (m_targetBlends.isEmpty()) {
+        D3D11_RENDER_TARGET_BLEND_DESC blend = {};
+        blend.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
+        blendDesc.RenderTarget[0] = blend;
+    }
+    hr = rhiD->dev->CreateBlendState(&blendDesc, &blendState);
+    if (FAILED(hr)) {
+        qWarning("Failed to create blend state: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+
+    QByteArray vsByteCode;
+    for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) {
+        auto cacheIt = rhiD->m_shaderCache.constFind(shaderStage);
+        if (cacheIt != rhiD->m_shaderCache.constEnd()) {
+            switch (shaderStage.type()) {
+            case QRhiShaderStage::Vertex:
+                vs.shader = static_cast<ID3D11VertexShader *>(cacheIt->s);
+                vs.shader->AddRef();
+                vsByteCode = cacheIt->bytecode;
+                vs.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap;
+                break;
+            case QRhiShaderStage::TessellationControl:
+                hs.shader = static_cast<ID3D11HullShader *>(cacheIt->s);
+                hs.shader->AddRef();
+                hs.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap;
+                break;
+            case QRhiShaderStage::TessellationEvaluation:
+                ds.shader = static_cast<ID3D11DomainShader *>(cacheIt->s);
+                ds.shader->AddRef();
+                ds.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap;
+                break;
+            case QRhiShaderStage::Geometry:
+                gs.shader = static_cast<ID3D11GeometryShader *>(cacheIt->s);
+                gs.shader->AddRef();
+                gs.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap;
+                break;
+            case QRhiShaderStage::Fragment:
+                fs.shader = static_cast<ID3D11PixelShader *>(cacheIt->s);
+                fs.shader->AddRef();
+                fs.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap;
+                break;
+            default:
+                break;
+            }
+        } else {
+            QString error;
+            QShaderKey shaderKey;
+            UINT compileFlags = 0;
+            if (m_flags.testFlag(CompileShadersWithDebugInfo))
+                compileFlags |= D3DCOMPILE_DEBUG;
+
+            const QByteArray bytecode = rhiD->compileHlslShaderSource(shaderStage.shader(), shaderStage.shaderVariant(), compileFlags,
+                                                                      &error, &shaderKey);
+            if (bytecode.isEmpty()) {
+                qWarning("HLSL shader compilation failed: %s", qPrintable(error));
+                return false;
+            }
+
+            if (rhiD->m_shaderCache.count() >= QRhiD3D11::MAX_SHADER_CACHE_ENTRIES) {
+                // Use the simplest strategy: too many cached shaders -> drop them all.
+                rhiD->clearShaderCache();
+            }
+
+            switch (shaderStage.type()) {
+            case QRhiShaderStage::Vertex:
+                hr = rhiD->dev->CreateVertexShader(bytecode.constData(), SIZE_T(bytecode.size()), nullptr, &vs.shader);
+                if (FAILED(hr)) {
+                    qWarning("Failed to create vertex shader: %s",
+                        qPrintable(QSystemError::windowsComString(hr)));
+                    return false;
+                }
+                vsByteCode = bytecode;
+                vs.nativeResourceBindingMap = shaderStage.shader().nativeResourceBindingMap(shaderKey);
+                rhiD->m_shaderCache.insert(shaderStage, QRhiD3D11::Shader(vs.shader, bytecode, vs.nativeResourceBindingMap));
+                vs.shader->AddRef();
+                break;
+            case QRhiShaderStage::TessellationControl:
+                hr = rhiD->dev->CreateHullShader(bytecode.constData(), SIZE_T(bytecode.size()), nullptr, &hs.shader);
+                if (FAILED(hr)) {
+                    qWarning("Failed to create hull shader: %s",
+                        qPrintable(QSystemError::windowsComString(hr)));
+                    return false;
+                }
+                hs.nativeResourceBindingMap = shaderStage.shader().nativeResourceBindingMap(shaderKey);
+                rhiD->m_shaderCache.insert(shaderStage, QRhiD3D11::Shader(hs.shader, bytecode, hs.nativeResourceBindingMap));
+                hs.shader->AddRef();
+                break;
+            case QRhiShaderStage::TessellationEvaluation:
+                hr = rhiD->dev->CreateDomainShader(bytecode.constData(), SIZE_T(bytecode.size()), nullptr, &ds.shader);
+                if (FAILED(hr)) {
+                    qWarning("Failed to create domain shader: %s",
+                        qPrintable(QSystemError::windowsComString(hr)));
+                    return false;
+                }
+                ds.nativeResourceBindingMap = shaderStage.shader().nativeResourceBindingMap(shaderKey);
+                rhiD->m_shaderCache.insert(shaderStage, QRhiD3D11::Shader(ds.shader, bytecode, ds.nativeResourceBindingMap));
+                ds.shader->AddRef();
+                break;
+            case QRhiShaderStage::Geometry:
+                hr = rhiD->dev->CreateGeometryShader(bytecode.constData(), SIZE_T(bytecode.size()), nullptr, &gs.shader);
+                if (FAILED(hr)) {
+                    qWarning("Failed to create geometry shader: %s",
+                        qPrintable(QSystemError::windowsComString(hr)));
+                    return false;
+                }
+                gs.nativeResourceBindingMap = shaderStage.shader().nativeResourceBindingMap(shaderKey);
+                rhiD->m_shaderCache.insert(shaderStage, QRhiD3D11::Shader(gs.shader, bytecode, gs.nativeResourceBindingMap));
+                gs.shader->AddRef();
+                break;
+            case QRhiShaderStage::Fragment:
+                hr = rhiD->dev->CreatePixelShader(bytecode.constData(), SIZE_T(bytecode.size()), nullptr, &fs.shader);
+                if (FAILED(hr)) {
+                    qWarning("Failed to create pixel shader: %s",
+                        qPrintable(QSystemError::windowsComString(hr)));
+                    return false;
+                }
+                fs.nativeResourceBindingMap = shaderStage.shader().nativeResourceBindingMap(shaderKey);
+                rhiD->m_shaderCache.insert(shaderStage, QRhiD3D11::Shader(fs.shader, bytecode, fs.nativeResourceBindingMap));
+                fs.shader->AddRef();
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+    d3dTopology = toD3DTopology(m_topology, m_patchControlPointCount);
+
+    if (!vsByteCode.isEmpty()) {
+        QByteArrayList matrixSliceSemantics;
+        QVarLengthArray<D3D11_INPUT_ELEMENT_DESC, 4> inputDescs;
+        for (auto it = m_vertexInputLayout.cbeginAttributes(), itEnd = m_vertexInputLayout.cendAttributes();
+             it != itEnd; ++it)
+        {
+            D3D11_INPUT_ELEMENT_DESC desc = {};
+            // The output from SPIRV-Cross uses TEXCOORD<location> as the
+            // semantic, except for matrices that are unrolled into consecutive
+            // vec2/3/4s attributes and need TEXCOORD<location>_ as
+            // SemanticName and row/column index as SemanticIndex.
+            const int matrixSlice = it->matrixSlice();
+            if (matrixSlice < 0) {
+                desc.SemanticName = "TEXCOORD";
+                desc.SemanticIndex = UINT(it->location());
+            } else {
+                QByteArray sem;
+                sem.resize(16);
+                qsnprintf(sem.data(), sem.size(), "TEXCOORD%d_", it->location() - matrixSlice);
+                matrixSliceSemantics.append(sem);
+                desc.SemanticName = matrixSliceSemantics.last().constData();
+                desc.SemanticIndex = UINT(matrixSlice);
+            }
+            desc.Format = toD3DAttributeFormat(it->format());
+            desc.InputSlot = UINT(it->binding());
+            desc.AlignedByteOffset = it->offset();
+            const QRhiVertexInputBinding *inputBinding = m_vertexInputLayout.bindingAt(it->binding());
+            if (inputBinding->classification() == QRhiVertexInputBinding::PerInstance) {
+                desc.InputSlotClass = D3D11_INPUT_PER_INSTANCE_DATA;
+                desc.InstanceDataStepRate = inputBinding->instanceStepRate();
+            } else {
+                desc.InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
+            }
+            inputDescs.append(desc);
+        }
+        if (!inputDescs.isEmpty()) {
+            hr = rhiD->dev->CreateInputLayout(inputDescs.constData(), UINT(inputDescs.count()),
+                                              vsByteCode, SIZE_T(vsByteCode.size()), &inputLayout);
+            if (FAILED(hr)) {
+                qWarning("Failed to create input layout: %s",
+                    qPrintable(QSystemError::windowsComString(hr)));
+                return false;
+            }
+        } // else leave inputLayout set to nullptr; that's valid and it avoids a debug layer warning about an input layout with 0 elements
+    }
+
+    rhiD->pipelineCreationEnd();
+    generation += 1;
+    rhiD->registerResource(this);
+    return true;
+}
+
+QD3D11ComputePipeline::QD3D11ComputePipeline(QRhiImplementation *rhi)
+    : QRhiComputePipeline(rhi)
+{
+}
+
+QD3D11ComputePipeline::~QD3D11ComputePipeline()
+{
+    destroy();
+}
+
+void QD3D11ComputePipeline::destroy()
+{
+    if (!cs.shader)
+        return;
+
+    cs.shader->Release();
+    cs.shader = nullptr;
+    cs.nativeResourceBindingMap.clear();
+
+    QRHI_RES_RHI(QRhiD3D11);
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+bool QD3D11ComputePipeline::create()
+{
+    if (cs.shader)
+        destroy();
+
+    QRHI_RES_RHI(QRhiD3D11);
+    rhiD->pipelineCreationStart();
+
+    auto cacheIt = rhiD->m_shaderCache.constFind(m_shaderStage);
+    if (cacheIt != rhiD->m_shaderCache.constEnd()) {
+        cs.shader = static_cast<ID3D11ComputeShader *>(cacheIt->s);
+        cs.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap;
+    } else {
+        QString error;
+        QShaderKey shaderKey;
+        UINT compileFlags = 0;
+        if (m_flags.testFlag(CompileShadersWithDebugInfo))
+            compileFlags |= D3DCOMPILE_DEBUG;
+
+        const QByteArray bytecode = rhiD->compileHlslShaderSource(m_shaderStage.shader(), m_shaderStage.shaderVariant(), compileFlags,
+                                                                  &error, &shaderKey);
+        if (bytecode.isEmpty()) {
+            qWarning("HLSL compute shader compilation failed: %s", qPrintable(error));
+            return false;
+        }
+
+        HRESULT hr = rhiD->dev->CreateComputeShader(bytecode.constData(), SIZE_T(bytecode.size()), nullptr, &cs.shader);
+        if (FAILED(hr)) {
+            qWarning("Failed to create compute shader: %s",
+                qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+
+        cs.nativeResourceBindingMap = m_shaderStage.shader().nativeResourceBindingMap(shaderKey);
+
+        if (rhiD->m_shaderCache.count() >= QRhiD3D11::MAX_SHADER_CACHE_ENTRIES)
+            rhiD->clearShaderCache();
+
+        rhiD->m_shaderCache.insert(m_shaderStage, QRhiD3D11::Shader(cs.shader, bytecode, cs.nativeResourceBindingMap));
+    }
+
+    cs.shader->AddRef();
+
+    rhiD->pipelineCreationEnd();
+    generation += 1;
+    rhiD->registerResource(this);
+    return true;
+}
+
+QD3D11CommandBuffer::QD3D11CommandBuffer(QRhiImplementation *rhi)
+    : QRhiCommandBuffer(rhi)
+{
+    resetState();
+}
+
+QD3D11CommandBuffer::~QD3D11CommandBuffer()
+{
+    destroy();
+}
+
+void QD3D11CommandBuffer::destroy()
+{
+    // nothing to do here
+}
+
+bool QD3D11SwapChainTimestamps::prepare(QRhiD3D11 *rhiD)
+{
+    // Creates the query objects if not yet done, but otherwise calling this
+    // function is expected to be a no-op.
+
+    D3D11_QUERY_DESC queryDesc = {};
+    for (int i = 0; i < TIMESTAMP_PAIRS; ++i) {
+        if (!disjointQuery[i]) {
+            queryDesc.Query = D3D11_QUERY_TIMESTAMP_DISJOINT;
+            HRESULT hr = rhiD->dev->CreateQuery(&queryDesc, &disjointQuery[i]);
+            if (FAILED(hr)) {
+                qWarning("Failed to create timestamp disjoint query: %s",
+                         qPrintable(QSystemError::windowsComString(hr)));
+                return false;
+            }
+        }
+        queryDesc.Query = D3D11_QUERY_TIMESTAMP;
+        for (int j = 0; j < 2; ++j) {
+            const int idx = 2 * i + j;
+            if (!query[idx]) {
+                HRESULT hr = rhiD->dev->CreateQuery(&queryDesc, &query[idx]);
+                if (FAILED(hr)) {
+                    qWarning("Failed to create timestamp query: %s",
+                             qPrintable(QSystemError::windowsComString(hr)));
+                    return false;
+                }
+            }
+        }
+    }
+    return true;
+}
+
+void QD3D11SwapChainTimestamps::destroy()
+{
+    for (int i = 0; i < TIMESTAMP_PAIRS; ++i) {
+        active[i] = false;
+        if (disjointQuery[i]) {
+            disjointQuery[i]->Release();
+            disjointQuery[i] = nullptr;
+        }
+        for (int j = 0; j < 2; ++j) {
+            const int idx = TIMESTAMP_PAIRS * i + j;
+            if (query[idx]) {
+                query[idx]->Release();
+                query[idx] = nullptr;
+            }
+        }
+    }
+}
+
+bool QD3D11SwapChainTimestamps::tryQueryTimestamps(int pairIndex, ID3D11DeviceContext *context, double *elapsedSec)
+{
+    bool result = false;
+    if (!active[pairIndex])
+        return result;
+
+    ID3D11Query *tsDisjoint = disjointQuery[pairIndex];
+    ID3D11Query *tsStart = query[pairIndex * 2];
+    ID3D11Query *tsEnd = query[pairIndex * 2 + 1];
+    quint64 timestamps[2];
+    D3D11_QUERY_DATA_TIMESTAMP_DISJOINT dj;
+
+    bool ok = true;
+    ok &= context->GetData(tsDisjoint, &dj, sizeof(dj), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK;
+    ok &= context->GetData(tsEnd, &timestamps[1], sizeof(quint64), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK;
+    ok &= context->GetData(tsStart, &timestamps[0], sizeof(quint64), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK;
+
+    if (ok) {
+        if (!dj.Disjoint && dj.Frequency) {
+            const float elapsedMs = (timestamps[1] - timestamps[0]) / float(dj.Frequency) * 1000.0f;
+            *elapsedSec = elapsedMs / 1000.0;
+            result = true;
+        }
+        active[pairIndex] = false;
+    } // else leave active set, will retry in a subsequent beginFrame
+
+    return result;
+}
+
+QD3D11SwapChain::QD3D11SwapChain(QRhiImplementation *rhi)
+    : QRhiSwapChain(rhi), rt(rhi, this), rtRight(rhi, this), cb(rhi)
+{
+    backBufferTex = nullptr;
+    backBufferRtv = nullptr;
+    for (int i = 0; i < BUFFER_COUNT; ++i) {
+        msaaTex[i] = nullptr;
+        msaaRtv[i] = nullptr;
+    }
+}
+
+QD3D11SwapChain::~QD3D11SwapChain()
+{
+    destroy();
+}
+
+void QD3D11SwapChain::releaseBuffers()
+{
+    if (backBufferRtv) {
+        backBufferRtv->Release();
+        backBufferRtv = nullptr;
+    }
+    if (backBufferRtvRight) {
+        backBufferRtvRight->Release();
+        backBufferRtvRight = nullptr;
+    }
+    if (backBufferTex) {
+        backBufferTex->Release();
+        backBufferTex = nullptr;
+    }
+    for (int i = 0; i < BUFFER_COUNT; ++i) {
+        if (msaaRtv[i]) {
+            msaaRtv[i]->Release();
+            msaaRtv[i] = nullptr;
+        }
+        if (msaaTex[i]) {
+            msaaTex[i]->Release();
+            msaaTex[i] = nullptr;
+        }
+    }
+}
+
+void QD3D11SwapChain::destroy()
+{
+    if (!swapChain)
+        return;
+
+    releaseBuffers();
+
+    timestamps.destroy();
+
+    swapChain->Release();
+    swapChain = nullptr;
+
+    if (dcompVisual) {
+        dcompVisual->Release();
+        dcompVisual = nullptr;
+    }
+
+    if (dcompTarget) {
+        dcompTarget->Release();
+        dcompTarget = nullptr;
+    }
+
+    QRHI_RES_RHI(QRhiD3D11);
+    if (rhiD) {
+        rhiD->unregisterResource(this);
+        // See Deferred Destruction Issues with Flip Presentation Swap Chains in
+        // https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-flush
+        rhiD->context->Flush();
+    }
+}
+
+QRhiCommandBuffer *QD3D11SwapChain::currentFrameCommandBuffer()
+{
+    return &cb;
+}
+
+QRhiRenderTarget *QD3D11SwapChain::currentFrameRenderTarget()
+{
+    return &rt;
+}
+
+QRhiRenderTarget *QD3D11SwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer)
+{
+    return targetBuffer == StereoTargetBuffer::LeftBuffer? &rt: &rtRight;
+}
+
+QSize QD3D11SwapChain::surfacePixelSize()
+{
+    Q_ASSERT(m_window);
+    return m_window->size() * m_window->devicePixelRatio();
+}
+
+bool QD3D11SwapChain::isFormatSupported(Format f)
+{
+    if (f == SDR)
+        return true;
+
+    if (!m_window) {
+        qWarning("Attempted to call isFormatSupported() without a window set");
+        return false;
+    }
+
+    QRHI_RES_RHI(QRhiD3D11);
+    DXGI_OUTPUT_DESC1 desc1;
+    if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) {
+        if (desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)
+            return f == QRhiSwapChain::HDRExtendedSrgbLinear || f == QRhiSwapChain::HDR10;
+    }
+
+    return false;
+}
+
+QRhiSwapChainHdrInfo QD3D11SwapChain::hdrInfo()
+{
+    QRhiSwapChainHdrInfo info = QRhiSwapChain::hdrInfo();
+    // Must use m_window, not window, given this may be called before createOrResize().
+    if (m_window) {
+        QRHI_RES_RHI(QRhiD3D11);
+        DXGI_OUTPUT_DESC1 hdrOutputDesc;
+        if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) {
+            info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits;
+            info.limits.luminanceInNits.minLuminance = hdrOutputDesc.MinLuminance;
+            info.limits.luminanceInNits.maxLuminance = hdrOutputDesc.MaxLuminance;
+            info.luminanceBehavior = QRhiSwapChainHdrInfo::SceneReferred; // 1.0 = 80 nits
+            info.sdrWhiteLevel = QRhiD3D::sdrWhiteLevelInNits(hdrOutputDesc);
+        }
+    }
+    return info;
+}
+
+QRhiRenderPassDescriptor *QD3D11SwapChain::newCompatibleRenderPassDescriptor()
+{
+    QD3D11RenderPassDescriptor *rpD = new QD3D11RenderPassDescriptor(m_rhi);
+    QRHI_RES_RHI(QRhiD3D11);
+    rhiD->registerResource(rpD, false);
+    return rpD;
+}
+
+bool QD3D11SwapChain::newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI_SAMPLE_DESC sampleDesc,
+                                     ID3D11Texture2D **tex, ID3D11RenderTargetView **rtv) const
+{
+    D3D11_TEXTURE2D_DESC desc = {};
+    desc.Width = UINT(size.width());
+    desc.Height = UINT(size.height());
+    desc.MipLevels = 1;
+    desc.ArraySize = 1;
+    desc.Format = format;
+    desc.SampleDesc = sampleDesc;
+    desc.Usage = D3D11_USAGE_DEFAULT;
+    desc.BindFlags = D3D11_BIND_RENDER_TARGET;
+
+    QRHI_RES_RHI(QRhiD3D11);
+    HRESULT hr = rhiD->dev->CreateTexture2D(&desc, nullptr, tex);
+    if (FAILED(hr)) {
+        qWarning("Failed to create color buffer texture: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+
+    D3D11_RENDER_TARGET_VIEW_DESC rtvDesc = {};
+    rtvDesc.Format = format;
+    rtvDesc.ViewDimension = sampleDesc.Count > 1 ? D3D11_RTV_DIMENSION_TEXTURE2DMS : D3D11_RTV_DIMENSION_TEXTURE2D;
+    hr = rhiD->dev->CreateRenderTargetView(*tex, &rtvDesc, rtv);
+    if (FAILED(hr)) {
+        qWarning("Failed to create color buffer rtv: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        (*tex)->Release();
+        *tex = nullptr;
+        return false;
+    }
+
+    return true;
+}
+
+bool QRhiD3D11::ensureDirectCompositionDevice()
+{
+    if (dcompDevice)
+        return true;
+
+    qCDebug(QRHI_LOG_INFO, "Creating Direct Composition device (needed for semi-transparent windows)");
+    dcompDevice = QRhiD3D::createDirectCompositionDevice();
+    return dcompDevice ? true : false;
+}
+
+static const DXGI_FORMAT DEFAULT_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;
+static const DXGI_FORMAT DEFAULT_SRGB_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
+
+bool QD3D11SwapChain::createOrResize()
+{
+    // Can be called multiple times due to window resizes - that is not the
+    // same as a simple destroy+create (as with other resources). Just need to
+    // resize the buffers then.
+
+    const bool needsRegistration = !window || window != m_window;
+    const bool stereo = m_window->format().stereo();
+
+    // except if the window actually changes
+    if (window && window != m_window)
+        destroy();
+
+    window = m_window;
+    m_currentPixelSize = surfacePixelSize();
+    pixelSize = m_currentPixelSize;
+
+    if (pixelSize.isEmpty())
+        return false;
+
+    HWND hwnd = reinterpret_cast<HWND>(window->winId());
+    HRESULT hr;
+
+    QRHI_RES_RHI(QRhiD3D11);
+
+    if (m_flags.testFlag(SurfaceHasPreMulAlpha) || m_flags.testFlag(SurfaceHasNonPreMulAlpha)) {
+        if (!rhiD->useLegacySwapchainModel && rhiD->ensureDirectCompositionDevice()) {
+            if (!dcompTarget) {
+                hr = rhiD->dcompDevice->CreateTargetForHwnd(hwnd, true, &dcompTarget);
+                if (FAILED(hr)) {
+                    qWarning("Failed to create Direct Compsition target for the window: %s",
+                             qPrintable(QSystemError::windowsComString(hr)));
+                }
+            }
+            if (dcompTarget && !dcompVisual) {
+                hr = rhiD->dcompDevice->CreateVisual(&dcompVisual);
+                if (FAILED(hr)) {
+                    qWarning("Failed to create DirectComposition visual: %s",
+                             qPrintable(QSystemError::windowsComString(hr)));
+                }
+            }
+        }
+        // simple consistency check
+        if (window->requestedFormat().alphaBufferSize() <= 0)
+            qWarning("Swapchain says surface has alpha but the window has no alphaBufferSize set. "
+                     "This may lead to problems.");
+    }
+
+    swapInterval = m_flags.testFlag(QRhiSwapChain::NoVSync) ? 0 : 1;
+    swapChainFlags = 0;
+
+    // A non-flip swapchain can do Present(0) as expected without
+    // ALLOW_TEARING, and ALLOW_TEARING is not compatible with it at all so the
+    // flag must not be set then. Whereas for flip we should use it, if
+    // supported, to get better results for 'unthrottled' presentation.
+    if (swapInterval == 0 && rhiD->supportsAllowTearing)
+        swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
+
+    if (!swapChain) {
+        sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount);
+        colorFormat = DEFAULT_FORMAT;
+        srgbAdjustedColorFormat = m_flags.testFlag(sRGB) ? DEFAULT_SRGB_FORMAT : DEFAULT_FORMAT;
+
+        DXGI_COLOR_SPACE_TYPE hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; // SDR
+        DXGI_OUTPUT_DESC1 hdrOutputDesc;
+        if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc) && m_format != SDR) {
+            // https://docs.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range
+            if (hdrOutputDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
+                switch (m_format) {
+                case HDRExtendedSrgbLinear:
+                    colorFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
+                    hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
+                    srgbAdjustedColorFormat = colorFormat;
+                    break;
+                case HDR10:
+                    colorFormat = DXGI_FORMAT_R10G10B10A2_UNORM;
+                    hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
+                    srgbAdjustedColorFormat = colorFormat;
+                    break;
+                default:
+                    break;
+                }
+            } else {
+                // This happens also when Use HDR is set to Off in the Windows
+                // Display settings. Show a helpful warning, but continue with the
+                // default non-HDR format.
+                qWarning("The output associated with the window is not HDR capable "
+                         "(or Use HDR is Off in the Display Settings), ignoring HDR format request");
+            }
+        }
+
+        // We use a FLIP model swapchain which implies a buffer count of 2
+        // (as opposed to the old DISCARD with back buffer count == 1).
+        // This makes no difference for the rest of the stuff except that
+        // automatic MSAA is unsupported and needs to be implemented via a
+        // custom multisample render target and an explicit resolve.
+
+        DXGI_SWAP_CHAIN_DESC1 desc = {};
+        desc.Width = UINT(pixelSize.width());
+        desc.Height = UINT(pixelSize.height());
+        desc.Format = colorFormat;
+        desc.SampleDesc.Count = 1;
+        desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+        desc.BufferCount = BUFFER_COUNT;
+        desc.Flags = swapChainFlags;
+        desc.Scaling = rhiD->useLegacySwapchainModel ? DXGI_SCALING_STRETCH : DXGI_SCALING_NONE;
+        desc.SwapEffect = rhiD->useLegacySwapchainModel ? DXGI_SWAP_EFFECT_DISCARD : DXGI_SWAP_EFFECT_FLIP_DISCARD;
+        desc.Stereo = stereo;
+
+        if (dcompVisual) {
+            // With DirectComposition setting AlphaMode to STRAIGHT fails the
+            // swapchain creation, whereas the result seems to be identical
+            // with any of the other values, including IGNORE. (?)
+            desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
+
+            // DirectComposition has its own limitations, cannot use
+            // SCALING_NONE. So with semi-transparency requested we are forced
+            // to SCALING_STRETCH.
+            desc.Scaling = DXGI_SCALING_STRETCH;
+        }
+
+        IDXGIFactory2 *fac = static_cast<IDXGIFactory2 *>(rhiD->dxgiFactory);
+        IDXGISwapChain1 *sc1;
+
+        if (dcompVisual)
+            hr = fac->CreateSwapChainForComposition(rhiD->dev, &desc, nullptr, &sc1);
+        else
+            hr = fac->CreateSwapChainForHwnd(rhiD->dev, hwnd, &desc, nullptr, nullptr, &sc1);
+
+        // If failed and we tried a HDR format, then try with SDR. This
+        // matches other backends, such as Vulkan where if the format is
+        // not supported, the default one is used instead.
+        if (FAILED(hr) && m_format != SDR) {
+            colorFormat = DEFAULT_FORMAT;
+            desc.Format = DEFAULT_FORMAT;
+            if (dcompVisual)
+                hr = fac->CreateSwapChainForComposition(rhiD->dev, &desc, nullptr, &sc1);
+            else
+                hr = fac->CreateSwapChainForHwnd(rhiD->dev, hwnd, &desc, nullptr, nullptr, &sc1);
+        }
+
+        if (SUCCEEDED(hr)) {
+            swapChain = sc1;
+            if (m_format != SDR) {
+                IDXGISwapChain3 *sc3 = nullptr;
+                if (SUCCEEDED(sc1->QueryInterface(__uuidof(IDXGISwapChain3), reinterpret_cast<void **>(&sc3)))) {
+                    hr = sc3->SetColorSpace1(hdrColorSpace);
+                    if (FAILED(hr))
+                        qWarning("Failed to set color space on swapchain: %s",
+                            qPrintable(QSystemError::windowsComString(hr)));
+                    sc3->Release();
+                } else {
+                    qWarning("IDXGISwapChain3 not available, HDR swapchain will not work as expected");
+                }
+            }
+            if (dcompVisual) {
+                hr = dcompVisual->SetContent(sc1);
+                if (SUCCEEDED(hr)) {
+                    hr = dcompTarget->SetRoot(dcompVisual);
+                    if (FAILED(hr)) {
+                        qWarning("Failed to associate Direct Composition visual with the target: %s",
+                                 qPrintable(QSystemError::windowsComString(hr)));
+                    }
+                } else {
+                    qWarning("Failed to set content for Direct Composition visual: %s",
+                             qPrintable(QSystemError::windowsComString(hr)));
+                }
+            } else {
+                // disable Alt+Enter; not relevant when using DirectComposition
+                rhiD->dxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
+            }
+        }
+        if (FAILED(hr)) {
+            qWarning("Failed to create D3D11 swapchain: %s"
+                     " (Width=%u Height=%u Format=%u SampleCount=%u BufferCount=%u Scaling=%u SwapEffect=%u Stereo=%u)",
+                     qPrintable(QSystemError::windowsComString(hr)),
+                     desc.Width, desc.Height, UINT(desc.Format), desc.SampleDesc.Count,
+                     desc.BufferCount, UINT(desc.Scaling), UINT(desc.SwapEffect), UINT(desc.Stereo));
+            return false;
+        }
+    } else {
+        releaseBuffers();
+        // flip model -> buffer count is the real buffer count, not 1 like with the legacy modes
+        hr = swapChain->ResizeBuffers(UINT(BUFFER_COUNT), UINT(pixelSize.width()), UINT(pixelSize.height()),
+                                      colorFormat, swapChainFlags);
+        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(QSystemError::windowsComString(hr)));
+            return false;
+        }
+    }
+
+    // This looks odd (for FLIP_*, esp. compared with backends for Vulkan
+    // & co.) but the backbuffer is always at index 0, with magic underneath.
+    // Some explanation from
+    // https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/dxgi-1-4-improvements
+    //
+    // "In Direct3D 11, applications could call GetBuffer( 0, … ) only once.
+    // Every call to Present implicitly changed the resource identity of the
+    // returned interface. Direct3D 12 no longer supports that implicit
+    // resource identity change, due to the CPU overhead required and the
+    // flexible resource descriptor design. As a result, the application must
+    // manually call GetBuffer for every each buffer created with the
+    // swapchain."
+
+    // So just query index 0 once (per resize) and be done with it.
+    hr = swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void **>(&backBufferTex));
+    if (FAILED(hr)) {
+        qWarning("Failed to query swapchain backbuffer: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+    D3D11_RENDER_TARGET_VIEW_DESC rtvDesc = {};
+    rtvDesc.Format = srgbAdjustedColorFormat;
+    rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+    hr = rhiD->dev->CreateRenderTargetView(backBufferTex, &rtvDesc, &backBufferRtv);
+    if (FAILED(hr)) {
+        qWarning("Failed to create rtv for swapchain backbuffer: %s",
+            qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+
+    if (stereo) {
+        // Create a second render target view for the right eye
+        rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
+        rtvDesc.Texture2DArray.FirstArraySlice = 1;
+        rtvDesc.Texture2DArray.ArraySize = 1;
+        hr = rhiD->dev->CreateRenderTargetView(backBufferTex, &rtvDesc, &backBufferRtvRight);
+        if (FAILED(hr)) {
+            qWarning("Failed to create rtv for swapchain backbuffer (right eye): %s",
+                     qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+    }
+
+    // Try to reduce stalls by having a dedicated MSAA texture per swapchain buffer.
+    for (int i = 0; i < BUFFER_COUNT; ++i) {
+        if (sampleDesc.Count > 1) {
+            if (!newColorBuffer(pixelSize, srgbAdjustedColorFormat, sampleDesc, &msaaTex[i], &msaaRtv[i]))
+                return false;
+        }
+    }
+
+    if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
+        qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
+                 m_depthStencil->sampleCount(), m_sampleCount);
+    }
+    if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
+        if (m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly)) {
+            m_depthStencil->setPixelSize(pixelSize);
+            if (!m_depthStencil->create())
+                qWarning("Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d",
+                         pixelSize.width(), pixelSize.height());
+        } else {
+            qWarning("Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems.",
+                     m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
+                     pixelSize.width(), pixelSize.height());
+        }
+    }
+
+    currentFrameSlot = 0;
+    frameCount = 0;
+    ds = m_depthStencil ? QRHI_RES(QD3D11RenderBuffer, m_depthStencil) : nullptr;
+
+    rt.setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget
+    QD3D11SwapChainRenderTarget *rtD = QRHI_RES(QD3D11SwapChainRenderTarget, &rt);
+    rtD->d.rp = QRHI_RES(QD3D11RenderPassDescriptor, m_renderPassDesc);
+    rtD->d.pixelSize = pixelSize;
+    rtD->d.dpr = float(window->devicePixelRatio());
+    rtD->d.sampleCount = int(sampleDesc.Count);
+    rtD->d.colorAttCount = 1;
+    rtD->d.dsAttCount = m_depthStencil ? 1 : 0;
+
+    if (stereo) {
+        rtD = QRHI_RES(QD3D11SwapChainRenderTarget, &rtRight);
+        rtD->d.rp = QRHI_RES(QD3D11RenderPassDescriptor, m_renderPassDesc);
+        rtD->d.pixelSize = pixelSize;
+        rtD->d.dpr = float(window->devicePixelRatio());
+        rtD->d.sampleCount = int(sampleDesc.Count);
+        rtD->d.colorAttCount = 1;
+        rtD->d.dsAttCount = m_depthStencil ? 1 : 0;
+        rtD->d.rtv[0] = backBufferRtvRight;
+        rtD->d.dsv = ds ? ds->dsv : nullptr;
+    }
+
+    if (rhiD->rhiFlags.testFlag(QRhi::EnableTimestamps)) {
+        timestamps.prepare(rhiD);
+        // timestamp queries are optional so we can go on even if they failed
+    }
+
+    if (needsRegistration)
+        rhiD->registerResource(this);
+
+    return true;
+}
+
+QT_END_NAMESPACE
diff --git a/qtbase/src/gui/rhi/qrhid3d11_p.h b/qtbase/src/gui/rhi/qrhid3d11_p.h
index 7644748407..e23529dd24 100644
--- a/qtbase/src/gui/rhi/qrhid3d11_p.h
+++ b/qtbase/src/gui/rhi/qrhid3d11_p.h
@@ -596,7 +596,8 @@ struct QD3D11SwapChain : public QRhiSwapChain
 
     QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
     bool createOrResize() override;
-
+    bool createOrResizeWin7();
+    
     void releaseBuffers();
     bool newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI_SAMPLE_DESC sampleDesc,
                         ID3D11Texture2D **tex, ID3D11RenderTargetView **rtv) const;
diff --git a/qtbase/src/gui/rhi/qrhid3d11_p.h.orig b/qtbase/src/gui/rhi/qrhid3d11_p.h.orig
new file mode 100644
index 0000000000..7644748407
--- /dev/null
+++ b/qtbase/src/gui/rhi/qrhid3d11_p.h.orig
@@ -0,0 +1,866 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QRHID3D11_P_H
+#define QRHID3D11_P_H
+
+//
+//  W A R N I N G
+//  -------------
+//
+// This file is not part of the Qt API.  It exists purely as an
+// implementation detail.  This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhi_p.h"
+#include <rhi/qshaderdescription.h>
+#include <QWindow>
+
+#include <d3d11_1.h>
+#include <dxgi1_6.h>
+#include <dcomp.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRhiD3D11;
+
+struct QD3D11Buffer : public QRhiBuffer
+{
+    QD3D11Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size);
+    ~QD3D11Buffer();
+    void destroy() override;
+    bool create() override;
+    QRhiBuffer::NativeBuffer nativeBuffer() override;
+    char *beginFullDynamicBufferUpdateForCurrentFrame() override;
+    void endFullDynamicBufferUpdateForCurrentFrame() override;
+
+    ID3D11UnorderedAccessView *unorderedAccessView(quint32 offset);
+
+    ID3D11Buffer *buffer = nullptr;
+    char *dynBuf = nullptr;
+    bool hasPendingDynamicUpdates = false;
+    QHash<quint32, ID3D11UnorderedAccessView *> uavs;
+    uint generation = 0;
+    friend class QRhiD3D11;
+};
+
+struct QD3D11RenderBuffer : public QRhiRenderBuffer
+{
+    QD3D11RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize,
+                       int sampleCount, QRhiRenderBuffer::Flags flags,
+                       QRhiTexture::Format backingFormatHint);
+    ~QD3D11RenderBuffer();
+    void destroy() override;
+    bool create() override;
+    QRhiTexture::Format backingFormat() const override;
+
+    ID3D11Texture2D *tex = nullptr;
+    ID3D11DepthStencilView *dsv = nullptr;
+    ID3D11RenderTargetView *rtv = nullptr;
+    DXGI_FORMAT dxgiFormat;
+    DXGI_SAMPLE_DESC sampleDesc;
+    uint generation = 0;
+    friend class QRhiD3D11;
+};
+
+struct QD3D11Texture : public QRhiTexture
+{
+    QD3D11Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
+                  int arraySize, int sampleCount, Flags flags);
+    ~QD3D11Texture();
+    void destroy() override;
+    bool create() override;
+    bool createFrom(NativeTexture src) override;
+    NativeTexture nativeTexture() override;
+
+    bool prepareCreate(QSize *adjustedSize = nullptr);
+    bool finishCreate();
+    ID3D11UnorderedAccessView *unorderedAccessViewForLevel(int level);
+    ID3D11Resource *textureResource() const
+    {
+        if (tex)
+            return tex;
+        else if (tex1D)
+            return tex1D;
+        return tex3D;
+    }
+
+    ID3D11Texture2D *tex = nullptr;
+    ID3D11Texture3D *tex3D = nullptr;
+    ID3D11Texture1D *tex1D = nullptr;
+    bool owns = true;
+    ID3D11ShaderResourceView *srv = nullptr;
+    DXGI_FORMAT dxgiFormat;
+    uint mipLevelCount = 0;
+    DXGI_SAMPLE_DESC sampleDesc;
+    ID3D11UnorderedAccessView *perLevelViews[QRhi::MAX_MIP_LEVELS];
+    uint generation = 0;
+    friend class QRhiD3D11;
+};
+
+struct QD3D11Sampler : public QRhiSampler
+{
+    QD3D11Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+                  AddressMode u, AddressMode v, AddressMode w);
+    ~QD3D11Sampler();
+    void destroy() override;
+    bool create() override;
+
+    ID3D11SamplerState *samplerState = nullptr;
+    uint generation = 0;
+    friend class QRhiD3D11;
+};
+
+struct QD3D11RenderPassDescriptor : public QRhiRenderPassDescriptor
+{
+    QD3D11RenderPassDescriptor(QRhiImplementation *rhi);
+    ~QD3D11RenderPassDescriptor();
+    void destroy() override;
+    bool isCompatible(const QRhiRenderPassDescriptor *other) const override;
+    QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override;
+    QVector<quint32> serializedFormat() const override;
+};
+
+struct QD3D11RenderTargetData
+{
+    QD3D11RenderTargetData(QRhiImplementation *)
+    {
+        for (int i = 0; i < MAX_COLOR_ATTACHMENTS; ++i)
+            rtv[i] = nullptr;
+    }
+
+    QD3D11RenderPassDescriptor *rp = nullptr;
+    QSize pixelSize;
+    float dpr = 1;
+    int sampleCount = 1;
+    int colorAttCount = 0;
+    int dsAttCount = 0;
+
+    static const int MAX_COLOR_ATTACHMENTS = 8;
+    ID3D11RenderTargetView *rtv[MAX_COLOR_ATTACHMENTS];
+    ID3D11DepthStencilView *dsv = nullptr;
+
+    QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList;
+};
+
+struct QD3D11SwapChainRenderTarget : public QRhiSwapChainRenderTarget
+{
+    QD3D11SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain);
+    ~QD3D11SwapChainRenderTarget();
+    void destroy() override;
+
+    QSize pixelSize() const override;
+    float devicePixelRatio() const override;
+    int sampleCount() const override;
+
+    QD3D11RenderTargetData d;
+};
+
+struct QD3D11TextureRenderTarget : public QRhiTextureRenderTarget
+{
+    QD3D11TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags);
+    ~QD3D11TextureRenderTarget();
+    void destroy() override;
+
+    QSize pixelSize() const override;
+    float devicePixelRatio() const override;
+    int sampleCount() const override;
+
+    QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+    bool create() override;
+
+    QD3D11RenderTargetData d;
+    bool ownsRtv[QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS];
+    ID3D11RenderTargetView *rtv[QD3D11RenderTargetData::MAX_COLOR_ATTACHMENTS];
+    bool ownsDsv = false;
+    ID3D11DepthStencilView *dsv = nullptr;
+    friend class QRhiD3D11;
+};
+
+struct QD3D11ShaderResourceBindings : public QRhiShaderResourceBindings
+{
+    QD3D11ShaderResourceBindings(QRhiImplementation *rhi);
+    ~QD3D11ShaderResourceBindings();
+    void destroy() override;
+    bool create() override;
+    void updateResources(UpdateFlags flags) override;
+
+    bool hasDynamicOffset = false;
+    QVarLengthArray<QRhiShaderResourceBinding, 8> sortedBindings;
+    uint generation = 0;
+
+    // Keep track of the generation number of each referenced QRhi* to be able
+    // to detect that the batched bindings are out of date.
+    struct BoundUniformBufferData {
+        quint64 id;
+        uint generation;
+    };
+    struct BoundSampledTextureData {
+        int count;
+        struct {
+            quint64 texId;
+            uint texGeneration;
+            quint64 samplerId;
+            uint samplerGeneration;
+        } d[QRhiShaderResourceBinding::Data::MAX_TEX_SAMPLER_ARRAY_SIZE];
+    };
+    struct BoundStorageImageData {
+        quint64 id;
+        uint generation;
+    };
+    struct BoundStorageBufferData {
+        quint64 id;
+        uint generation;
+    };
+    struct BoundResourceData {
+        union {
+            BoundUniformBufferData ubuf;
+            BoundSampledTextureData stex;
+            BoundStorageImageData simage;
+            BoundStorageBufferData sbuf;
+        };
+    };
+    QVarLengthArray<BoundResourceData, 8> boundResourceData;
+
+    struct StageUniformBufferBatches {
+        bool present = false;
+        QRhiBatchedBindings<ID3D11Buffer *> ubufs;
+        QRhiBatchedBindings<UINT> ubuforigbindings;
+        QRhiBatchedBindings<UINT> ubufoffsets;
+        QRhiBatchedBindings<UINT> ubufsizes;
+        void finish() {
+            present = ubufs.finish();
+            ubuforigbindings.finish();
+            ubufoffsets.finish();
+            ubufsizes.finish();
+        }
+        void clear() {
+            ubufs.clear();
+            ubuforigbindings.clear();
+            ubufoffsets.clear();
+            ubufsizes.clear();
+        }
+    };
+
+    struct StageSamplerBatches {
+        bool present = false;
+        QRhiBatchedBindings<ID3D11SamplerState *> samplers;
+        QRhiBatchedBindings<ID3D11ShaderResourceView *> shaderresources;
+        void finish() {
+            present = samplers.finish();
+            shaderresources.finish();
+        }
+        void clear() {
+            samplers.clear();
+            shaderresources.clear();
+        }
+    };
+
+    struct StageUavBatches {
+        bool present = false;
+        QRhiBatchedBindings<ID3D11UnorderedAccessView *> uavs;
+        void finish() {
+            present = uavs.finish();
+        }
+        void clear() {
+            uavs.clear();
+        }
+    };
+
+    StageUniformBufferBatches vsUniformBufferBatches;
+    StageUniformBufferBatches hsUniformBufferBatches;
+    StageUniformBufferBatches dsUniformBufferBatches;
+    StageUniformBufferBatches gsUniformBufferBatches;
+    StageUniformBufferBatches fsUniformBufferBatches;
+    StageUniformBufferBatches csUniformBufferBatches;
+
+    StageSamplerBatches vsSamplerBatches;
+    StageSamplerBatches hsSamplerBatches;
+    StageSamplerBatches dsSamplerBatches;
+    StageSamplerBatches gsSamplerBatches;
+    StageSamplerBatches fsSamplerBatches;
+    StageSamplerBatches csSamplerBatches;
+
+    StageUavBatches csUavBatches;
+
+    friend class QRhiD3D11;
+};
+
+Q_DECLARE_TYPEINFO(QD3D11ShaderResourceBindings::BoundResourceData, Q_RELOCATABLE_TYPE);
+
+struct QD3D11GraphicsPipeline : public QRhiGraphicsPipeline
+{
+    QD3D11GraphicsPipeline(QRhiImplementation *rhi);
+    ~QD3D11GraphicsPipeline();
+    void destroy() override;
+    bool create() override;
+
+    ID3D11DepthStencilState *dsState = nullptr;
+    ID3D11BlendState *blendState = nullptr;
+    struct {
+        ID3D11VertexShader *shader = nullptr;
+        QShader::NativeResourceBindingMap nativeResourceBindingMap;
+    } vs;
+    struct {
+        ID3D11HullShader *shader = nullptr;
+        QShader::NativeResourceBindingMap nativeResourceBindingMap;
+    } hs;
+    struct {
+        ID3D11DomainShader *shader = nullptr;
+        QShader::NativeResourceBindingMap nativeResourceBindingMap;
+    } ds;
+    struct {
+        ID3D11GeometryShader *shader = nullptr;
+        QShader::NativeResourceBindingMap nativeResourceBindingMap;
+    } gs;
+    struct {
+        ID3D11PixelShader *shader = nullptr;
+        QShader::NativeResourceBindingMap nativeResourceBindingMap;
+    } fs;
+    ID3D11InputLayout *inputLayout = nullptr;
+    D3D11_PRIMITIVE_TOPOLOGY d3dTopology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+    ID3D11RasterizerState *rastState = nullptr;
+    uint generation = 0;
+    friend class QRhiD3D11;
+};
+
+struct QD3D11ComputePipeline : public QRhiComputePipeline
+{
+    QD3D11ComputePipeline(QRhiImplementation *rhi);
+    ~QD3D11ComputePipeline();
+    void destroy() override;
+    bool create() override;
+
+    struct {
+        ID3D11ComputeShader *shader = nullptr;
+        QShader::NativeResourceBindingMap nativeResourceBindingMap;
+    } cs;
+    uint generation = 0;
+    friend class QRhiD3D11;
+};
+
+struct QD3D11SwapChain;
+
+struct QD3D11CommandBuffer : public QRhiCommandBuffer
+{
+    QD3D11CommandBuffer(QRhiImplementation *rhi);
+    ~QD3D11CommandBuffer();
+    void destroy() override;
+
+    // these must be kept at a reasonably low value otherwise sizeof Command explodes
+    static const int MAX_DYNAMIC_OFFSET_COUNT = 8;
+    static const int MAX_VERTEX_BUFFER_BINDING_COUNT = 8;
+
+    struct Command {
+        enum Cmd {
+            BeginFrame,
+            EndFrame,
+            ResetShaderResources,
+            SetRenderTarget,
+            Clear,
+            Viewport,
+            Scissor,
+            BindVertexBuffers,
+            BindIndexBuffer,
+            BindGraphicsPipeline,
+            BindShaderResources,
+            StencilRef,
+            BlendConstants,
+            Draw,
+            DrawIndexed,
+            UpdateSubRes,
+            CopySubRes,
+            ResolveSubRes,
+            GenMip,
+            DebugMarkBegin,
+            DebugMarkEnd,
+            DebugMarkMsg,
+            BindComputePipeline,
+            Dispatch
+        };
+        enum ClearFlag { Color = 1, Depth = 2, Stencil = 4 };
+        Cmd cmd;
+
+        // QRhi*/QD3D11* references should be kept at minimum (so no
+        // QRhiTexture/Buffer/etc. pointers).
+        union Args {
+            struct {
+                ID3D11Query *tsQuery;
+                ID3D11Query *tsDisjointQuery;
+                QD3D11RenderTargetData *swapchainData;
+            } beginFrame;
+            struct {
+                ID3D11Query *tsQuery;
+                ID3D11Query *tsDisjointQuery;
+            } endFrame;
+            struct {
+                QRhiRenderTarget *rt;
+            } setRenderTarget;
+            struct {
+                QRhiRenderTarget *rt;
+                int mask;
+                float c[4];
+                float d;
+                quint32 s;
+            } clear;
+            struct {
+                float x, y, w, h;
+                float d0, d1;
+            } viewport;
+            struct {
+                int x, y, w, h;
+            } scissor;
+            struct {
+                int startSlot;
+                int slotCount;
+                ID3D11Buffer *buffers[MAX_VERTEX_BUFFER_BINDING_COUNT];
+                UINT offsets[MAX_VERTEX_BUFFER_BINDING_COUNT];
+                UINT strides[MAX_VERTEX_BUFFER_BINDING_COUNT];
+            } bindVertexBuffers;
+            struct {
+                ID3D11Buffer *buffer;
+                quint32 offset;
+                DXGI_FORMAT format;
+            } bindIndexBuffer;
+            struct {
+                QD3D11GraphicsPipeline *ps;
+            } bindGraphicsPipeline;
+            struct {
+                QD3D11ShaderResourceBindings *srb;
+                bool offsetOnlyChange;
+                int dynamicOffsetCount;
+                uint dynamicOffsetPairs[MAX_DYNAMIC_OFFSET_COUNT * 2]; // binding, offsetInConstants
+            } bindShaderResources;
+            struct {
+                QD3D11GraphicsPipeline *ps;
+                quint32 ref;
+            } stencilRef;
+            struct {
+                QD3D11GraphicsPipeline *ps;
+                float c[4];
+            } blendConstants;
+            struct {
+                QD3D11GraphicsPipeline *ps;
+                quint32 vertexCount;
+                quint32 instanceCount;
+                quint32 firstVertex;
+                quint32 firstInstance;
+            } draw;
+            struct {
+                QD3D11GraphicsPipeline *ps;
+                quint32 indexCount;
+                quint32 instanceCount;
+                quint32 firstIndex;
+                qint32 vertexOffset;
+                quint32 firstInstance;
+            } drawIndexed;
+            struct {
+                ID3D11Resource *dst;
+                UINT dstSubRes;
+                bool hasDstBox;
+                D3D11_BOX dstBox;
+                const void *src; // must come from retain*()
+                UINT srcRowPitch;
+            } updateSubRes;
+            struct {
+                ID3D11Resource *dst;
+                UINT dstSubRes;
+                UINT dstX;
+                UINT dstY;
+                UINT dstZ;
+                ID3D11Resource *src;
+                UINT srcSubRes;
+                bool hasSrcBox;
+                D3D11_BOX srcBox;
+            } copySubRes;
+            struct {
+                ID3D11Resource *dst;
+                UINT dstSubRes;
+                ID3D11Resource *src;
+                UINT srcSubRes;
+                DXGI_FORMAT format;
+            } resolveSubRes;
+            struct {
+                ID3D11ShaderResourceView *srv;
+            } genMip;
+            struct {
+                char s[64];
+            } debugMark;
+            struct {
+                QD3D11ComputePipeline *ps;
+            } bindComputePipeline;
+            struct {
+                UINT x;
+                UINT y;
+                UINT z;
+            } dispatch;
+        } args;
+    };
+
+    enum PassType {
+        NoPass,
+        RenderPass,
+        ComputePass
+    };
+
+    QRhiBackendCommandList<Command> commands;
+    PassType recordingPass;
+    double lastGpuTime = 0;
+    QRhiRenderTarget *currentTarget;
+    QRhiGraphicsPipeline *currentGraphicsPipeline;
+    QRhiComputePipeline *currentComputePipeline;
+    uint currentPipelineGeneration;
+    QRhiShaderResourceBindings *currentGraphicsSrb;
+    QRhiShaderResourceBindings *currentComputeSrb;
+    uint currentSrbGeneration;
+    ID3D11Buffer *currentIndexBuffer;
+    quint32 currentIndexOffset;
+    DXGI_FORMAT currentIndexFormat;
+    ID3D11Buffer *currentVertexBuffers[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+    quint32 currentVertexOffsets[D3D11_IA_VERTEX_INPUT_RESOURCE_SLOT_COUNT];
+
+    QVarLengthArray<QByteArray, 4> dataRetainPool;
+    QVarLengthArray<QRhiBufferData, 4> bufferDataRetainPool;
+    QVarLengthArray<QImage, 4> imageRetainPool;
+
+    // relies heavily on implicit sharing (no copies of the actual data will be made)
+    const uchar *retainData(const QByteArray &data) {
+        dataRetainPool.append(data);
+        return reinterpret_cast<const uchar *>(dataRetainPool.last().constData());
+    }
+    const uchar *retainBufferData(const QRhiBufferData &data) {
+        bufferDataRetainPool.append(data);
+        return reinterpret_cast<const uchar *>(bufferDataRetainPool.last().constData());
+    }
+    const uchar *retainImage(const QImage &image) {
+        imageRetainPool.append(image);
+        return imageRetainPool.last().constBits();
+    }
+    void resetCommands() {
+        commands.reset();
+        dataRetainPool.clear();
+        bufferDataRetainPool.clear();
+        imageRetainPool.clear();
+    }
+    void resetState() {
+        recordingPass = NoPass;
+        // do not zero lastGpuTime
+        currentTarget = nullptr;
+        resetCommands();
+        resetCachedState();
+    }
+    void resetCachedState() {
+        currentGraphicsPipeline = nullptr;
+        currentComputePipeline = nullptr;
+        currentPipelineGeneration = 0;
+        currentGraphicsSrb = nullptr;
+        currentComputeSrb = nullptr;
+        currentSrbGeneration = 0;
+        currentIndexBuffer = nullptr;
+        currentIndexOffset = 0;
+        currentIndexFormat = DXGI_FORMAT_R16_UINT;
+        memset(currentVertexBuffers, 0, sizeof(currentVertexBuffers));
+        memset(currentVertexOffsets, 0, sizeof(currentVertexOffsets));
+    }
+};
+
+struct QD3D11SwapChainTimestamps
+{
+    static const int TIMESTAMP_PAIRS = 2;
+
+    bool active[TIMESTAMP_PAIRS] = {};
+    ID3D11Query *disjointQuery[TIMESTAMP_PAIRS] = {};
+    ID3D11Query *query[TIMESTAMP_PAIRS * 2] = {};
+
+    bool prepare(QRhiD3D11 *rhiD);
+    void destroy();
+    bool tryQueryTimestamps(int idx, ID3D11DeviceContext *context, double *elapsedSec);
+};
+
+struct QD3D11SwapChain : public QRhiSwapChain
+{
+    QD3D11SwapChain(QRhiImplementation *rhi);
+    ~QD3D11SwapChain();
+    void destroy() override;
+
+    QRhiCommandBuffer *currentFrameCommandBuffer() override;
+    QRhiRenderTarget *currentFrameRenderTarget() override;
+    QRhiRenderTarget *currentFrameRenderTarget(StereoTargetBuffer targetBuffer) override;
+
+    QSize surfacePixelSize() override;
+    bool isFormatSupported(Format f) override;
+    QRhiSwapChainHdrInfo hdrInfo() override;
+
+    QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override;
+    bool createOrResize() override;
+
+    void releaseBuffers();
+    bool newColorBuffer(const QSize &size, DXGI_FORMAT format, DXGI_SAMPLE_DESC sampleDesc,
+                        ID3D11Texture2D **tex, ID3D11RenderTargetView **rtv) const;
+
+    QWindow *window = nullptr;
+    QSize pixelSize;
+    QD3D11SwapChainRenderTarget rt;
+    QD3D11SwapChainRenderTarget rtRight;
+    QD3D11CommandBuffer cb;
+    DXGI_FORMAT colorFormat;
+    DXGI_FORMAT srgbAdjustedColorFormat;
+    IDXGISwapChain *swapChain = nullptr;
+    UINT swapChainFlags = 0;
+    ID3D11Texture2D *backBufferTex;
+    ID3D11RenderTargetView *backBufferRtv;
+    ID3D11RenderTargetView *backBufferRtvRight = nullptr;
+    static const int BUFFER_COUNT = 2;
+    ID3D11Texture2D *msaaTex[BUFFER_COUNT];
+    ID3D11RenderTargetView *msaaRtv[BUFFER_COUNT];
+    DXGI_SAMPLE_DESC sampleDesc;
+    int currentFrameSlot = 0;
+    int frameCount = 0;
+    QD3D11RenderBuffer *ds = nullptr;
+    UINT swapInterval = 1;
+    IDCompositionTarget *dcompTarget = nullptr;
+    IDCompositionVisual *dcompVisual = nullptr;
+    QD3D11SwapChainTimestamps timestamps;
+    int currentTimestampPairIndex = 0;
+};
+
+class QRhiD3D11 : public QRhiImplementation
+{
+public:
+    QRhiD3D11(QRhiD3D11InitParams *params, QRhiD3D11NativeHandles *importDevice = nullptr);
+
+    bool create(QRhi::Flags flags) override;
+    void destroy() override;
+
+    QRhiGraphicsPipeline *createGraphicsPipeline() override;
+    QRhiComputePipeline *createComputePipeline() override;
+    QRhiShaderResourceBindings *createShaderResourceBindings() override;
+    QRhiBuffer *createBuffer(QRhiBuffer::Type type,
+                             QRhiBuffer::UsageFlags usage,
+                             quint32 size) override;
+    QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type,
+                                         const QSize &pixelSize,
+                                         int sampleCount,
+                                         QRhiRenderBuffer::Flags flags,
+                                         QRhiTexture::Format backingFormatHint) override;
+    QRhiTexture *createTexture(QRhiTexture::Format format,
+                               const QSize &pixelSize,
+                               int depth,
+                               int arraySize,
+                               int sampleCount,
+                               QRhiTexture::Flags flags) override;
+    QRhiSampler *createSampler(QRhiSampler::Filter magFilter,
+                               QRhiSampler::Filter minFilter,
+                               QRhiSampler::Filter mipmapMode,
+                               QRhiSampler:: AddressMode u,
+                               QRhiSampler::AddressMode v,
+                               QRhiSampler::AddressMode w) override;
+
+    QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+                                                       QRhiTextureRenderTarget::Flags flags) override;
+
+    QRhiSwapChain *createSwapChain() override;
+    QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override;
+    QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override;
+    QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override;
+    QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override;
+    QRhi::FrameOpResult finish() override;
+
+    void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+    void beginPass(QRhiCommandBuffer *cb,
+                   QRhiRenderTarget *rt,
+                   const QColor &colorClearValue,
+                   const QRhiDepthStencilClearValue &depthStencilClearValue,
+                   QRhiResourceUpdateBatch *resourceUpdates,
+                   QRhiCommandBuffer::BeginPassFlags flags) override;
+    void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+    void setGraphicsPipeline(QRhiCommandBuffer *cb,
+                             QRhiGraphicsPipeline *ps) override;
+
+    void setShaderResources(QRhiCommandBuffer *cb,
+                            QRhiShaderResourceBindings *srb,
+                            int dynamicOffsetCount,
+                            const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override;
+
+    void setVertexInput(QRhiCommandBuffer *cb,
+                        int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+                        QRhiBuffer *indexBuf, quint32 indexOffset,
+                        QRhiCommandBuffer::IndexFormat indexFormat) override;
+
+    void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override;
+    void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override;
+    void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override;
+    void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override;
+
+    void draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+              quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override;
+
+    void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+                     quint32 instanceCount, quint32 firstIndex,
+                     qint32 vertexOffset, quint32 firstInstance) override;
+
+    void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override;
+    void debugMarkEnd(QRhiCommandBuffer *cb) override;
+    void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override;
+
+    void beginComputePass(QRhiCommandBuffer *cb,
+                          QRhiResourceUpdateBatch *resourceUpdates,
+                          QRhiCommandBuffer::BeginPassFlags flags) override;
+    void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override;
+    void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override;
+    void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override;
+
+    const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override;
+    void beginExternal(QRhiCommandBuffer *cb) override;
+    void endExternal(QRhiCommandBuffer *cb) override;
+    double lastCompletedGpuTime(QRhiCommandBuffer *cb) override;
+
+    QList<int> supportedSampleCounts() const override;
+    int ubufAlignment() const override;
+    bool isYUpInFramebuffer() const override;
+    bool isYUpInNDC() const override;
+    bool isClipDepthZeroToOne() const override;
+    QMatrix4x4 clipSpaceCorrMatrix() const override;
+    bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override;
+    bool isFeatureSupported(QRhi::Feature feature) const override;
+    int resourceLimit(QRhi::ResourceLimit limit) const override;
+    const QRhiNativeHandles *nativeHandles() override;
+    QRhiDriverInfo driverInfo() const override;
+    QRhiStats statistics() override;
+    bool makeThreadLocalNativeContextCurrent() override;
+    void releaseCachedResources() override;
+    bool isDeviceLost() const override;
+
+    QByteArray pipelineCacheData() override;
+    void setPipelineCacheData(const QByteArray &data) override;
+
+    void enqueueSubresUpload(QD3D11Texture *texD, QD3D11CommandBuffer *cbD,
+                             int layer, int level, const QRhiTextureSubresourceUploadDescription &subresDesc);
+    void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates);
+    void updateShaderResourceBindings(QD3D11ShaderResourceBindings *srbD,
+                                      const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[]);
+    void executeBufferHostWrites(QD3D11Buffer *bufD);
+    void bindShaderResources(QD3D11ShaderResourceBindings *srbD,
+                             const uint *dynOfsPairs, int dynOfsPairCount,
+                             bool offsetOnlyChange);
+    void resetShaderResources();
+    void executeCommandBuffer(QD3D11CommandBuffer *cbD);
+    DXGI_SAMPLE_DESC effectiveSampleDesc(int sampleCount) const;
+    void finishActiveReadbacks();
+    void reportLiveObjects(ID3D11Device *device);
+    void clearShaderCache();
+    QByteArray compileHlslShaderSource(const QShader &shader, QShader::Variant shaderVariant, uint flags,
+                                       QString *error, QShaderKey *usedShaderKey);
+    bool ensureDirectCompositionDevice();
+
+    QRhi::Flags rhiFlags;
+    bool debugLayer = false;
+    bool importedDeviceAndContext = false;
+    ID3D11Device *dev = nullptr;
+    ID3D11DeviceContext1 *context = nullptr;
+    D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL(0);
+    LUID adapterLuid = {};
+    ID3DUserDefinedAnnotation *annotations = nullptr;
+    IDXGIAdapter1 *activeAdapter = nullptr;
+    IDXGIFactory1 *dxgiFactory = nullptr;
+    IDCompositionDevice *dcompDevice = nullptr;
+    bool supportsAllowTearing = false;
+    bool useLegacySwapchainModel = false;
+    bool deviceLost = false;
+    QRhiD3D11NativeHandles nativeHandlesStruct;
+    QRhiDriverInfo driverInfoStruct;
+
+    struct {
+        int vsHighestActiveVertexBufferBinding = -1;
+        bool vsHasIndexBufferBound = false;
+        int vsHighestActiveSrvBinding = -1;
+        int hsHighestActiveSrvBinding = -1;
+        int dsHighestActiveSrvBinding = -1;
+        int gsHighestActiveSrvBinding = -1;
+        int fsHighestActiveSrvBinding = -1;
+        int csHighestActiveSrvBinding = -1;
+        int csHighestActiveUavBinding = -1;
+        QD3D11SwapChain *currentSwapChain = nullptr;
+    } contextState;
+
+    struct OffscreenFrame {
+        OffscreenFrame(QRhiImplementation *rhi) : cbWrapper(rhi) { }
+        bool active = false;
+        QD3D11CommandBuffer cbWrapper;
+        ID3D11Query *tsQueries[2] = {};
+        ID3D11Query *tsDisjointQuery = nullptr;
+    } ofr;
+
+    struct TextureReadback {
+        QRhiReadbackDescription desc;
+        QRhiReadbackResult *result;
+        ID3D11Texture2D *stagingTex;
+        quint32 byteSize;
+        quint32 bpl;
+        QSize pixelSize;
+        QRhiTexture::Format format;
+    };
+    QVarLengthArray<TextureReadback, 2> activeTextureReadbacks;
+    struct BufferReadback {
+        QRhiReadbackResult *result;
+        quint32 byteSize;
+        ID3D11Buffer *stagingBuf;
+    };
+    QVarLengthArray<BufferReadback, 2> activeBufferReadbacks;
+
+    struct Shader {
+        Shader() = default;
+        Shader(IUnknown *s, const QByteArray &bytecode, const QShader::NativeResourceBindingMap &rbm)
+            : s(s), bytecode(bytecode), nativeResourceBindingMap(rbm) { }
+        IUnknown *s;
+        QByteArray bytecode;
+        QShader::NativeResourceBindingMap nativeResourceBindingMap;
+    };
+    QHash<QRhiShaderStage, Shader> m_shaderCache;
+
+    // This is what gets exposed as the "pipeline cache", not that that concept
+    // applies anyway. Here we are just storing the DX bytecode for a shader so
+    // we can skip the HLSL->DXBC compilation when the QShader has HLSL source
+    // code and the same shader source has already been compiled before.
+    // m_shaderCache seemingly does the same, but this here does not care about
+    // the ID3D11*Shader, this is just about the bytecode and about allowing
+    // the data to be serialized to persistent storage and then reloaded in
+    // future runs of the app, or when creating another QRhi, etc.
+    struct BytecodeCacheKey {
+        QByteArray sourceHash;
+        QByteArray target;
+        QByteArray entryPoint;
+        uint compileFlags;
+    };
+    QHash<BytecodeCacheKey, QByteArray> m_bytecodeCache;
+};
+
+Q_DECLARE_TYPEINFO(QRhiD3D11::TextureReadback, Q_RELOCATABLE_TYPE);
+Q_DECLARE_TYPEINFO(QRhiD3D11::BufferReadback, Q_RELOCATABLE_TYPE);
+
+inline bool operator==(const QRhiD3D11::BytecodeCacheKey &a, const QRhiD3D11::BytecodeCacheKey &b) noexcept
+{
+    return a.sourceHash == b.sourceHash
+            && a.target == b.target
+            && a.entryPoint == b.entryPoint
+            && a.compileFlags == b.compileFlags;
+}
+
+inline bool operator!=(const QRhiD3D11::BytecodeCacheKey &a, const QRhiD3D11::BytecodeCacheKey &b) noexcept
+{
+    return !(a == b);
+}
+
+inline size_t qHash(const QRhiD3D11::BytecodeCacheKey &k, size_t seed = 0) noexcept
+{
+    return qHash(k.sourceHash, seed) ^ qHash(k.target) ^ qHash(k.entryPoint) ^ k.compileFlags;
+}
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/qtbase/src/gui/rhi/qrhid3d12.cpp b/qtbase/src/gui/rhi/qrhid3d12.cpp
index ce9c5dcbdb..f87867353c 100644
--- a/qtbase/src/gui/rhi/qrhid3d12.cpp
+++ b/qtbase/src/gui/rhi/qrhid3d12.cpp
@@ -174,19 +174,35 @@ static inline QD3D12RenderTargetData *rtData(QRhiRenderTarget *rt)
 
 bool QRhiD3D12::create(QRhi::Flags flags)
 {
+    typedef HRESULT(WINAPI* CreateDXGIFactory2Func) (UINT flags, REFIID riid, void** factory);
+    typedef HRESULT(WINAPI* D3D12CreateDeviceFunc) (IUnknown *, D3D_FEATURE_LEVEL, REFIID, void **);
+    typedef HRESULT(WINAPI* D3D12GetDebugInterfaceFunc) (REFIID, void **);
+
+    static CreateDXGIFactory2Func myCreateDXGIFactory2 =
+        (CreateDXGIFactory2Func)::GetProcAddress(::GetModuleHandle(L"dxgi"), "CreateDXGIFactory2");
+
+    static D3D12CreateDeviceFunc myD3D12CreateDevice =
+        (D3D12CreateDeviceFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12CreateDevice");
+
+    static D3D12GetDebugInterfaceFunc myD3D12GetDebugInterface =
+        (D3D12GetDebugInterfaceFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12GetDebugInterface");
+
+    if (!myCreateDXGIFactory2 || !myD3D12CreateDevice || !myD3D12GetDebugInterface)
+        return false;
+
     rhiFlags = flags;
 
     UINT factoryFlags = 0;
     if (debugLayer)
         factoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
-    HRESULT hr = CreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
+    HRESULT hr = myCreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
     if (FAILED(hr)) {
         // retry without debug, if it was requested (to match D3D11 backend behavior)
         if (debugLayer) {
             qCDebug(QRHI_LOG_INFO, "Debug layer was requested but is not available. "
                                    "Attempting to create DXGIFactory2 without it.");
             factoryFlags &= ~DXGI_CREATE_FACTORY_DEBUG;
-            hr = CreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
+            hr = myCreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
         }
         if (SUCCEEDED(hr)) {
             debugLayer = false;
@@ -208,7 +224,7 @@ bool QRhiD3D12::create(QRhi::Flags flags)
 
     if (debugLayer) {
         ID3D12Debug1 *debug = nullptr;
-        if (SUCCEEDED(D3D12GetDebugInterface(__uuidof(ID3D12Debug1), reinterpret_cast<void **>(&debug)))) {
+        if (SUCCEEDED(myD3D12GetDebugInterface(__uuidof(ID3D12Debug1), reinterpret_cast<void **>(&debug)))) {
             qCDebug(QRHI_LOG_INFO, "Enabling D3D12 debug layer");
             debug->EnableDebugLayer();
             debug->Release();
@@ -276,7 +292,7 @@ bool QRhiD3D12::create(QRhi::Flags flags)
         if (minimumFeatureLevel == 0)
             minimumFeatureLevel = MIN_FEATURE_LEVEL;
 
-        hr = D3D12CreateDevice(activeAdapter,
+        hr = myD3D12CreateDevice(activeAdapter,
                                minimumFeatureLevel,
                                __uuidof(ID3D12Device2),
                                reinterpret_cast<void **>(&dev));
@@ -2713,6 +2729,9 @@ QD3D12Descriptor QD3D12SamplerManager::getShaderVisibleDescriptor(const D3D12_SA
     return descriptor;
 }
 
+typedef HRESULT(WINAPI* D3D12SerializeVersionedRootSignatureFunc) (const D3D12_VERSIONED_ROOT_SIGNATURE_DESC *, ID3DBlob **, ID3DBlob **);
+D3D12SerializeVersionedRootSignatureFunc myD3D12SerializeVersionedRootSignature = nullptr;
+
 bool QD3D12MipmapGenerator::create(QRhiD3D12 *rhiD)
 {
     this->rhiD = rhiD;
@@ -2758,8 +2777,15 @@ bool QD3D12MipmapGenerator::create(QRhiD3D12 *rhiD)
     rsDesc.Desc_1_1.NumStaticSamplers = 1;
     rsDesc.Desc_1_1.pStaticSamplers = &samplerDesc;
 
+    if (!myD3D12SerializeVersionedRootSignature)
+        myD3D12SerializeVersionedRootSignature =
+        (D3D12SerializeVersionedRootSignatureFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12SerializeVersionedRootSignature");
+
+    if (!myD3D12SerializeVersionedRootSignature)
+        return false;
+
     ID3DBlob *signature = nullptr;
-    HRESULT hr = D3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
+    HRESULT hr = myD3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
     if (FAILED(hr)) {
         qWarning("Failed to serialize root signature: %s", qPrintable(QSystemError::windowsComString(hr)));
         return false;
@@ -5013,8 +5039,15 @@ QD3D12ObjectHandle QD3D12ShaderResourceBindings::createRootSignature(const QD3D1
     }
     rsDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAGS(rsFlags);
 
+    if (!myD3D12SerializeVersionedRootSignature)
+        myD3D12SerializeVersionedRootSignature =
+        (D3D12SerializeVersionedRootSignatureFunc)::GetProcAddress(::GetModuleHandle(L"D3d12"), "D3D12SerializeVersionedRootSignature");
+
+    if (!myD3D12SerializeVersionedRootSignature)
+        return {};
+
     ID3DBlob *signature = nullptr;
-    HRESULT hr = D3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
+    HRESULT hr = myD3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
     if (FAILED(hr)) {
         qWarning("Failed to serialize root signature: %s", qPrintable(QSystemError::windowsComString(hr)));
         return {};
diff --git a/qtbase/src/gui/rhi/qrhid3d12.cpp.orig b/qtbase/src/gui/rhi/qrhid3d12.cpp.orig
new file mode 100644
index 0000000000..ce9c5dcbdb
--- /dev/null
+++ b/qtbase/src/gui/rhi/qrhid3d12.cpp.orig
@@ -0,0 +1,6497 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qrhid3d12_p.h"
+#include <qmath.h>
+#include <QtCore/private/qsystemerror_p.h>
+#include <comdef.h>
+#include "qrhid3dhelpers_p.h"
+#include "cs_mipmap_p.h"
+
+#if __has_include(<pix.h>)
+#include <pix.h>
+#define QRHI_D3D12_HAS_OLD_PIX
+#endif
+
+#ifdef __ID3D12Device2_INTERFACE_DEFINED__
+
+QT_BEGIN_NAMESPACE
+
+/*
+  Direct 3D 12 backend.
+*/
+
+/*!
+    \class QRhiD3D12InitParams
+    \inmodule QtGui
+    \brief Direct3D 12 specific initialization parameters.
+
+    \note This is a RHI API with limited compatibility guarantees, see \l QRhi
+    for details.
+
+    A D3D12-based QRhi needs no special parameters for initialization. If
+    desired, enableDebugLayer can be set to \c true to enable the Direct3D
+    debug layer. This can be useful during development, but should be avoided
+    in production builds.
+
+    \badcode
+        QRhiD3D12InitParams params;
+        params.enableDebugLayer = true;
+        rhi = QRhi::create(QRhi::D3D12, &params);
+    \endcode
+
+    \note QRhiSwapChain should only be used in combination with QWindow
+    instances that have their surface type set to QSurface::Direct3DSurface.
+
+    \section2 Working with existing Direct3D 12 devices
+
+    When interoperating with another graphics engine, it may be necessary to
+    get a QRhi instance that uses the same Direct3D device. This can be
+    achieved by passing a pointer to a QRhiD3D12NativeHandles to
+    QRhi::create(). QRhi does not take ownership of any of the external
+    objects.
+
+    Sometimes, for example when using QRhi in combination with OpenXR, one will
+    want to specify which adapter to use, and optionally, which feature level
+    to request on the device, while leaving the device creation to QRhi. This
+    is achieved by leaving the device pointer set to null, while specifying the
+    adapter LUID and feature level.
+
+    Optionally the ID3D12CommandQueue can be specified as well, by setting \c
+    commandQueue to a non-null value.
+ */
+
+/*!
+    \variable QRhiD3D12InitParams::enableDebugLayer
+
+    When set to true, the debug layer is enabled, if installed and available.
+    The default value is false.
+*/
+
+/*!
+    \class QRhiD3D12NativeHandles
+    \inmodule QtGui
+    \brief Holds the D3D12 device used by the QRhi.
+
+    \note The class uses \c{void *} as the type since including the COM-based
+    \c{d3d12.h} headers is not acceptable here. The actual types are
+    \c{ID3D12Device *} and \c{ID3D12CommandQueue *}.
+
+    \note This is a RHI API with limited compatibility guarantees, see \l QRhi
+    for details.
+ */
+
+/*!
+    \variable QRhiD3D12NativeHandles::dev
+*/
+
+/*!
+    \variable QRhiD3D12NativeHandles::minimumFeatureLevel
+*/
+
+/*!
+    \variable QRhiD3D12NativeHandles::adapterLuidLow
+*/
+
+/*!
+    \variable QRhiD3D12NativeHandles::adapterLuidHigh
+*/
+
+/*!
+    \variable QRhiD3D12NativeHandles::commandQueue
+*/
+
+/*!
+    \class QRhiD3D12CommandBufferNativeHandles
+    \inmodule QtGui
+    \brief Holds the ID3D12GraphicsCommandList1 object that is backing a QRhiCommandBuffer.
+
+    \note The command list object is only guaranteed to be valid, and
+    in recording state, while recording a frame. That is, between a
+    \l{QRhi::beginFrame()}{beginFrame()} - \l{QRhi::endFrame()}{endFrame()} or
+    \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
+    \l{QRhi::endOffscreenFrame()}{endOffscreenFrame()} pair.
+
+    \note This is a RHI API with limited compatibility guarantees, see \l QRhi
+    for details.
+ */
+
+/*!
+    \variable QRhiD3D12CommandBufferNativeHandles::commandList
+*/
+
+// https://learn.microsoft.com/en-us/windows/win32/direct3d12/hardware-feature-levels
+static const D3D_FEATURE_LEVEL MIN_FEATURE_LEVEL = D3D_FEATURE_LEVEL_11_0;
+
+QRhiD3D12::QRhiD3D12(QRhiD3D12InitParams *params, QRhiD3D12NativeHandles *importParams)
+{
+    debugLayer = params->enableDebugLayer;
+    if (importParams) {
+        if (importParams->dev) {
+            ID3D12Device *d3d12Device = reinterpret_cast<ID3D12Device *>(importParams->dev);
+            if (SUCCEEDED(d3d12Device->QueryInterface(__uuidof(ID3D12Device2), reinterpret_cast<void **>(&dev)))) {
+                // get rid of the ref added by QueryInterface
+                d3d12Device->Release();
+                importedDevice = true;
+            } else {
+                qWarning("ID3D12Device2 not supported, cannot import device");
+            }
+        }
+        if (importParams->commandQueue) {
+            cmdQueue = reinterpret_cast<ID3D12CommandQueue *>(importParams->commandQueue);
+            importedCommandQueue = true;
+        }
+        minimumFeatureLevel = D3D_FEATURE_LEVEL(importParams->minimumFeatureLevel);
+        adapterLuid.LowPart = importParams->adapterLuidLow;
+        adapterLuid.HighPart = importParams->adapterLuidHigh;
+    }
+}
+
+template <class Int>
+inline Int aligned(Int v, Int byteAlign)
+{
+    return (v + byteAlign - 1) & ~(byteAlign - 1);
+}
+
+static inline UINT calcSubresource(UINT mipSlice, UINT arraySlice, UINT mipLevels)
+{
+    return mipSlice + arraySlice * mipLevels;
+}
+
+static inline QD3D12RenderTargetData *rtData(QRhiRenderTarget *rt)
+{
+    switch (rt->resourceType()) {
+    case QRhiResource::SwapChainRenderTarget:
+        return &QRHI_RES(QD3D12SwapChainRenderTarget, rt)->d;
+    case QRhiResource::TextureRenderTarget:
+        return &QRHI_RES(QD3D12TextureRenderTarget, rt)->d;
+        break;
+    default:
+        break;
+    }
+    Q_UNREACHABLE_RETURN(nullptr);
+}
+
+bool QRhiD3D12::create(QRhi::Flags flags)
+{
+    rhiFlags = flags;
+
+    UINT factoryFlags = 0;
+    if (debugLayer)
+        factoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
+    HRESULT hr = CreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
+    if (FAILED(hr)) {
+        // retry without debug, if it was requested (to match D3D11 backend behavior)
+        if (debugLayer) {
+            qCDebug(QRHI_LOG_INFO, "Debug layer was requested but is not available. "
+                                   "Attempting to create DXGIFactory2 without it.");
+            factoryFlags &= ~DXGI_CREATE_FACTORY_DEBUG;
+            hr = CreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
+        }
+        if (SUCCEEDED(hr)) {
+            debugLayer = false;
+        } else {
+            qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s",
+                     qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+    }
+
+    supportsAllowTearing = false;
+    IDXGIFactory5 *factory5 = nullptr;
+    if (SUCCEEDED(dxgiFactory->QueryInterface(__uuidof(IDXGIFactory5), reinterpret_cast<void **>(&factory5)))) {
+        BOOL allowTearing = false;
+        if (SUCCEEDED(factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing))))
+            supportsAllowTearing = allowTearing;
+        factory5->Release();
+    }
+
+    if (debugLayer) {
+        ID3D12Debug1 *debug = nullptr;
+        if (SUCCEEDED(D3D12GetDebugInterface(__uuidof(ID3D12Debug1), reinterpret_cast<void **>(&debug)))) {
+            qCDebug(QRHI_LOG_INFO, "Enabling D3D12 debug layer");
+            debug->EnableDebugLayer();
+            debug->Release();
+        }
+    }
+
+    if (!importedDevice) {
+        IDXGIAdapter1 *adapter;
+        int requestedAdapterIndex = -1;
+        if (qEnvironmentVariableIsSet("QT_D3D_ADAPTER_INDEX"))
+            requestedAdapterIndex = qEnvironmentVariableIntValue("QT_D3D_ADAPTER_INDEX");
+
+        // The importParams may specify an adapter by the luid, take that into account.
+        if (requestedAdapterIndex < 0 && (adapterLuid.LowPart || adapterLuid.HighPart)) {
+            for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
+                DXGI_ADAPTER_DESC1 desc;
+                adapter->GetDesc1(&desc);
+                adapter->Release();
+                if (desc.AdapterLuid.LowPart == adapterLuid.LowPart
+                        && desc.AdapterLuid.HighPart == adapterLuid.HighPart)
+                {
+                    requestedAdapterIndex = adapterIndex;
+                    break;
+                }
+            }
+        }
+
+        if (requestedAdapterIndex < 0 && flags.testFlag(QRhi::PreferSoftwareRenderer)) {
+            for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
+                DXGI_ADAPTER_DESC1 desc;
+                adapter->GetDesc1(&desc);
+                adapter->Release();
+                if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) {
+                    requestedAdapterIndex = adapterIndex;
+                    break;
+                }
+            }
+        }
+
+        activeAdapter = nullptr;
+        for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
+            DXGI_ADAPTER_DESC1 desc;
+            adapter->GetDesc1(&desc);
+            const QString name = QString::fromUtf16(reinterpret_cast<char16_t *>(desc.Description));
+            qCDebug(QRHI_LOG_INFO, "Adapter %d: '%s' (vendor 0x%X device 0x%X flags 0x%X)",
+                    adapterIndex,
+                    qPrintable(name),
+                    desc.VendorId,
+                    desc.DeviceId,
+                    desc.Flags);
+            if (!activeAdapter && (requestedAdapterIndex < 0 || requestedAdapterIndex == adapterIndex)) {
+                activeAdapter = adapter;
+                adapterLuid = desc.AdapterLuid;
+                QRhiD3D::fillDriverInfo(&driverInfoStruct, desc);
+                qCDebug(QRHI_LOG_INFO, "  using this adapter");
+            } else {
+                adapter->Release();
+            }
+        }
+        if (!activeAdapter) {
+            qWarning("No adapter");
+            return false;
+        }
+
+        if (minimumFeatureLevel == 0)
+            minimumFeatureLevel = MIN_FEATURE_LEVEL;
+
+        hr = D3D12CreateDevice(activeAdapter,
+                               minimumFeatureLevel,
+                               __uuidof(ID3D12Device2),
+                               reinterpret_cast<void **>(&dev));
+        if (FAILED(hr)) {
+            qWarning("Failed to create D3D12 device: %s", qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+    } else {
+        Q_ASSERT(dev);
+        // cannot just get a IDXGIDevice from the ID3D12Device anymore, look up the adapter instead
+        adapterLuid = dev->GetAdapterLuid();
+        IDXGIAdapter1 *adapter;
+        for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
+            DXGI_ADAPTER_DESC1 desc;
+            adapter->GetDesc1(&desc);
+            if (desc.AdapterLuid.LowPart == adapterLuid.LowPart
+                    && desc.AdapterLuid.HighPart == adapterLuid.HighPart)
+            {
+                activeAdapter = adapter;
+                QRhiD3D::fillDriverInfo(&driverInfoStruct, desc);
+                break;
+            } else {
+                adapter->Release();
+            }
+        }
+        if (!activeAdapter) {
+            qWarning("No adapter");
+            return false;
+        }
+        qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev);
+    }
+
+    if (debugLayer) {
+        ID3D12InfoQueue *infoQueue;
+        if (SUCCEEDED(dev->QueryInterface(__uuidof(ID3D12InfoQueue), reinterpret_cast<void **>(&infoQueue)))) {
+            if (qEnvironmentVariableIntValue("QT_D3D_DEBUG_BREAK")) {
+                infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true);
+                infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true);
+                infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true);
+            }
+            D3D12_INFO_QUEUE_FILTER filter = {};
+            D3D12_MESSAGE_ID suppressedMessages[2] = {
+                // there is no way of knowing the clear color upfront
+                D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE,
+                // we have no control over viewport and scissor rects
+                D3D12_MESSAGE_ID_DRAW_EMPTY_SCISSOR_RECTANGLE
+            };
+            filter.DenyList.NumIDs = 2;
+            filter.DenyList.pIDList = suppressedMessages;
+            // Setting the filter would enable Info messages (e.g. about
+            // resource creation) which we don't need.
+            D3D12_MESSAGE_SEVERITY infoSev = D3D12_MESSAGE_SEVERITY_INFO;
+            filter.DenyList.NumSeverities = 1;
+            filter.DenyList.pSeverityList = &infoSev;
+            infoQueue->PushStorageFilter(&filter);
+            infoQueue->Release();
+        }
+    }
+
+    if (!importedCommandQueue) {
+        D3D12_COMMAND_QUEUE_DESC queueDesc = {};
+        queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
+        queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;
+        hr = dev->CreateCommandQueue(&queueDesc, __uuidof(ID3D12CommandQueue), reinterpret_cast<void **>(&cmdQueue));
+        if (FAILED(hr)) {
+            qWarning("Failed to create command queue: %s", qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+    }
+
+    hr = dev->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), reinterpret_cast<void **>(&fullFence));
+    if (FAILED(hr)) {
+        qWarning("Failed to create fence: %s", qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+    fullFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+    fullFenceCounter = 0;
+
+    for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
+        hr = dev->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
+                                         __uuidof(ID3D12CommandAllocator),
+                                         reinterpret_cast<void **>(&cmdAllocators[i]));
+        if (FAILED(hr)) {
+            qWarning("Failed to create command allocator: %s", qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+    }
+
+    if (!vma.create(dev, activeAdapter)) {
+        qWarning("Failed to initialize graphics memory suballocator");
+        return false;
+    }
+
+    if (!rtvPool.create(dev, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, "main RTV pool")) {
+        qWarning("Could not create RTV pool");
+        return false;
+    }
+
+    if (!dsvPool.create(dev, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, "main DSV pool")) {
+        qWarning("Could not create DSV pool");
+        return false;
+    }
+
+    if (!cbvSrvUavPool.create(dev, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, "main CBV-SRV-UAV pool")) {
+        qWarning("Could not create CBV-SRV-UAV pool");
+        return false;
+    }
+
+    resourcePool.create("main resource pool");
+    pipelinePool.create("main pipeline pool");
+    rootSignaturePool.create("main root signature pool");
+    releaseQueue.create(&resourcePool, &pipelinePool, &rootSignaturePool);
+    barrierGen.create(&resourcePool);
+
+    if (!samplerMgr.create(dev)) {
+        qWarning("Could not create sampler pool and shader-visible sampler heap");
+        return false;
+    }
+
+    if (!mipmapGen.create(this)) {
+        qWarning("Could not initialize mipmap generator");
+        return false;
+    }
+
+    const qint32 smallStagingSize = aligned(SMALL_STAGING_AREA_BYTES_PER_FRAME, QD3D12StagingArea::ALIGNMENT);
+    for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
+        if (!smallStagingAreas[i].create(this, smallStagingSize, D3D12_HEAP_TYPE_UPLOAD)) {
+            qWarning("Could not create host-visible staging area");
+            return false;
+        }
+        QString decoratedName = QLatin1String("Small staging area buffer/");
+        decoratedName += QString::number(i);
+        smallStagingAreas[i].mem.buffer->SetName(reinterpret_cast<LPCWSTR>(decoratedName.utf16()));
+    }
+
+    if (!shaderVisibleCbvSrvUavHeap.create(dev,
+                                           D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
+                                           SHADER_VISIBLE_CBV_SRV_UAV_HEAP_PER_FRAME_START_SIZE))
+    {
+        qWarning("Could not create first shader-visible CBV/SRV/UAV heap");
+        return false;
+    }
+
+    if (flags.testFlag(QRhi::EnableTimestamps)) {
+        static bool wantsStablePowerState = qEnvironmentVariableIntValue("QT_D3D_STABLE_POWER_STATE");
+        //
+        // https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-setstablepowerstate
+        //
+        // NB! This is a _global_ setting, affecting other processes (and 3D
+        // APIs such as Vulkan), as long as this application is running. Hence
+        // making it an env.var. for now. Never enable it in production. But
+        // extremely useful for the GPU timings with NVIDIA at least; the
+        // timestamps become stable and smooth, making the number readable and
+        // actually useful e.g. in Quick 3D's DebugView when this is enabled.
+        // (otherwise the number's all over the place)
+        //
+        // See also
+        // https://developer.nvidia.com/blog/advanced-api-performance-setstablepowerstate/
+        // for possible other approaches.
+        //
+        if (wantsStablePowerState)
+            dev->SetStablePowerState(TRUE);
+
+        hr = cmdQueue->GetTimestampFrequency(&timestampTicksPerSecond);
+        if (FAILED(hr)) {
+            qWarning("Failed to query timestamp frequency: %s",
+                     qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+        if (!timestampQueryHeap.create(dev, QD3D12_FRAMES_IN_FLIGHT * 2, D3D12_QUERY_HEAP_TYPE_TIMESTAMP)) {
+            qWarning("Failed to create timestamp query pool");
+            return false;
+        }
+        const quint32 readbackBufSize = QD3D12_FRAMES_IN_FLIGHT * 2 * sizeof(quint64);
+        if (!timestampReadbackArea.create(this, readbackBufSize, D3D12_HEAP_TYPE_READBACK)) {
+            qWarning("Failed to create timestamp readback buffer");
+            return false;
+        }
+        timestampReadbackArea.mem.buffer->SetName(L"Timestamp readback buffer");
+        memset(timestampReadbackArea.mem.p, 0, readbackBufSize);
+    }
+
+    D3D12_FEATURE_DATA_D3D12_OPTIONS3 options3 = {};
+    if (SUCCEEDED(dev->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS3, &options3, sizeof(options3))))
+        caps.multiView = options3.ViewInstancingTier != D3D12_VIEW_INSTANCING_TIER_NOT_SUPPORTED;
+
+    deviceLost = false;
+    offscreenActive = false;
+
+    nativeHandlesStruct.dev = dev;
+    nativeHandlesStruct.minimumFeatureLevel = minimumFeatureLevel;
+    nativeHandlesStruct.adapterLuidLow = adapterLuid.LowPart;
+    nativeHandlesStruct.adapterLuidHigh = adapterLuid.HighPart;
+    nativeHandlesStruct.commandQueue = cmdQueue;
+
+    return true;
+}
+
+void QRhiD3D12::destroy()
+{
+    if (!deviceLost && fullFence && fullFenceEvent)
+        waitGpu();
+
+    releaseQueue.releaseAll();
+
+    for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
+        if (offscreenCb[i]) {
+            if (offscreenCb[i]->cmdList)
+                offscreenCb[i]->cmdList->Release();
+            delete offscreenCb[i];
+            offscreenCb[i] = nullptr;
+        }
+    }
+
+    timestampQueryHeap.destroy();
+    timestampReadbackArea.destroy();
+
+    shaderVisibleCbvSrvUavHeap.destroy();
+
+    for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i)
+        smallStagingAreas[i].destroy();
+
+    mipmapGen.destroy();
+    samplerMgr.destroy();
+    resourcePool.destroy();
+    pipelinePool.destroy();
+    rootSignaturePool.destroy();
+    rtvPool.destroy();
+    dsvPool.destroy();
+    cbvSrvUavPool.destroy();
+
+    for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
+        if (cmdAllocators[i]) {
+            cmdAllocators[i]->Release();
+            cmdAllocators[i] = nullptr;
+        }
+    }
+
+    if (fullFenceEvent) {
+        CloseHandle(fullFenceEvent);
+        fullFenceEvent = nullptr;
+    }
+
+    if (fullFence) {
+        fullFence->Release();
+        fullFence = nullptr;
+    }
+
+    if (!importedCommandQueue) {
+        if (cmdQueue) {
+            cmdQueue->Release();
+            cmdQueue = nullptr;
+        }
+    }
+
+    vma.destroy();
+
+    if (!importedDevice) {
+        if (dev) {
+            dev->Release();
+            dev = nullptr;
+        }
+    }
+
+    if (dcompDevice) {
+        dcompDevice->Release();
+        dcompDevice = nullptr;
+    }
+
+    if (activeAdapter) {
+        activeAdapter->Release();
+        activeAdapter = nullptr;
+    }
+
+    if (dxgiFactory) {
+        dxgiFactory->Release();
+        dxgiFactory = nullptr;
+    }
+}
+
+QList<int> QRhiD3D12::supportedSampleCounts() const
+{
+    return { 1, 2, 4, 8 };
+}
+
+QRhiSwapChain *QRhiD3D12::createSwapChain()
+{
+    return new QD3D12SwapChain(this);
+}
+
+QRhiBuffer *QRhiD3D12::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
+{
+    return new QD3D12Buffer(this, type, usage, size);
+}
+
+int QRhiD3D12::ubufAlignment() const
+{
+    return D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT; // 256
+}
+
+bool QRhiD3D12::isYUpInFramebuffer() const
+{
+    return false;
+}
+
+bool QRhiD3D12::isYUpInNDC() const
+{
+    return true;
+}
+
+bool QRhiD3D12::isClipDepthZeroToOne() const
+{
+    return true;
+}
+
+QMatrix4x4 QRhiD3D12::clipSpaceCorrMatrix() const
+{
+    // Like with Vulkan, but Y is already good.
+
+    static QMatrix4x4 m;
+    if (m.isIdentity()) {
+        // NB the ctor takes row-major
+        m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f,
+                       0.0f, 1.0f, 0.0f, 0.0f,
+                       0.0f, 0.0f, 0.5f, 0.5f,
+                       0.0f, 0.0f, 0.0f, 1.0f);
+    }
+    return m;
+}
+
+bool QRhiD3D12::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
+{
+    Q_UNUSED(flags);
+
+    if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ASTC_12x12)
+        return false;
+
+    return true;
+}
+
+bool QRhiD3D12::isFeatureSupported(QRhi::Feature feature) const
+{
+    switch (feature) {
+    case QRhi::MultisampleTexture:
+        return true;
+    case QRhi::MultisampleRenderBuffer:
+        return true;
+    case QRhi::DebugMarkers:
+#ifdef QRHI_D3D12_HAS_OLD_PIX
+        return true;
+#else
+        return false;
+#endif
+    case QRhi::Timestamps:
+        return true;
+    case QRhi::Instancing:
+        return true;
+    case QRhi::CustomInstanceStepRate:
+        return true;
+    case QRhi::PrimitiveRestart:
+        return true;
+    case QRhi::NonDynamicUniformBuffers:
+        return false;
+    case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
+        return true;
+    case QRhi::NPOTTextureRepeat:
+        return true;
+    case QRhi::RedOrAlpha8IsRed:
+        return true;
+    case QRhi::ElementIndexUint:
+        return true;
+    case QRhi::Compute:
+        return true;
+    case QRhi::WideLines:
+        return false;
+    case QRhi::VertexShaderPointSize:
+        return false;
+    case QRhi::BaseVertex:
+        return true;
+    case QRhi::BaseInstance:
+        return true;
+    case QRhi::TriangleFanTopology:
+        return false;
+    case QRhi::ReadBackNonUniformBuffer:
+        return true;
+    case QRhi::ReadBackNonBaseMipLevel:
+        return true;
+    case QRhi::TexelFetch:
+        return true;
+    case QRhi::RenderToNonBaseMipLevel:
+        return true;
+    case QRhi::IntAttributes:
+        return true;
+    case QRhi::ScreenSpaceDerivatives:
+        return true;
+    case QRhi::ReadBackAnyTextureFormat:
+        return true;
+    case QRhi::PipelineCacheDataLoadSave:
+        return false; // ###
+    case QRhi::ImageDataStride:
+        return true;
+    case QRhi::RenderBufferImport:
+        return false;
+    case QRhi::ThreeDimensionalTextures:
+        return true;
+    case QRhi::RenderTo3DTextureSlice:
+        return true;
+    case QRhi::TextureArrays:
+        return true;
+    case QRhi::Tessellation:
+        return true;
+    case QRhi::GeometryShader:
+        return true;
+    case QRhi::TextureArrayRange:
+        return true;
+    case QRhi::NonFillPolygonMode:
+        return true;
+    case QRhi::OneDimensionalTextures:
+        return true;
+    case QRhi::OneDimensionalTextureMipmaps:
+        return false; // we generate mipmaps ourselves with compute and this is not implemented
+    case QRhi::HalfAttributes:
+        return true;
+    case QRhi::RenderToOneDimensionalTexture:
+        return true;
+    case QRhi::ThreeDimensionalTextureMipmaps:
+        return false; // we generate mipmaps ourselves with compute and this is not implemented
+    case QRhi::MultiView:
+        return caps.multiView;
+    }
+    return false;
+}
+
+int QRhiD3D12::resourceLimit(QRhi::ResourceLimit limit) const
+{
+    switch (limit) {
+    case QRhi::TextureSizeMin:
+        return 1;
+    case QRhi::TextureSizeMax:
+        return 16384;
+    case QRhi::MaxColorAttachments:
+        return 8;
+    case QRhi::FramesInFlight:
+        return QD3D12_FRAMES_IN_FLIGHT;
+    case QRhi::MaxAsyncReadbackFrames:
+        return QD3D12_FRAMES_IN_FLIGHT;
+    case QRhi::MaxThreadGroupsPerDimension:
+        return 65535;
+    case QRhi::MaxThreadsPerThreadGroup:
+        return 1024;
+    case QRhi::MaxThreadGroupX:
+        return 1024;
+    case QRhi::MaxThreadGroupY:
+        return 1024;
+    case QRhi::MaxThreadGroupZ:
+        return 1024;
+    case QRhi::TextureArraySizeMax:
+        return 2048;
+    case QRhi::MaxUniformBufferRange:
+        return 65536;
+    case QRhi::MaxVertexInputs:
+        return 32;
+    case QRhi::MaxVertexOutputs:
+        return 32;
+    }
+    return 0;
+}
+
+const QRhiNativeHandles *QRhiD3D12::nativeHandles()
+{
+    return &nativeHandlesStruct;
+}
+
+QRhiDriverInfo QRhiD3D12::driverInfo() const
+{
+    return driverInfoStruct;
+}
+
+QRhiStats QRhiD3D12::statistics()
+{
+    QRhiStats result;
+    result.totalPipelineCreationTime = totalPipelineCreationTime();
+
+    D3D12MA::Budget budgets[2]; // [gpu, system] with discreet GPU or [shared, nothing] with UMA
+    vma.getBudget(&budgets[0], &budgets[1]);
+    for (int i = 0; i < 2; ++i) {
+        const D3D12MA::Statistics &stats(budgets[i].Stats);
+        result.blockCount += stats.BlockCount;
+        result.allocCount += stats.AllocationCount;
+        result.usedBytes += stats.AllocationBytes;
+        result.unusedBytes += stats.BlockBytes - stats.AllocationBytes;
+        result.totalUsageBytes += budgets[i].UsageBytes;
+    }
+
+    return result;
+}
+
+bool QRhiD3D12::makeThreadLocalNativeContextCurrent()
+{
+    // not applicable
+    return false;
+}
+
+void QRhiD3D12::releaseCachedResources()
+{
+    shaderBytecodeCache.data.clear();
+}
+
+bool QRhiD3D12::isDeviceLost() const
+{
+    return deviceLost;
+}
+
+QByteArray QRhiD3D12::pipelineCacheData()
+{
+    return {};
+}
+
+void QRhiD3D12::setPipelineCacheData(const QByteArray &data)
+{
+    Q_UNUSED(data);
+}
+
+QRhiRenderBuffer *QRhiD3D12::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
+                                                int sampleCount, QRhiRenderBuffer::Flags flags,
+                                                QRhiTexture::Format backingFormatHint)
+{
+    return new QD3D12RenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint);
+}
+
+QRhiTexture *QRhiD3D12::createTexture(QRhiTexture::Format format,
+                                      const QSize &pixelSize, int depth, int arraySize,
+                                      int sampleCount, QRhiTexture::Flags flags)
+{
+    return new QD3D12Texture(this, format, pixelSize, depth, arraySize, sampleCount, flags);
+}
+
+QRhiSampler *QRhiD3D12::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
+                                      QRhiSampler::Filter mipmapMode,
+                                      QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w)
+{
+    return new QD3D12Sampler(this, magFilter, minFilter, mipmapMode, u, v, w);
+}
+
+QRhiTextureRenderTarget *QRhiD3D12::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
+                                                              QRhiTextureRenderTarget::Flags flags)
+{
+    return new QD3D12TextureRenderTarget(this, desc, flags);
+}
+
+QRhiGraphicsPipeline *QRhiD3D12::createGraphicsPipeline()
+{
+    return new QD3D12GraphicsPipeline(this);
+}
+
+QRhiComputePipeline *QRhiD3D12::createComputePipeline()
+{
+    return new QD3D12ComputePipeline(this);
+}
+
+QRhiShaderResourceBindings *QRhiD3D12::createShaderResourceBindings()
+{
+    return new QD3D12ShaderResourceBindings(this);
+}
+
+void QRhiD3D12::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
+    QD3D12GraphicsPipeline *psD = QRHI_RES(QD3D12GraphicsPipeline, ps);
+    const bool pipelineChanged = cbD->currentGraphicsPipeline != psD || cbD->currentPipelineGeneration != psD->generation;
+
+    if (pipelineChanged) {
+        cbD->currentGraphicsPipeline = psD;
+        cbD->currentComputePipeline = nullptr;
+        cbD->currentPipelineGeneration = psD->generation;
+
+        if (QD3D12Pipeline *pipeline = pipelinePool.lookupRef(psD->handle)) {
+            Q_ASSERT(pipeline->type == QD3D12Pipeline::Graphics);
+            cbD->cmdList->SetPipelineState(pipeline->pso);
+            if (QD3D12RootSignature *rs = rootSignaturePool.lookupRef(psD->rootSigHandle))
+                cbD->cmdList->SetGraphicsRootSignature(rs->rootSig);
+        }
+
+        cbD->cmdList->IASetPrimitiveTopology(psD->topology);
+
+        if (psD->viewInstanceMask)
+            cbD->cmdList->SetViewInstanceMask(psD->viewInstanceMask);
+    }
+}
+
+void QD3D12CommandBuffer::visitUniformBuffer(QD3D12Stage s,
+                                             const QRhiShaderResourceBinding::Data::UniformBufferData &d,
+                                             int,
+                                             int binding,
+                                             int dynamicOffsetCount,
+                                             const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+    QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, d.buf);
+    quint32 offset = d.offset;
+    if (d.hasDynamicOffset) {
+        for (int i = 0; i < dynamicOffsetCount; ++i) {
+            const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
+            if (dynOfs.first == binding) {
+                Q_ASSERT(aligned(dynOfs.second, 256u) == dynOfs.second);
+                offset += dynOfs.second;
+            }
+        }
+    }
+    QRHI_RES_RHI(QRhiD3D12);
+    visitorData.cbufs[s].append({ bufD->handles[rhiD->currentFrameSlot], offset });
+}
+
+void QD3D12CommandBuffer::visitTexture(QD3D12Stage s,
+                                       const QRhiShaderResourceBinding::TextureAndSampler &d,
+                                       int)
+{
+    QD3D12Texture *texD = QRHI_RES(QD3D12Texture, d.tex);
+    visitorData.srvs[s].append(texD->srv);
+}
+
+void QD3D12CommandBuffer::visitSampler(QD3D12Stage s,
+                                       const QRhiShaderResourceBinding::TextureAndSampler &d,
+                                       int)
+{
+    QD3D12Sampler *samplerD = QRHI_RES(QD3D12Sampler, d.sampler);
+    visitorData.samplers[s].append(samplerD->lookupOrCreateShaderVisibleDescriptor());
+}
+
+void QD3D12CommandBuffer::visitStorageBuffer(QD3D12Stage s,
+                                             const QRhiShaderResourceBinding::Data::StorageBufferData &d,
+                                             QD3D12ShaderResourceVisitor::StorageOp,
+                                             int)
+{
+    QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, d.buf);
+    // SPIRV-Cross generated HLSL uses RWByteAddressBuffer
+    D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
+    uavDesc.Format = DXGI_FORMAT_R32_TYPELESS;
+    uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
+    uavDesc.Buffer.FirstElement = d.offset / 4;
+    uavDesc.Buffer.NumElements = aligned(bufD->m_size - d.offset, 4u) / 4;
+    uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW;
+    visitorData.uavs[s].append({ bufD->handles[0], uavDesc });
+}
+
+void QD3D12CommandBuffer::visitStorageImage(QD3D12Stage s,
+                                            const QRhiShaderResourceBinding::Data::StorageImageData &d,
+                                            QD3D12ShaderResourceVisitor::StorageOp,
+                                            int)
+{
+    QD3D12Texture *texD = QRHI_RES(QD3D12Texture, d.tex);
+    const bool isCube = texD->m_flags.testFlag(QRhiTexture::CubeMap);
+    const bool isArray = texD->m_flags.testFlag(QRhiTexture::TextureArray);
+    const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
+    D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
+    uavDesc.Format = texD->dxgiFormat;
+    if (isCube) {
+        uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
+        uavDesc.Texture2DArray.MipSlice = UINT(d.level);
+        uavDesc.Texture2DArray.FirstArraySlice = 0;
+        uavDesc.Texture2DArray.ArraySize = 6;
+    } else if (isArray) {
+        uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
+        uavDesc.Texture2DArray.MipSlice = UINT(d.level);
+        uavDesc.Texture2DArray.FirstArraySlice = 0;
+        uavDesc.Texture2DArray.ArraySize = UINT(qMax(0, texD->m_arraySize));
+    } else if (is3D) {
+        uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D;
+        uavDesc.Texture3D.MipSlice = UINT(d.level);
+    } else {
+        uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
+        uavDesc.Texture2D.MipSlice = UINT(d.level);
+    }
+    visitorData.uavs[s].append({ texD->handle, uavDesc });
+}
+
+void QRhiD3D12::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
+                                   int dynamicOffsetCount,
+                                   const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass != QD3D12CommandBuffer::NoPass);
+    QD3D12GraphicsPipeline *gfxPsD = QRHI_RES(QD3D12GraphicsPipeline, cbD->currentGraphicsPipeline);
+    QD3D12ComputePipeline *compPsD = QRHI_RES(QD3D12ComputePipeline, cbD->currentComputePipeline);
+
+    if (!srb) {
+        if (gfxPsD)
+            srb = gfxPsD->m_shaderResourceBindings;
+        else
+            srb = compPsD->m_shaderResourceBindings;
+    }
+
+    QD3D12ShaderResourceBindings *srbD = QRHI_RES(QD3D12ShaderResourceBindings, srb);
+
+    for (int i = 0, ie = srbD->m_bindings.size(); i != ie; ++i) {
+        const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->m_bindings[i]);
+        switch (b->type) {
+        case QRhiShaderResourceBinding::UniformBuffer:
+        {
+            QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, b->u.ubuf.buf);
+            Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
+            Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+            bufD->executeHostWritesForFrameSlot(currentFrameSlot);
+        }
+            break;
+        case QRhiShaderResourceBinding::SampledTexture:
+        case QRhiShaderResourceBinding::Texture:
+        case QRhiShaderResourceBinding::Sampler:
+        {
+            const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
+            for (int elem = 0; elem < data->count; ++elem) {
+                QD3D12Texture *texD = QRHI_RES(QD3D12Texture, data->texSamplers[elem].tex);
+                QD3D12Sampler *samplerD = QRHI_RES(QD3D12Sampler, data->texSamplers[elem].sampler);
+                // We use the same code path for both combined and separate
+                // images and samplers, so tex or sampler (but not both) can be
+                // null here.
+                Q_ASSERT(texD || samplerD);
+                if (texD) {
+                    UINT state = 0;
+                    if (b->stage == QRhiShaderResourceBinding::FragmentStage) {
+                        state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
+                    } else if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
+                        state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
+                    } else {
+                        state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
+                    }
+                    barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATES(state));
+                    barrierGen.enqueueBufferedTransitionBarriers(cbD);
+                }
+            }
+        }
+            break;
+        case QRhiShaderResourceBinding::ImageLoad:
+        case QRhiShaderResourceBinding::ImageStore:
+        case QRhiShaderResourceBinding::ImageLoadStore:
+        {
+            QD3D12Texture *texD = QRHI_RES(QD3D12Texture, b->u.simage.tex);
+            if (QD3D12Resource *res = resourcePool.lookupRef(texD->handle)) {
+                if (res->uavUsage) {
+                    if (res->uavUsage & QD3D12Resource::UavUsageWrite) {
+                        // RaW or WaW
+                        barrierGen.enqueueUavBarrier(cbD, texD->handle);
+                    } else {
+                        if (b->type == QRhiShaderResourceBinding::ImageStore
+                                || b->type == QRhiShaderResourceBinding::ImageLoadStore)
+                        {
+                            // WaR or WaW
+                            barrierGen.enqueueUavBarrier(cbD, texD->handle);
+                        }
+                    }
+                }
+                res->uavUsage = 0;
+                if (b->type == QRhiShaderResourceBinding::ImageLoad || b->type == QRhiShaderResourceBinding::ImageLoadStore)
+                    res->uavUsage |= QD3D12Resource::UavUsageRead;
+                if (b->type == QRhiShaderResourceBinding::ImageStore || b->type == QRhiShaderResourceBinding::ImageLoadStore)
+                    res->uavUsage |= QD3D12Resource::UavUsageWrite;
+                barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
+                barrierGen.enqueueBufferedTransitionBarriers(cbD);
+            }
+        }
+            break;
+        case QRhiShaderResourceBinding::BufferLoad:
+        case QRhiShaderResourceBinding::BufferStore:
+        case QRhiShaderResourceBinding::BufferLoadStore:
+        {
+            QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, b->u.sbuf.buf);
+            Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
+            Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
+            if (QD3D12Resource *res = resourcePool.lookupRef(bufD->handles[0])) {
+                if (res->uavUsage) {
+                    if (res->uavUsage & QD3D12Resource::UavUsageWrite) {
+                        // RaW or WaW
+                        barrierGen.enqueueUavBarrier(cbD, bufD->handles[0]);
+                    } else {
+                        if (b->type == QRhiShaderResourceBinding::BufferStore
+                                || b->type == QRhiShaderResourceBinding::BufferLoadStore)
+                        {
+                            // WaR or WaW
+                            barrierGen.enqueueUavBarrier(cbD, bufD->handles[0]);
+                        }
+                    }
+                }
+                res->uavUsage = 0;
+                if (b->type == QRhiShaderResourceBinding::BufferLoad || b->type == QRhiShaderResourceBinding::BufferLoadStore)
+                    res->uavUsage |= QD3D12Resource::UavUsageRead;
+                if (b->type == QRhiShaderResourceBinding::BufferStore || b->type == QRhiShaderResourceBinding::BufferLoadStore)
+                    res->uavUsage |= QD3D12Resource::UavUsageWrite;
+                barrierGen.addTransitionBarrier(bufD->handles[0], D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
+                barrierGen.enqueueBufferedTransitionBarriers(cbD);
+            }
+        }
+            break;
+        }
+    }
+
+    const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
+    const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
+
+    if (srbChanged || srbRebuilt || srbD->hasDynamicOffset) {
+        const QD3D12ShaderStageData *stageData = gfxPsD ? gfxPsD->stageData.data() : &compPsD->stageData;
+
+        // The order of root parameters must match
+        // QD3D12ShaderResourceBindings::createRootSignature(), meaning the
+        // logic below must mirror that function (uniform buffers first etc.)
+
+        QD3D12ShaderResourceVisitor visitor(srbD, stageData, gfxPsD ? 5 : 1);
+
+        QD3D12CommandBuffer::VisitorData &visitorData(cbD->visitorData);
+        visitorData = {};
+
+        using namespace std::placeholders;
+        visitor.uniformBuffer = std::bind(&QD3D12CommandBuffer::visitUniformBuffer, cbD, _1, _2, _3, _4, dynamicOffsetCount, dynamicOffsets);
+        visitor.texture = std::bind(&QD3D12CommandBuffer::visitTexture, cbD, _1, _2, _3);
+        visitor.sampler = std::bind(&QD3D12CommandBuffer::visitSampler, cbD, _1, _2, _3);
+        visitor.storageBuffer = std::bind(&QD3D12CommandBuffer::visitStorageBuffer, cbD, _1, _2, _3, _4);
+        visitor.storageImage = std::bind(&QD3D12CommandBuffer::visitStorageImage, cbD, _1, _2, _3, _4);
+
+        visitor.visit();
+
+        quint32 cbvSrvUavCount = 0;
+        for (int s = 0; s < 6; ++s) {
+            // CBs use root constant buffer views, no need to count them here
+            cbvSrvUavCount += visitorData.srvs[s].count();
+            cbvSrvUavCount += visitorData.uavs[s].count();
+        }
+
+        bool gotNewHeap = false;
+        if (!ensureShaderVisibleDescriptorHeapCapacity(&shaderVisibleCbvSrvUavHeap,
+                                                       D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
+                                                       currentFrameSlot,
+                                                       cbvSrvUavCount,
+                                                       &gotNewHeap))
+        {
+            return;
+        }
+        if (gotNewHeap) {
+            qCDebug(QRHI_LOG_INFO, "Created new shader-visible CBV/SRV/UAV descriptor heap,"
+                    " per-frame slice size is now %u,"
+                    " if this happens frequently then that's not great.",
+                    shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[0].capacity);
+            bindShaderVisibleHeaps(cbD);
+        }
+
+        int rootParamIndex = 0;
+        for (int s = 0; s < 6; ++s) {
+            if (!visitorData.cbufs[s].isEmpty()) {
+                for (int i = 0, count = visitorData.cbufs[s].count(); i < count; ++i) {
+                    const auto &cbuf(visitorData.cbufs[s][i]);
+                    if (QD3D12Resource *res = resourcePool.lookupRef(cbuf.first)) {
+                        quint32 offset = cbuf.second;
+                        D3D12_GPU_VIRTUAL_ADDRESS gpuAddr = res->resource->GetGPUVirtualAddress() + offset;
+                        if (cbD->currentGraphicsPipeline)
+                            cbD->cmdList->SetGraphicsRootConstantBufferView(rootParamIndex, gpuAddr);
+                        else
+                            cbD->cmdList->SetComputeRootConstantBufferView(rootParamIndex, gpuAddr);
+                    }
+                    rootParamIndex += 1;
+                }
+            }
+        }
+        for (int s = 0; s < 6; ++s) {
+            if (!visitorData.srvs[s].isEmpty()) {
+                QD3D12DescriptorHeap &gpuSrvHeap(shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot]);
+                QD3D12Descriptor startDesc = gpuSrvHeap.get(visitorData.srvs[s].count());
+                for (int i = 0, count = visitorData.srvs[s].count(); i < count; ++i) {
+                    const auto &srv(visitorData.srvs[s][i]);
+                    dev->CopyDescriptorsSimple(1, gpuSrvHeap.incremented(startDesc, i).cpuHandle, srv.cpuHandle,
+                                               D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
+                }
+
+                if (cbD->currentGraphicsPipeline)
+                    cbD->cmdList->SetGraphicsRootDescriptorTable(rootParamIndex, startDesc.gpuHandle);
+                else if (cbD->currentComputePipeline)
+                    cbD->cmdList->SetComputeRootDescriptorTable(rootParamIndex, startDesc.gpuHandle);
+
+                rootParamIndex += 1;
+            }
+        }
+        for (int s = 0; s < 6; ++s) {
+            // Samplers are one parameter / descriptor table each, and the
+            // descriptor is from the shader visible sampler heap already.
+            for (const QD3D12Descriptor &samplerDescriptor : visitorData.samplers[s]) {
+                if (cbD->currentGraphicsPipeline)
+                    cbD->cmdList->SetGraphicsRootDescriptorTable(rootParamIndex, samplerDescriptor.gpuHandle);
+                else if (cbD->currentComputePipeline)
+                    cbD->cmdList->SetComputeRootDescriptorTable(rootParamIndex, samplerDescriptor.gpuHandle);
+
+                rootParamIndex += 1;
+            }
+        }
+        for (int s = 0; s < 6; ++s) {
+            if (!visitorData.uavs[s].isEmpty()) {
+                QD3D12DescriptorHeap &gpuUavHeap(shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot]);
+                QD3D12Descriptor startDesc = gpuUavHeap.get(visitorData.uavs[s].count());
+                for (int i = 0, count = visitorData.uavs[s].count(); i < count; ++i) {
+                    const auto &uav(visitorData.uavs[s][i]);
+                    if (QD3D12Resource *res = resourcePool.lookupRef(uav.first)) {
+                        dev->CreateUnorderedAccessView(res->resource, nullptr, &uav.second,
+                                                       gpuUavHeap.incremented(startDesc, i).cpuHandle);
+                    } else {
+                        dev->CreateUnorderedAccessView(nullptr, nullptr, nullptr,
+                                                       gpuUavHeap.incremented(startDesc, i).cpuHandle);
+                    }
+                }
+
+                if (cbD->currentGraphicsPipeline)
+                    cbD->cmdList->SetGraphicsRootDescriptorTable(rootParamIndex, startDesc.gpuHandle);
+                else if (cbD->currentComputePipeline)
+                    cbD->cmdList->SetComputeRootDescriptorTable(rootParamIndex, startDesc.gpuHandle);
+
+                rootParamIndex += 1;
+            }
+        }
+
+        if (gfxPsD) {
+            cbD->currentGraphicsSrb = srb;
+            cbD->currentComputeSrb = nullptr;
+        } else {
+            cbD->currentGraphicsSrb = nullptr;
+            cbD->currentComputeSrb = srb;
+        }
+        cbD->currentSrbGeneration = srbD->generation;
+    }
+}
+
+void QRhiD3D12::setVertexInput(QRhiCommandBuffer *cb,
+                               int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
+                               QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
+
+    bool needsBindVBuf = false;
+    for (int i = 0; i < bindingCount; ++i) {
+        const int inputSlot = startBinding + i;
+        QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, bindings[i].first);
+        Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer));
+        const bool isDynamic = bufD->m_type == QRhiBuffer::Dynamic;
+        if (isDynamic)
+            bufD->executeHostWritesForFrameSlot(currentFrameSlot);
+
+        if (cbD->currentVertexBuffers[inputSlot] != bufD->handles[isDynamic ? currentFrameSlot : 0]
+                || cbD->currentVertexOffsets[inputSlot] != bindings[i].second)
+        {
+            needsBindVBuf = true;
+            cbD->currentVertexBuffers[inputSlot] = bufD->handles[isDynamic ? currentFrameSlot : 0];
+            cbD->currentVertexOffsets[inputSlot] = bindings[i].second;
+        }
+    }
+
+    if (needsBindVBuf) {
+        QVarLengthArray<D3D12_VERTEX_BUFFER_VIEW, 4> vbv;
+        vbv.reserve(bindingCount);
+
+        QD3D12GraphicsPipeline *psD = cbD->currentGraphicsPipeline;
+        const QRhiVertexInputLayout &inputLayout(psD->m_vertexInputLayout);
+        const int inputBindingCount = inputLayout.cendBindings() - inputLayout.cbeginBindings();
+
+        for (int i = 0, ie = qMin(bindingCount, inputBindingCount); i != ie; ++i) {
+            QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, bindings[i].first);
+            const QD3D12ObjectHandle handle = bufD->handles[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0];
+            const quint32 offset = bindings[i].second;
+            const quint32 stride = inputLayout.bindingAt(i)->stride();
+
+            if (bufD->m_type != QRhiBuffer::Dynamic) {
+                barrierGen.addTransitionBarrier(handle, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
+                barrierGen.enqueueBufferedTransitionBarriers(cbD);
+            }
+
+            if (QD3D12Resource *res = resourcePool.lookupRef(handle)) {
+                vbv.append({
+                    res->resource->GetGPUVirtualAddress() + offset,
+                    UINT(res->desc.Width - offset),
+                    stride
+                });
+            }
+        }
+
+        cbD->cmdList->IASetVertexBuffers(UINT(startBinding), vbv.count(), vbv.constData());
+    }
+
+    if (indexBuf) {
+        QD3D12Buffer *ibufD = QRHI_RES(QD3D12Buffer, indexBuf);
+        Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer));
+        const bool isDynamic = ibufD->m_type == QRhiBuffer::Dynamic;
+        if (isDynamic)
+            ibufD->executeHostWritesForFrameSlot(currentFrameSlot);
+
+        const DXGI_FORMAT dxgiFormat = indexFormat == QRhiCommandBuffer::IndexUInt16 ? DXGI_FORMAT_R16_UINT
+                                                                                     : DXGI_FORMAT_R32_UINT;
+        if (cbD->currentIndexBuffer != ibufD->handles[isDynamic ? currentFrameSlot : 0]
+                || cbD->currentIndexOffset != indexOffset
+                || cbD->currentIndexFormat != dxgiFormat)
+        {
+            cbD->currentIndexBuffer = ibufD->handles[isDynamic ? currentFrameSlot : 0];
+            cbD->currentIndexOffset = indexOffset;
+            cbD->currentIndexFormat = dxgiFormat;
+
+            if (ibufD->m_type != QRhiBuffer::Dynamic) {
+                barrierGen.addTransitionBarrier(cbD->currentIndexBuffer, D3D12_RESOURCE_STATE_INDEX_BUFFER);
+                barrierGen.enqueueBufferedTransitionBarriers(cbD);
+            }
+
+            if (QD3D12Resource *res = resourcePool.lookupRef(cbD->currentIndexBuffer)) {
+                const D3D12_INDEX_BUFFER_VIEW ibv = {
+                    res->resource->GetGPUVirtualAddress() + indexOffset,
+                    UINT(res->desc.Width - indexOffset),
+                    dxgiFormat
+                };
+                cbD->cmdList->IASetIndexBuffer(&ibv);
+            }
+        }
+    }
+}
+
+void QRhiD3D12::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
+    Q_ASSERT(cbD->currentTarget);
+    const QSize outputSize = cbD->currentTarget->pixelSize();
+
+    // D3D expects top-left, QRhiViewport is bottom-left
+    float x, y, w, h;
+    if (!qrhi_toTopLeftRenderTargetRect<UnBounded>(outputSize, viewport.viewport(), &x, &y, &w, &h))
+        return;
+
+    D3D12_VIEWPORT v;
+    v.TopLeftX = x;
+    v.TopLeftY = y;
+    v.Width = w;
+    v.Height = h;
+    v.MinDepth = viewport.minDepth();
+    v.MaxDepth = viewport.maxDepth();
+    cbD->cmdList->RSSetViewports(1, &v);
+
+    if (cbD->currentGraphicsPipeline
+            && !cbD->currentGraphicsPipeline->flags().testFlag(QRhiGraphicsPipeline::UsesScissor))
+    {
+        qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, viewport.viewport(), &x, &y, &w, &h);
+        D3D12_RECT r;
+        r.left = x;
+        r.top = y;
+        // right and bottom are exclusive
+        r.right = x + w;
+        r.bottom = y + h;
+        cbD->cmdList->RSSetScissorRects(1, &r);
+    }
+}
+
+void QRhiD3D12::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
+    Q_ASSERT(cbD->currentTarget);
+    const QSize outputSize = cbD->currentTarget->pixelSize();
+
+    // D3D expects top-left, QRhiScissor is bottom-left
+    int x, y, w, h;
+    if (!qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, scissor.scissor(), &x, &y, &w, &h))
+        return;
+
+    D3D12_RECT r;
+    r.left = x;
+    r.top = y;
+    // right and bottom are exclusive
+    r.right = x + w;
+    r.bottom = y + h;
+    cbD->cmdList->RSSetScissorRects(1, &r);
+}
+
+void QRhiD3D12::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
+    float v[4] = { c.redF(), c.greenF(), c.blueF(), c.alphaF() };
+    cbD->cmdList->OMSetBlendFactor(v);
+}
+
+void QRhiD3D12::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
+    cbD->cmdList->OMSetStencilRef(refValue);
+}
+
+void QRhiD3D12::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
+                     quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
+    cbD->cmdList->DrawInstanced(vertexCount, instanceCount, firstVertex, firstInstance);
+}
+
+void QRhiD3D12::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
+                            quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
+    cbD->cmdList->DrawIndexedInstanced(indexCount, instanceCount,
+                                       firstIndex, vertexOffset,
+                                       firstInstance);
+}
+
+void QRhiD3D12::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
+{
+    if (!debugMarkers)
+        return;
+
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+#ifdef QRHI_D3D12_HAS_OLD_PIX
+    PIXBeginEvent(cbD->cmdList, PIX_COLOR_DEFAULT, reinterpret_cast<LPCWSTR>(QString::fromLatin1(name).utf16()));
+#else
+    Q_UNUSED(cbD);
+    Q_UNUSED(name);
+#endif
+}
+
+void QRhiD3D12::debugMarkEnd(QRhiCommandBuffer *cb)
+{
+    if (!debugMarkers)
+        return;
+
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+#ifdef QRHI_D3D12_HAS_OLD_PIX
+    PIXEndEvent(cbD->cmdList);
+#else
+    Q_UNUSED(cbD);
+#endif
+}
+
+void QRhiD3D12::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
+{
+    if (!debugMarkers)
+        return;
+
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+#ifdef QRHI_D3D12_HAS_OLD_PIX
+    PIXSetMarker(cbD->cmdList, PIX_COLOR_DEFAULT, reinterpret_cast<LPCWSTR>(QString::fromLatin1(msg).utf16()));
+#else
+    Q_UNUSED(cbD);
+    Q_UNUSED(msg);
+#endif
+}
+
+const QRhiNativeHandles *QRhiD3D12::nativeHandles(QRhiCommandBuffer *cb)
+{
+    return QRHI_RES(QD3D12CommandBuffer, cb)->nativeHandles();
+}
+
+void QRhiD3D12::beginExternal(QRhiCommandBuffer *cb)
+{
+    Q_UNUSED(cb);
+}
+
+void QRhiD3D12::endExternal(QRhiCommandBuffer *cb)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    cbD->resetPerPassState();
+    bindShaderVisibleHeaps(cbD);
+    if (cbD->currentTarget) { // could be compute, no rendertarget then
+        QD3D12RenderTargetData *rtD = rtData(cbD->currentTarget);
+        cbD->cmdList->OMSetRenderTargets(UINT(rtD->colorAttCount),
+                                         rtD->rtv,
+                                         TRUE,
+                                         rtD->dsAttCount ? &rtD->dsv : nullptr);
+    }
+}
+
+double QRhiD3D12::lastCompletedGpuTime(QRhiCommandBuffer *cb)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    return cbD->lastGpuTime;
+}
+
+static void calculateGpuTime(QD3D12CommandBuffer *cbD,
+                             int timestampPairStartIndex,
+                             const quint8 *readbackBufPtr,
+                             quint64 timestampTicksPerSecond)
+{
+    const size_t byteOffset = timestampPairStartIndex * sizeof(quint64);
+    const quint64 *p = reinterpret_cast<const quint64 *>(readbackBufPtr + byteOffset);
+    const quint64 startTime = *p++;
+    const quint64 endTime = *p;
+    if (startTime < endTime) {
+        const quint64 ticks = endTime - startTime;
+        const double timeSec = ticks / double(timestampTicksPerSecond);
+        cbD->lastGpuTime = timeSec;
+    }
+}
+
+QRhi::FrameOpResult QRhiD3D12::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
+{
+    Q_UNUSED(flags);
+
+    QD3D12SwapChain *swapChainD = QRHI_RES(QD3D12SwapChain, swapChain);
+    currentSwapChain = swapChainD;
+    currentFrameSlot = swapChainD->currentFrameSlot;
+    QD3D12SwapChain::FrameResources &fr(swapChainD->frameRes[currentFrameSlot]);
+
+    // We could do smarter things but mirror the Vulkan backend for now: Make
+    // sure the previous commands for this same frame slot have finished. Do
+    // this also for any other swapchain's commands with the same frame slot.
+    // While this reduces concurrency in render-to-swapchain-A,
+    // render-to-swapchain-B, repeat kind of scenarios, it keeps resource usage
+    // safe: swapchain A starting its frame 0, followed by swapchain B starting
+    // its own frame 0 will make B wait for A's frame 0 commands. If a resource
+    // is written in B's frame or when B checks for pending resource releases,
+    // that won't mess up A's in-flight commands (as they are guaranteed not to
+    // be in flight anymore). With Qt Quick this situation cannot happen anyway
+    // by design (one QRhi per window).
+    for (QD3D12SwapChain *sc : std::as_const(swapchains))
+        sc->waitCommandCompletionForFrameSlot(currentFrameSlot); // note: swapChainD->currentFrameSlot, not sc's
+
+    HRESULT hr = cmdAllocators[currentFrameSlot]->Reset();
+    if (FAILED(hr)) {
+        qWarning("Failed to reset command allocator: %s",
+                 qPrintable(QSystemError::windowsComString(hr)));
+        return QRhi::FrameOpError;
+    }
+
+    if (!startCommandListForCurrentFrameSlot(&fr.cmdList))
+        return QRhi::FrameOpError;
+
+    QD3D12CommandBuffer *cbD = &swapChainD->cbWrapper;
+    cbD->cmdList = fr.cmdList;
+
+    swapChainD->rtWrapper.d.rtv[0] = swapChainD->sampleDesc.Count > 1
+            ? swapChainD->msaaRtvs[swapChainD->currentBackBufferIndex].cpuHandle
+            : swapChainD->rtvs[swapChainD->currentBackBufferIndex].cpuHandle;
+
+    swapChainD->rtWrapper.d.dsv = swapChainD->ds ? swapChainD->ds->dsv.cpuHandle
+                                                 : D3D12_CPU_DESCRIPTOR_HANDLE { 0 };
+
+    if (swapChainD->stereo) {
+        swapChainD->rtWrapperRight.d.rtv[0] = swapChainD->sampleDesc.Count > 1
+                ? swapChainD->msaaRtvs[swapChainD->currentBackBufferIndex].cpuHandle
+                : swapChainD->rtvsRight[swapChainD->currentBackBufferIndex].cpuHandle;
+
+        swapChainD->rtWrapperRight.d.dsv =
+                swapChainD->ds ? swapChainD->ds->dsv.cpuHandle : D3D12_CPU_DESCRIPTOR_HANDLE{ 0 };
+    }
+
+
+    // Time to release things that are marked for currentFrameSlot since due to
+    // the wait above we know that the previous commands on the GPU for this
+    // slot must have finished already.
+    releaseQueue.executeDeferredReleases(currentFrameSlot);
+
+    // Full reset of the command buffer data.
+    cbD->resetState();
+
+    // Move the head back to zero for the per-frame shader-visible descriptor heap work areas.
+    shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot].head = 0;
+    // Same for the small staging area.
+    smallStagingAreas[currentFrameSlot].head = 0;
+
+    bindShaderVisibleHeaps(cbD);
+
+    finishActiveReadbacks(); // last, in case the readback-completed callback issues rhi calls
+
+    if (timestampQueryHeap.isValid() && timestampTicksPerSecond) {
+        // Read the timestamps for the previous frame for this slot. (the
+        // ResolveQuery() should have completed by now due to the wait above)
+        const int timestampPairStartIndex = currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT;
+        calculateGpuTime(cbD,
+                         timestampPairStartIndex,
+                         timestampReadbackArea.mem.p,
+                         timestampTicksPerSecond);
+        // Write the start timestamp for this frame for this slot.
+        cbD->cmdList->EndQuery(timestampQueryHeap.heap,
+                               D3D12_QUERY_TYPE_TIMESTAMP,
+                               timestampPairStartIndex);
+    }
+
+    return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D12::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
+{
+    QD3D12SwapChain *swapChainD = QRHI_RES(QD3D12SwapChain, swapChain);
+    Q_ASSERT(currentSwapChain == swapChainD);
+    QD3D12CommandBuffer *cbD = &swapChainD->cbWrapper;
+
+    QD3D12ObjectHandle backBufferResourceHandle = swapChainD->colorBuffers[swapChainD->currentBackBufferIndex];
+    if (swapChainD->sampleDesc.Count > 1) {
+        QD3D12ObjectHandle msaaBackBufferResourceHandle = swapChainD->msaaBuffers[swapChainD->currentBackBufferIndex];
+        barrierGen.addTransitionBarrier(msaaBackBufferResourceHandle, D3D12_RESOURCE_STATE_RESOLVE_SOURCE);
+        barrierGen.addTransitionBarrier(backBufferResourceHandle, D3D12_RESOURCE_STATE_RESOLVE_DEST);
+        barrierGen.enqueueBufferedTransitionBarriers(cbD);
+        const QD3D12Resource *src = resourcePool.lookupRef(msaaBackBufferResourceHandle);
+        const QD3D12Resource *dst = resourcePool.lookupRef(backBufferResourceHandle);
+        if (src && dst)
+            cbD->cmdList->ResolveSubresource(dst->resource, 0, src->resource, 0, swapChainD->colorFormat);
+    }
+
+    barrierGen.addTransitionBarrier(backBufferResourceHandle, D3D12_RESOURCE_STATE_PRESENT);
+    barrierGen.enqueueBufferedTransitionBarriers(cbD);
+
+    if (timestampQueryHeap.isValid()) {
+        const int timestampPairStartIndex = currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT;
+        cbD->cmdList->EndQuery(timestampQueryHeap.heap,
+                               D3D12_QUERY_TYPE_TIMESTAMP,
+                               timestampPairStartIndex + 1);
+        cbD->cmdList->ResolveQueryData(timestampQueryHeap.heap,
+                                       D3D12_QUERY_TYPE_TIMESTAMP,
+                                       timestampPairStartIndex,
+                                       2,
+                                       timestampReadbackArea.mem.buffer,
+                                       timestampPairStartIndex * sizeof(quint64));
+    }
+
+    ID3D12GraphicsCommandList1 *cmdList = cbD->cmdList;
+    HRESULT hr = cmdList->Close();
+    if (FAILED(hr)) {
+        qWarning("Failed to close command list: %s",
+                 qPrintable(QSystemError::windowsComString(hr)));
+        return QRhi::FrameOpError;
+    }
+
+    ID3D12CommandList *execList[] = { cmdList };
+    cmdQueue->ExecuteCommandLists(1, execList);
+
+    if (!flags.testFlag(QRhi::SkipPresent)) {
+        UINT presentFlags = 0;
+        if (swapChainD->swapInterval == 0
+                && (swapChainD->swapChainFlags & DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING))
+        {
+            presentFlags |= DXGI_PRESENT_ALLOW_TEARING;
+        }
+        if (!swapChainD->swapChain) {
+            qWarning("Failed to present, no swapchain");
+            return QRhi::FrameOpError;
+        }
+        HRESULT hr = swapChainD->swapChain->Present(swapChainD->swapInterval, presentFlags);
+        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(QSystemError::windowsComString(hr)));
+            return QRhi::FrameOpError;
+        }
+
+        if (dcompDevice && swapChainD->dcompTarget && swapChainD->dcompVisual)
+            dcompDevice->Commit();
+    }
+
+    swapChainD->addCommandCompletionSignalForCurrentFrameSlot();
+
+    // NB! The deferred-release mechanism here differs from the older QRhi
+    // backends. There is no lastActiveFrameSlot tracking. Instead,
+    // currentFrameSlot is written to the registered entries now, and so the
+    // resources will get released in the frames_in_flight'th beginFrame()
+    // counting starting from now.
+    releaseQueue.activatePendingDeferredReleaseRequests(currentFrameSlot);
+
+    if (!flags.testFlag(QRhi::SkipPresent)) {
+        // Only move to the next slot if we presented. Otherwise will block and
+        // wait for completion in the next beginFrame already, but SkipPresent
+        // should be infrequent anyway.
+        swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QD3D12_FRAMES_IN_FLIGHT;
+        swapChainD->currentBackBufferIndex = swapChainD->swapChain->GetCurrentBackBufferIndex();
+    }
+
+    currentSwapChain = nullptr;
+    return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D12::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags)
+{
+    Q_UNUSED(flags);
+
+    // Switch to the next slot manually. Swapchains do not know about this
+    // which is good. So for example an onscreen, onscreen, offscreen,
+    // onscreen, onscreen, onscreen sequence of frames leads to 0, 1, 0, 0, 1,
+    // 0. (no strict alternation anymore) But this is not different from what
+    // happens when multiple swapchains are involved. Offscreen frames are
+    // synchronous anyway in the sense that they wait for execution to complete
+    // in endOffscreenFrame, so no resources used in that frame are busy
+    // anymore in the next frame.
+
+    currentFrameSlot = (currentFrameSlot + 1) % QD3D12_FRAMES_IN_FLIGHT;
+
+    for (QD3D12SwapChain *sc : std::as_const(swapchains))
+        sc->waitCommandCompletionForFrameSlot(currentFrameSlot); // note: not sc's currentFrameSlot
+
+    if (!offscreenCb[currentFrameSlot])
+        offscreenCb[currentFrameSlot] = new QD3D12CommandBuffer(this);
+    QD3D12CommandBuffer *cbD = offscreenCb[currentFrameSlot];
+    if (!startCommandListForCurrentFrameSlot(&cbD->cmdList))
+        return QRhi::FrameOpError;
+
+    releaseQueue.executeDeferredReleases(currentFrameSlot);
+    cbD->resetState();
+    shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot].head = 0;
+    smallStagingAreas[currentFrameSlot].head = 0;
+
+    bindShaderVisibleHeaps(cbD);
+
+    if (timestampQueryHeap.isValid() && timestampTicksPerSecond) {
+        cbD->cmdList->EndQuery(timestampQueryHeap.heap,
+                               D3D12_QUERY_TYPE_TIMESTAMP,
+                               currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT);
+    }
+
+    offscreenActive = true;
+    *cb = cbD;
+
+    return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D12::endOffscreenFrame(QRhi::EndFrameFlags flags)
+{
+    Q_UNUSED(flags);
+    Q_ASSERT(offscreenActive);
+    offscreenActive = false;
+
+    QD3D12CommandBuffer *cbD = offscreenCb[currentFrameSlot];
+    if (timestampQueryHeap.isValid()) {
+        const int timestampPairStartIndex = currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT;
+        cbD->cmdList->EndQuery(timestampQueryHeap.heap,
+                               D3D12_QUERY_TYPE_TIMESTAMP,
+                               timestampPairStartIndex + 1);
+        cbD->cmdList->ResolveQueryData(timestampQueryHeap.heap,
+                                       D3D12_QUERY_TYPE_TIMESTAMP,
+                                       timestampPairStartIndex,
+                                       2,
+                                       timestampReadbackArea.mem.buffer,
+                                       timestampPairStartIndex * sizeof(quint64));
+    }
+
+    ID3D12GraphicsCommandList1 *cmdList = cbD->cmdList;
+    HRESULT hr = cmdList->Close();
+    if (FAILED(hr)) {
+        qWarning("Failed to close command list: %s",
+                 qPrintable(QSystemError::windowsComString(hr)));
+        return QRhi::FrameOpError;
+    }
+
+    ID3D12CommandList *execList[] = { cmdList };
+    cmdQueue->ExecuteCommandLists(1, execList);
+
+    releaseQueue.activatePendingDeferredReleaseRequests(currentFrameSlot);
+
+    // wait for completion
+    waitGpu();
+
+    // Here we know that executing the host-side reads for this (or any
+    // previous) frame is safe since we waited for completion above.
+    finishActiveReadbacks(true);
+
+    // the timestamp query results should be available too, given the wait
+    if (timestampQueryHeap.isValid()) {
+        calculateGpuTime(cbD,
+                         currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT,
+                         timestampReadbackArea.mem.p,
+                         timestampTicksPerSecond);
+    }
+
+    return QRhi::FrameOpSuccess;
+}
+
+QRhi::FrameOpResult QRhiD3D12::finish()
+{
+    if (!inFrame)
+        return QRhi::FrameOpSuccess;
+
+    QD3D12CommandBuffer *cbD = nullptr;
+    if (offscreenActive) {
+        Q_ASSERT(!currentSwapChain);
+        cbD = offscreenCb[currentFrameSlot];
+    } else {
+        Q_ASSERT(currentSwapChain);
+        cbD = &currentSwapChain->cbWrapper;
+    }
+    if (!cbD)
+        return QRhi::FrameOpError;
+
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass);
+
+    ID3D12GraphicsCommandList1 *cmdList = cbD->cmdList;
+    HRESULT hr = cmdList->Close();
+    if (FAILED(hr)) {
+        qWarning("Failed to close command list: %s",
+                 qPrintable(QSystemError::windowsComString(hr)));
+        return QRhi::FrameOpError;
+    }
+
+    ID3D12CommandList *execList[] = { cmdList };
+    cmdQueue->ExecuteCommandLists(1, execList);
+
+    releaseQueue.activatePendingDeferredReleaseRequests(currentFrameSlot);
+
+    // full blocking wait for everything, frame slots do not matter now
+    waitGpu();
+
+    hr = cmdAllocators[currentFrameSlot]->Reset();
+    if (FAILED(hr)) {
+        qWarning("Failed to reset command allocator: %s",
+                 qPrintable(QSystemError::windowsComString(hr)));
+        return QRhi::FrameOpError;
+    }
+
+    if (!startCommandListForCurrentFrameSlot(&cmdList))
+        return QRhi::FrameOpError;
+
+    cbD->resetState();
+
+    shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot].head = 0;
+    smallStagingAreas[currentFrameSlot].head = 0;
+
+    bindShaderVisibleHeaps(cbD);
+
+    releaseQueue.executeDeferredReleases(currentFrameSlot);
+
+    finishActiveReadbacks(true);
+
+    return QRhi::FrameOpSuccess;
+}
+
+void QRhiD3D12::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass);
+    enqueueResourceUpdates(cbD, resourceUpdates);
+}
+
+void QRhiD3D12::beginPass(QRhiCommandBuffer *cb,
+                          QRhiRenderTarget *rt,
+                          const QColor &colorClearValue,
+                          const QRhiDepthStencilClearValue &depthStencilClearValue,
+                          QRhiResourceUpdateBatch *resourceUpdates,
+                          QRhiCommandBuffer::BeginPassFlags)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass);
+
+    if (resourceUpdates)
+        enqueueResourceUpdates(cbD, resourceUpdates);
+
+    QD3D12RenderTargetData *rtD = rtData(rt);
+    bool wantsColorClear = true;
+    bool wantsDsClear = true;
+    if (rt->resourceType() == QRhiRenderTarget::TextureRenderTarget) {
+        QD3D12TextureRenderTarget *rtTex = QRHI_RES(QD3D12TextureRenderTarget, rt);
+        wantsColorClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
+        wantsDsClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
+        if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QD3D12Texture, QD3D12RenderBuffer>(rtTex->description(), rtD->currentResIdList))
+            rtTex->create();
+
+        for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments(); it != itEnd; ++it) {
+            QD3D12Texture *texD = QRHI_RES(QD3D12Texture, it->texture());
+            QD3D12Texture *resolveTexD = QRHI_RES(QD3D12Texture, it->resolveTexture());
+            QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, it->renderBuffer());
+            if (texD)
+                barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATE_RENDER_TARGET);
+            else if (rbD)
+                barrierGen.addTransitionBarrier(rbD->handle, D3D12_RESOURCE_STATE_RENDER_TARGET);
+            if (resolveTexD)
+                barrierGen.addTransitionBarrier(resolveTexD->handle, D3D12_RESOURCE_STATE_RENDER_TARGET);
+        }
+        if (rtTex->m_desc.depthStencilBuffer()) {
+            QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, rtTex->m_desc.depthStencilBuffer());
+            Q_ASSERT(rbD->m_type == QRhiRenderBuffer::DepthStencil);
+            barrierGen.addTransitionBarrier(rbD->handle, D3D12_RESOURCE_STATE_DEPTH_WRITE);
+        } else if (rtTex->m_desc.depthTexture()) {
+            QD3D12Texture *depthTexD = QRHI_RES(QD3D12Texture, rtTex->m_desc.depthTexture());
+            barrierGen.addTransitionBarrier(depthTexD->handle, D3D12_RESOURCE_STATE_DEPTH_WRITE);
+        }
+        barrierGen.enqueueBufferedTransitionBarriers(cbD);
+    } else {
+        Q_ASSERT(currentSwapChain);
+        barrierGen.addTransitionBarrier(currentSwapChain->sampleDesc.Count > 1
+                                        ? currentSwapChain->msaaBuffers[currentSwapChain->currentBackBufferIndex]
+                                        : currentSwapChain->colorBuffers[currentSwapChain->currentBackBufferIndex],
+                                        D3D12_RESOURCE_STATE_RENDER_TARGET);
+        barrierGen.enqueueBufferedTransitionBarriers(cbD);
+    }
+
+    cbD->cmdList->OMSetRenderTargets(UINT(rtD->colorAttCount),
+                                     rtD->rtv,
+                                     TRUE,
+                                     rtD->dsAttCount ? &rtD->dsv : nullptr);
+
+    if (rtD->colorAttCount && wantsColorClear) {
+        float clearColor[4] = {
+            colorClearValue.redF(),
+            colorClearValue.greenF(),
+            colorClearValue.blueF(),
+            colorClearValue.alphaF()
+        };
+        for (int i = 0; i < rtD->colorAttCount; ++i)
+            cbD->cmdList->ClearRenderTargetView(rtD->rtv[i], clearColor, 0, nullptr);
+    }
+    if (rtD->dsAttCount && wantsDsClear) {
+        cbD->cmdList->ClearDepthStencilView(rtD->dsv,
+                                            D3D12_CLEAR_FLAGS(D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL),
+                                            depthStencilClearValue.depthClearValue(),
+                                            UINT8(depthStencilClearValue.stencilClearValue()),
+                                            0,
+                                            nullptr);
+    }
+
+    cbD->recordingPass = QD3D12CommandBuffer::RenderPass;
+    cbD->currentTarget = rt;
+
+    cbD->resetPerPassState();
+}
+
+void QRhiD3D12::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
+
+    if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) {
+        QD3D12TextureRenderTarget *rtTex = QRHI_RES(QD3D12TextureRenderTarget, cbD->currentTarget);
+        for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
+             it != itEnd; ++it)
+        {
+            const QRhiColorAttachment &colorAtt(*it);
+            if (!colorAtt.resolveTexture())
+                continue;
+
+            QD3D12Texture *dstTexD = QRHI_RES(QD3D12Texture, colorAtt.resolveTexture());
+            QD3D12Resource *dstRes = resourcePool.lookupRef(dstTexD->handle);
+            if (!dstRes)
+                continue;
+
+            QD3D12Texture *srcTexD = QRHI_RES(QD3D12Texture, colorAtt.texture());
+            QD3D12RenderBuffer *srcRbD = QRHI_RES(QD3D12RenderBuffer, colorAtt.renderBuffer());
+            Q_ASSERT(srcTexD || srcRbD);
+            QD3D12Resource *srcRes = resourcePool.lookupRef(srcTexD ? srcTexD->handle : srcRbD->handle);
+            if (!srcRes)
+                continue;
+
+            if (srcTexD) {
+                if (srcTexD->dxgiFormat != dstTexD->dxgiFormat) {
+                    qWarning("Resolve source (%d) and destination (%d) formats do not match",
+                             int(srcTexD->dxgiFormat), int(dstTexD->dxgiFormat));
+                    continue;
+                }
+                if (srcTexD->sampleDesc.Count <= 1) {
+                    qWarning("Cannot resolve a non-multisample texture");
+                    continue;
+                }
+                if (srcTexD->m_pixelSize != dstTexD->m_pixelSize) {
+                    qWarning("Resolve source and destination sizes do not match");
+                    continue;
+                }
+            } else {
+                if (srcRbD->dxgiFormat != dstTexD->dxgiFormat) {
+                    qWarning("Resolve source (%d) and destination (%d) formats do not match",
+                             int(srcRbD->dxgiFormat), int(dstTexD->dxgiFormat));
+                    continue;
+                }
+                if (srcRbD->m_pixelSize != dstTexD->m_pixelSize) {
+                    qWarning("Resolve source and destination sizes do not match");
+                    continue;
+                }
+            }
+
+            barrierGen.addTransitionBarrier(srcTexD ? srcTexD->handle : srcRbD->handle, D3D12_RESOURCE_STATE_RESOLVE_SOURCE);
+            barrierGen.addTransitionBarrier(dstTexD->handle, D3D12_RESOURCE_STATE_RESOLVE_DEST);
+            barrierGen.enqueueBufferedTransitionBarriers(cbD);
+
+            const UINT resolveCount = colorAtt.multiViewCount() >= 2 ? colorAtt.multiViewCount() : 1;
+            for (UINT resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) {
+                const UINT srcSubresource = calcSubresource(0, UINT(colorAtt.layer()) + resolveIdx, 1);
+                const UINT dstSubresource = calcSubresource(UINT(colorAtt.resolveLevel()),
+                                                            UINT(colorAtt.resolveLayer()) + resolveIdx,
+                                                            dstTexD->mipLevelCount);
+                cbD->cmdList->ResolveSubresource(dstRes->resource, dstSubresource,
+                                                 srcRes->resource, srcSubresource,
+                                                 dstTexD->dxgiFormat);
+            }
+        }
+
+    }
+
+    cbD->recordingPass = QD3D12CommandBuffer::NoPass;
+    cbD->currentTarget = nullptr;
+
+    if (resourceUpdates)
+        enqueueResourceUpdates(cbD, resourceUpdates);
+}
+
+void QRhiD3D12::beginComputePass(QRhiCommandBuffer *cb,
+                                 QRhiResourceUpdateBatch *resourceUpdates,
+                                 QRhiCommandBuffer::BeginPassFlags)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass);
+
+    if (resourceUpdates)
+        enqueueResourceUpdates(cbD, resourceUpdates);
+
+    cbD->recordingPass = QD3D12CommandBuffer::ComputePass;
+
+    cbD->resetPerPassState();
+}
+
+void QRhiD3D12::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::ComputePass);
+
+    cbD->recordingPass = QD3D12CommandBuffer::NoPass;
+
+    if (resourceUpdates)
+        enqueueResourceUpdates(cbD, resourceUpdates);
+}
+
+void QRhiD3D12::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::ComputePass);
+    QD3D12ComputePipeline *psD = QRHI_RES(QD3D12ComputePipeline, ps);
+    const bool pipelineChanged = cbD->currentComputePipeline != psD || cbD->currentPipelineGeneration != psD->generation;
+
+    if (pipelineChanged) {
+        cbD->currentGraphicsPipeline = nullptr;
+        cbD->currentComputePipeline = psD;
+        cbD->currentPipelineGeneration = psD->generation;
+
+        if (QD3D12Pipeline *pipeline = pipelinePool.lookupRef(psD->handle)) {
+            Q_ASSERT(pipeline->type == QD3D12Pipeline::Compute);
+            cbD->cmdList->SetPipelineState(pipeline->pso);
+            if (QD3D12RootSignature *rs = rootSignaturePool.lookupRef(psD->rootSigHandle))
+                cbD->cmdList->SetComputeRootSignature(rs->rootSig);
+        }
+    }
+}
+
+void QRhiD3D12::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
+{
+    QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
+    Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::ComputePass);
+    cbD->cmdList->Dispatch(UINT(x), UINT(y), UINT(z));
+}
+
+bool QD3D12DescriptorHeap::create(ID3D12Device *device,
+                                  quint32 descriptorCount,
+                                  D3D12_DESCRIPTOR_HEAP_TYPE heapType,
+                                  D3D12_DESCRIPTOR_HEAP_FLAGS heapFlags)
+{
+    head = 0;
+    capacity = descriptorCount;
+    this->heapType = heapType;
+    this->heapFlags = heapFlags;
+
+    D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};
+    heapDesc.Type = heapType;
+    heapDesc.NumDescriptors = capacity;
+    heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAGS(heapFlags);
+
+    HRESULT hr = device->CreateDescriptorHeap(&heapDesc, __uuidof(ID3D12DescriptorHeap), reinterpret_cast<void **>(&heap));
+    if (FAILED(hr)) {
+        qWarning("Failed to create descriptor heap: %s", qPrintable(QSystemError::windowsComString(hr)));
+        heap = nullptr;
+        capacity = descriptorByteSize = 0;
+        return false;
+    }
+
+    descriptorByteSize = device->GetDescriptorHandleIncrementSize(heapType);
+    heapStart.cpuHandle = heap->GetCPUDescriptorHandleForHeapStart();
+    if (heapFlags & D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE)
+        heapStart.gpuHandle = heap->GetGPUDescriptorHandleForHeapStart();
+
+    return true;
+}
+
+void QD3D12DescriptorHeap::createWithExisting(const QD3D12DescriptorHeap &other,
+                                              quint32 offsetInDescriptors,
+                                              quint32 descriptorCount)
+{
+    heap = nullptr;
+    head = 0;
+    capacity = descriptorCount;
+    heapType = other.heapType;
+    heapFlags = other.heapFlags;
+    descriptorByteSize = other.descriptorByteSize;
+    heapStart = incremented(other.heapStart, offsetInDescriptors);
+}
+
+void QD3D12DescriptorHeap::destroy()
+{
+    if (heap) {
+        heap->Release();
+        heap = nullptr;
+    }
+    capacity = 0;
+}
+
+void QD3D12DescriptorHeap::destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue)
+{
+    if (heap) {
+        releaseQueue->deferredReleaseDescriptorHeap(heap);
+        heap = nullptr;
+    }
+    capacity = 0;
+}
+
+QD3D12Descriptor QD3D12DescriptorHeap::get(quint32 count)
+{
+    Q_ASSERT(count > 0);
+    if (head + count > capacity) {
+        qWarning("Cannot get %u descriptors as that would exceed capacity %u", count, capacity);
+        return {};
+    }
+    head += count;
+    return at(head - count);
+}
+
+QD3D12Descriptor QD3D12DescriptorHeap::at(quint32 index) const
+{
+    const quint32 startOffset = index * descriptorByteSize;
+    QD3D12Descriptor result;
+    result.cpuHandle.ptr = heapStart.cpuHandle.ptr + startOffset;
+    if (heapStart.gpuHandle.ptr != 0)
+        result.gpuHandle.ptr = heapStart.gpuHandle.ptr + startOffset;
+    return result;
+}
+
+bool QD3D12CpuDescriptorPool::create(ID3D12Device *device, D3D12_DESCRIPTOR_HEAP_TYPE heapType, const char *debugName)
+{
+    QD3D12DescriptorHeap firstHeap;
+    if (!firstHeap.create(device, DESCRIPTORS_PER_HEAP, heapType, D3D12_DESCRIPTOR_HEAP_FLAG_NONE))
+        return false;
+    heaps.append(HeapWithMap::init(firstHeap, DESCRIPTORS_PER_HEAP));
+    descriptorByteSize = heaps[0].heap.descriptorByteSize;
+    this->device = device;
+    this->debugName = debugName;
+    return true;
+}
+
+void QD3D12CpuDescriptorPool::destroy()
+{
+#ifndef QT_NO_DEBUG
+    // debug builds: just do it always
+    static bool leakCheck = true;
+#else
+    // release builds: opt-in
+    static bool leakCheck = qEnvironmentVariableIntValue("QT_RHI_LEAK_CHECK");
+#endif
+    if (leakCheck) {
+        for (HeapWithMap &heap : heaps) {
+            const int leakedDescriptorCount = heap.map.count(true);
+            if (leakedDescriptorCount > 0) {
+                qWarning("QD3D12CpuDescriptorPool::destroy(): "
+                         "Heap %p for descriptor pool %p '%s' has %d unreleased descriptors",
+                         &heap.heap, this, debugName, leakedDescriptorCount);
+            }
+        }
+    }
+    for (HeapWithMap &heap : heaps)
+        heap.heap.destroy();
+    heaps.clear();
+}
+
+QD3D12Descriptor QD3D12CpuDescriptorPool::allocate(quint32 count)
+{
+    Q_ASSERT(count > 0 && count <= DESCRIPTORS_PER_HEAP);
+
+    HeapWithMap &last(heaps.last());
+    if (last.heap.head + count <= last.heap.capacity) {
+        quint32 firstIndex = last.heap.head;
+        for (quint32 i = 0; i < count; ++i)
+            last.map.setBit(firstIndex + i);
+        return last.heap.get(count);
+    }
+
+    for (HeapWithMap &heap : heaps) {
+        quint32 freeCount = 0;
+        for (quint32 i = 0; i < DESCRIPTORS_PER_HEAP; ++i) {
+            if (heap.map.testBit(i)) {
+                freeCount = 0;
+            } else {
+                freeCount += 1;
+                if (freeCount == count) {
+                    quint32 firstIndex = i - (freeCount - 1);
+                    for (quint32 j = 0; j < count; ++j) {
+                        heap.map.setBit(firstIndex + j);
+                        return heap.heap.at(firstIndex);
+                    }
+                }
+            }
+        }
+    }
+
+    QD3D12DescriptorHeap newHeap;
+    if (!newHeap.create(device, DESCRIPTORS_PER_HEAP, last.heap.heapType, last.heap.heapFlags))
+        return {};
+
+    heaps.append(HeapWithMap::init(newHeap, DESCRIPTORS_PER_HEAP));
+
+    for (quint32 i = 0; i < count; ++i)
+        heaps.last().map.setBit(i);
+
+    return heaps.last().heap.get(count);
+}
+
+void QD3D12CpuDescriptorPool::release(const QD3D12Descriptor &descriptor, quint32 count)
+{
+    Q_ASSERT(count > 0 && count <= DESCRIPTORS_PER_HEAP);
+    if (!descriptor.isValid())
+        return;
+
+    const SIZE_T addr = descriptor.cpuHandle.ptr;
+    for (HeapWithMap &heap : heaps) {
+        const SIZE_T begin = heap.heap.heapStart.cpuHandle.ptr;
+        const SIZE_T end = begin + heap.heap.descriptorByteSize * heap.heap.capacity;
+        if (addr >= begin && addr < end) {
+            quint32 firstIndex = (addr - begin) / heap.heap.descriptorByteSize;
+            for (quint32 i = 0; i < count; ++i)
+                heap.map.setBit(firstIndex + i, false);
+            return;
+        }
+    }
+
+    qWarning("QD3D12CpuDescriptorPool::release: Descriptor with address %llu is not in any heap",
+             quint64(descriptor.cpuHandle.ptr));
+}
+
+bool QD3D12QueryHeap::create(ID3D12Device *device,
+                             quint32 queryCount,
+                             D3D12_QUERY_HEAP_TYPE heapType)
+{
+    capacity = queryCount;
+
+    D3D12_QUERY_HEAP_DESC heapDesc = {};
+    heapDesc.Type = heapType;
+    heapDesc.Count = capacity;
+
+    HRESULT hr = device->CreateQueryHeap(&heapDesc, __uuidof(ID3D12QueryHeap), reinterpret_cast<void **>(&heap));
+    if (FAILED(hr)) {
+        qWarning("Failed to create query heap: %s", qPrintable(QSystemError::windowsComString(hr)));
+        heap = nullptr;
+        capacity = 0;
+        return false;
+    }
+
+    return true;
+}
+
+void QD3D12QueryHeap::destroy()
+{
+    if (heap) {
+        heap->Release();
+        heap = nullptr;
+    }
+    capacity = 0;
+}
+
+bool QD3D12StagingArea::create(QRhiD3D12 *rhi, quint32 capacity, D3D12_HEAP_TYPE heapType)
+{
+    Q_ASSERT(heapType == D3D12_HEAP_TYPE_UPLOAD || heapType == D3D12_HEAP_TYPE_READBACK);
+    D3D12_RESOURCE_DESC resourceDesc = {};
+    resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
+    resourceDesc.Width = capacity;
+    resourceDesc.Height = 1;
+    resourceDesc.DepthOrArraySize = 1;
+    resourceDesc.MipLevels = 1;
+    resourceDesc.Format = DXGI_FORMAT_UNKNOWN;
+    resourceDesc.SampleDesc = { 1, 0 };
+    resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
+    resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
+    UINT state = heapType == D3D12_HEAP_TYPE_UPLOAD ? D3D12_RESOURCE_STATE_GENERIC_READ : D3D12_RESOURCE_STATE_COPY_DEST;
+    HRESULT hr = rhi->vma.createResource(heapType,
+                                         &resourceDesc,
+                                         D3D12_RESOURCE_STATES(state),
+                                         nullptr,
+                                         &allocation,
+                                         __uuidof(ID3D12Resource),
+                                         reinterpret_cast<void **>(&resource));
+    if (FAILED(hr)) {
+        qWarning("Failed to create buffer for staging area: %s",
+                 qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+    void *p = nullptr;
+    hr = resource->Map(0, nullptr, &p);
+    if (FAILED(hr)) {
+        qWarning("Failed to map buffer for staging area: %s",
+                 qPrintable(QSystemError::windowsComString(hr)));
+        destroy();
+        return false;
+    }
+
+    mem.p = static_cast<quint8 *>(p);
+    mem.gpuAddr = resource->GetGPUVirtualAddress();
+    mem.buffer = resource;
+    mem.bufferOffset = 0;
+
+    this->capacity = capacity;
+    head = 0;
+
+    return true;
+}
+
+void QD3D12StagingArea::destroy()
+{
+    if (resource) {
+        resource->Release();
+        resource = nullptr;
+    }
+    if (allocation) {
+        allocation->Release();
+        allocation = nullptr;
+    }
+    mem = {};
+}
+
+void QD3D12StagingArea::destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue)
+{
+    if (resource)
+        releaseQueue->deferredReleaseResourceAndAllocation(resource, allocation);
+    mem = {};
+}
+
+QD3D12StagingArea::Allocation QD3D12StagingArea::get(quint32 byteSize)
+{
+    const quint32 allocSize = aligned(byteSize, ALIGNMENT);
+    if (head + allocSize > capacity) {
+        qWarning("Failed to allocate %u (%u) bytes from staging area of size %u with %u bytes left",
+                 allocSize, byteSize, capacity, remainingCapacity());
+        return {};
+    }
+    const quint32 offset = head;
+    head += allocSize;
+    return {
+        mem.p + offset,
+        mem.gpuAddr + offset,
+        mem.buffer,
+        offset
+    };
+}
+
+// Can be called inside and outside of begin-endFrame. Removes from the pool
+// and releases the underlying native resource only in the frames_in_flight'th
+// beginFrame() counted starting from the next endFrame().
+void QD3D12ReleaseQueue::deferredReleaseResource(const QD3D12ObjectHandle &handle)
+{
+    DeferredReleaseEntry e;
+    e.handle = handle;
+    queue.append(e);
+}
+
+void QD3D12ReleaseQueue::deferredReleaseResourceWithViews(const QD3D12ObjectHandle &handle,
+                                                          QD3D12CpuDescriptorPool *pool,
+                                                          const QD3D12Descriptor &viewsStart,
+                                                          int viewCount)
+{
+    DeferredReleaseEntry e;
+    e.type = DeferredReleaseEntry::Resource;
+    e.handle = handle;
+    e.poolForViews = pool;
+    e.viewsStart = viewsStart;
+    e.viewCount = viewCount;
+    queue.append(e);
+}
+
+void QD3D12ReleaseQueue::deferredReleasePipeline(const QD3D12ObjectHandle &handle)
+{
+    DeferredReleaseEntry e;
+    e.type = DeferredReleaseEntry::Pipeline;
+    e.handle = handle;
+    queue.append(e);
+}
+
+void QD3D12ReleaseQueue::deferredReleaseRootSignature(const QD3D12ObjectHandle &handle)
+{
+    DeferredReleaseEntry e;
+    e.type = DeferredReleaseEntry::RootSignature;
+    e.handle = handle;
+    queue.append(e);
+}
+
+void QD3D12ReleaseQueue::deferredReleaseCallback(std::function<void(void*)> callback, void *userData)
+{
+    DeferredReleaseEntry e;
+    e.type = DeferredReleaseEntry::Callback;
+    e.callback = callback;
+    e.callbackUserData = userData;
+    queue.append(e);
+}
+
+void QD3D12ReleaseQueue::deferredReleaseResourceAndAllocation(ID3D12Resource *resource,
+                                                              D3D12MA::Allocation *allocation)
+{
+    DeferredReleaseEntry e;
+    e.type = DeferredReleaseEntry::ResourceAndAllocation;
+    e.resourceAndAllocation = { resource, allocation };
+    queue.append(e);
+}
+
+void QD3D12ReleaseQueue::deferredReleaseDescriptorHeap(ID3D12DescriptorHeap *heap)
+{
+    DeferredReleaseEntry e;
+    e.type = DeferredReleaseEntry::DescriptorHeap;
+    e.descriptorHeap = heap;
+    queue.append(e);
+}
+
+void QD3D12ReleaseQueue::deferredReleaseViews(QD3D12CpuDescriptorPool *pool,
+                                              const QD3D12Descriptor &viewsStart,
+                                              int viewCount)
+{
+    DeferredReleaseEntry e;
+    e.type = DeferredReleaseEntry::Views;
+    e.poolForViews = pool;
+    e.viewsStart = viewsStart;
+    e.viewCount = viewCount;
+    queue.append(e);
+}
+
+void QD3D12ReleaseQueue::activatePendingDeferredReleaseRequests(int frameSlot)
+{
+    for (DeferredReleaseEntry &e : queue) {
+        if (!e.frameSlotToBeReleasedIn.has_value())
+            e.frameSlotToBeReleasedIn = frameSlot;
+    }
+}
+
+void QD3D12ReleaseQueue::executeDeferredReleases(int frameSlot, bool forced)
+{
+    for (int i = queue.count() - 1; i >= 0; --i) {
+        const DeferredReleaseEntry &e(queue[i]);
+        if (forced || (e.frameSlotToBeReleasedIn.has_value() && e.frameSlotToBeReleasedIn.value() == frameSlot)) {
+            switch (e.type) {
+            case DeferredReleaseEntry::Resource:
+                resourcePool->remove(e.handle);
+                if (e.poolForViews && e.viewsStart.isValid() && e.viewCount > 0)
+                    e.poolForViews->release(e.viewsStart, e.viewCount);
+                break;
+            case DeferredReleaseEntry::Pipeline:
+                pipelinePool->remove(e.handle);
+                break;
+            case DeferredReleaseEntry::RootSignature:
+                rootSignaturePool->remove(e.handle);
+                break;
+            case DeferredReleaseEntry::Callback:
+                e.callback(e.callbackUserData);
+                break;
+            case DeferredReleaseEntry::ResourceAndAllocation:
+                // order matters: resource first, then the allocation (which
+                // may be null)
+                e.resourceAndAllocation.first->Release();
+                if (e.resourceAndAllocation.second)
+                    e.resourceAndAllocation.second->Release();
+                break;
+            case DeferredReleaseEntry::DescriptorHeap:
+                e.descriptorHeap->Release();
+                break;
+            case DeferredReleaseEntry::Views:
+                e.poolForViews->release(e.viewsStart, e.viewCount);
+                break;
+            }
+            queue.removeAt(i);
+        }
+    }
+}
+
+void QD3D12ReleaseQueue::releaseAll()
+{
+    executeDeferredReleases(0, true);
+}
+
+void QD3D12ResourceBarrierGenerator::addTransitionBarrier(const QD3D12ObjectHandle &resourceHandle,
+                                                          D3D12_RESOURCE_STATES stateAfter)
+{
+    if (QD3D12Resource *res = resourcePool->lookupRef(resourceHandle)) {
+        if (stateAfter != res->state) {
+            transitionResourceBarriers.append({ resourceHandle, res->state, stateAfter });
+            res->state = stateAfter;
+        }
+    }
+}
+
+void QD3D12ResourceBarrierGenerator::enqueueBufferedTransitionBarriers(QD3D12CommandBuffer *cbD)
+{
+    QVarLengthArray<D3D12_RESOURCE_BARRIER, PREALLOC> barriers;
+    for (const TransitionResourceBarrier &trb : transitionResourceBarriers) {
+        if (QD3D12Resource *res = resourcePool->lookupRef(trb.resourceHandle)) {
+            D3D12_RESOURCE_BARRIER barrier = {};
+            barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
+            barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
+            barrier.Transition.pResource = res->resource;
+            barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
+            barrier.Transition.StateBefore = trb.stateBefore;
+            barrier.Transition.StateAfter = trb.stateAfter;
+            barriers.append(barrier);
+        }
+    }
+    transitionResourceBarriers.clear();
+    if (!barriers.isEmpty())
+        cbD->cmdList->ResourceBarrier(barriers.count(), barriers.constData());
+}
+
+void QD3D12ResourceBarrierGenerator::enqueueSubresourceTransitionBarrier(QD3D12CommandBuffer *cbD,
+                                                                         const QD3D12ObjectHandle &resourceHandle,
+                                                                         UINT subresource,
+                                                                         D3D12_RESOURCE_STATES stateBefore,
+                                                                         D3D12_RESOURCE_STATES stateAfter)
+{
+    if (QD3D12Resource *res = resourcePool->lookupRef(resourceHandle)) {
+        D3D12_RESOURCE_BARRIER barrier = {};
+        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
+        barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
+        barrier.Transition.pResource = res->resource;
+        barrier.Transition.Subresource = subresource;
+        barrier.Transition.StateBefore = stateBefore;
+        barrier.Transition.StateAfter = stateAfter;
+        cbD->cmdList->ResourceBarrier(1, &barrier);
+    }
+}
+
+void QD3D12ResourceBarrierGenerator::enqueueUavBarrier(QD3D12CommandBuffer *cbD,
+                                                       const QD3D12ObjectHandle &resourceHandle)
+{
+    if (QD3D12Resource *res = resourcePool->lookupRef(resourceHandle)) {
+        D3D12_RESOURCE_BARRIER barrier = {};
+        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
+        barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
+        barrier.UAV.pResource = res->resource;
+        cbD->cmdList->ResourceBarrier(1, &barrier);
+    }
+}
+
+void QD3D12ShaderBytecodeCache::insertWithCapacityLimit(const QRhiShaderStage &key, const Shader &s)
+{
+    if (data.count() >= QRhiD3D12::MAX_SHADER_CACHE_ENTRIES)
+        data.clear();
+    data.insert(key, s);
+}
+
+bool QD3D12ShaderVisibleDescriptorHeap::create(ID3D12Device *device,
+                                               D3D12_DESCRIPTOR_HEAP_TYPE type,
+                                               quint32 perFrameDescriptorCount)
+{
+    Q_ASSERT(type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV || type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
+
+    quint32 size = perFrameDescriptorCount * QD3D12_FRAMES_IN_FLIGHT;
+
+    // https://learn.microsoft.com/en-us/windows/win32/direct3d12/hardware-support
+    const quint32 CBV_SRV_UAV_MAX = 1000000;
+    const quint32 SAMPLER_MAX = 2048;
+    if (type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV)
+        size = qMin(size, CBV_SRV_UAV_MAX);
+    else if (type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER)
+        size = qMin(size, SAMPLER_MAX);
+
+    if (!heap.create(device, size, type, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE)) {
+        qWarning("Failed to create shader-visible descriptor heap of size %u", size);
+        return false;
+    }
+
+    perFrameDescriptorCount = size / QD3D12_FRAMES_IN_FLIGHT;
+    quint32 currentOffsetInDescriptors = 0;
+    for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
+        perFrameHeapSlice[i].createWithExisting(heap, currentOffsetInDescriptors, perFrameDescriptorCount);
+        currentOffsetInDescriptors += perFrameDescriptorCount;
+    }
+
+    return true;
+}
+
+void QD3D12ShaderVisibleDescriptorHeap::destroy()
+{
+    heap.destroy();
+}
+
+void QD3D12ShaderVisibleDescriptorHeap::destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue)
+{
+    heap.destroyWithDeferredRelease(releaseQueue);
+}
+
+static inline QPair<int, int> mapBinding(int binding, const QShader::NativeResourceBindingMap &map)
+{
+    if (map.isEmpty())
+        return { binding, binding }; // assume 1:1 mapping
+
+    auto it = map.constFind(binding);
+    if (it != map.cend())
+        return *it;
+
+    // Hitting this path is normal too. It is not given that the resource is
+    // present in the shaders for all the stages specified by the visibility
+    // mask in the QRhiShaderResourceBinding.
+    return { -1, -1 };
+}
+
+void QD3D12ShaderResourceVisitor::visit()
+{
+    for (int bindingIdx = 0, bindingCount = srb->m_bindings.count(); bindingIdx != bindingCount; ++bindingIdx) {
+        const QRhiShaderResourceBinding &b(srb->m_bindings[bindingIdx]);
+        const QRhiShaderResourceBinding::Data *bd = QRhiImplementation::shaderResourceBindingData(b);
+
+        for (int stageIdx = 0; stageIdx < stageCount; ++stageIdx) {
+            const QD3D12ShaderStageData *sd = &stageData[stageIdx];
+            if (!sd->valid)
+                continue;
+
+            if (!bd->stage.testFlag(qd3d12_stageToSrb(sd->stage)))
+                continue;
+
+            switch (bd->type) {
+            case QRhiShaderResourceBinding::UniformBuffer:
+            {
+                const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first;
+                if (shaderRegister >= 0 && uniformBuffer)
+                    uniformBuffer(sd->stage, bd->u.ubuf, shaderRegister, bd->binding);
+            }
+                break;
+            case QRhiShaderResourceBinding::SampledTexture:
+            {
+                Q_ASSERT(bd->u.stex.count > 0);
+                const int textureBaseShaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first;
+                const int samplerBaseShaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).second;
+                for (int i = 0; i < bd->u.stex.count; ++i) {
+                    if (textureBaseShaderRegister >= 0 && texture)
+                        texture(sd->stage, bd->u.stex.texSamplers[i], textureBaseShaderRegister + i);
+                    if (samplerBaseShaderRegister >= 0 && sampler)
+                        sampler(sd->stage, bd->u.stex.texSamplers[i], samplerBaseShaderRegister + i);
+                }
+            }
+                break;
+            case QRhiShaderResourceBinding::Texture:
+            {
+                Q_ASSERT(bd->u.stex.count > 0);
+                const int baseShaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first;
+                if (baseShaderRegister >= 0 && texture) {
+                    for (int i = 0; i < bd->u.stex.count; ++i)
+                        texture(sd->stage, bd->u.stex.texSamplers[i], baseShaderRegister + i);
+                }
+            }
+                break;
+            case QRhiShaderResourceBinding::Sampler:
+            {
+                Q_ASSERT(bd->u.stex.count > 0);
+                const int baseShaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first;
+                if (baseShaderRegister >= 0 && sampler) {
+                    for (int i = 0; i < bd->u.stex.count; ++i)
+                        sampler(sd->stage, bd->u.stex.texSamplers[i], baseShaderRegister + i);
+                }
+            }
+                break;
+            case QRhiShaderResourceBinding::ImageLoad:
+            {
+                const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first;
+                if (shaderRegister >= 0 && storageImage)
+                    storageImage(sd->stage, bd->u.simage, Load, shaderRegister);
+            }
+                break;
+            case QRhiShaderResourceBinding::ImageStore:
+            {
+                const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first;
+                if (shaderRegister >= 0 && storageImage)
+                    storageImage(sd->stage, bd->u.simage, Store, shaderRegister);
+            }
+                break;
+            case QRhiShaderResourceBinding::ImageLoadStore:
+            {
+                const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first;
+                if (shaderRegister >= 0 && storageImage)
+                    storageImage(sd->stage, bd->u.simage, LoadStore, shaderRegister);
+            }
+                break;
+            case QRhiShaderResourceBinding::BufferLoad:
+            {
+                const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first;
+                if (shaderRegister >= 0 && storageBuffer)
+                    storageBuffer(sd->stage, bd->u.sbuf, Load, shaderRegister);
+            }
+                break;
+            case QRhiShaderResourceBinding::BufferStore:
+            {
+                const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first;
+                if (shaderRegister >= 0 && storageBuffer)
+                    storageBuffer(sd->stage, bd->u.sbuf, Store, shaderRegister);
+            }
+                break;
+            case QRhiShaderResourceBinding::BufferLoadStore:
+            {
+                const int shaderRegister = mapBinding(bd->binding, sd->nativeResourceBindingMap).first;
+                if (shaderRegister >= 0 && storageBuffer)
+                    storageBuffer(sd->stage, bd->u.sbuf, LoadStore, shaderRegister);
+            }
+                break;
+            }
+        }
+    }
+}
+
+bool QD3D12SamplerManager::create(ID3D12Device *device)
+{
+    // This does not need to be per-frame slot, just grab space for MAX_SAMPLERS samplers.
+    if (!shaderVisibleSamplerHeap.create(device,
+                                         D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,
+                                         MAX_SAMPLERS / QD3D12_FRAMES_IN_FLIGHT))
+    {
+        qWarning("Could not create shader-visible SAMPLER heap");
+        return false;
+    }
+
+    this->device = device;
+    return true;
+}
+
+void QD3D12SamplerManager::destroy()
+{
+    if (device) {
+        shaderVisibleSamplerHeap.destroy();
+        device = nullptr;
+    }
+}
+
+QD3D12Descriptor QD3D12SamplerManager::getShaderVisibleDescriptor(const D3D12_SAMPLER_DESC &desc)
+{
+    auto it = gpuMap.constFind({desc});
+    if (it != gpuMap.cend())
+        return *it;
+
+    QD3D12Descriptor descriptor = shaderVisibleSamplerHeap.heap.get(1);
+    if (descriptor.isValid()) {
+        device->CreateSampler(&desc, descriptor.cpuHandle);
+        gpuMap.insert({desc}, descriptor);
+    } else {
+        qWarning("Out of shader-visible SAMPLER descriptor heap space,"
+                 " this should not happen, maximum number of unique samplers is %u",
+                 shaderVisibleSamplerHeap.heap.capacity);
+    }
+
+    return descriptor;
+}
+
+bool QD3D12MipmapGenerator::create(QRhiD3D12 *rhiD)
+{
+    this->rhiD = rhiD;
+
+    D3D12_ROOT_PARAMETER1 rootParams[3] = {};
+    D3D12_DESCRIPTOR_RANGE1 descriptorRanges[2] = {};
+
+    // b0
+    rootParams[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
+    rootParams[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
+    rootParams[0].Descriptor.Flags = D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC;
+
+    // t0
+    descriptorRanges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
+    descriptorRanges[0].NumDescriptors = 1;
+    descriptorRanges[0].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;
+    rootParams[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
+    rootParams[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
+    rootParams[1].DescriptorTable.NumDescriptorRanges = 1;
+    rootParams[1].DescriptorTable.pDescriptorRanges = &descriptorRanges[0];
+
+    // u0..3
+    descriptorRanges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
+    descriptorRanges[1].NumDescriptors = 4;
+    rootParams[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
+    rootParams[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
+    rootParams[2].DescriptorTable.NumDescriptorRanges = 1;
+    rootParams[2].DescriptorTable.pDescriptorRanges = &descriptorRanges[1];
+
+    // s0
+    D3D12_STATIC_SAMPLER_DESC samplerDesc = {};
+    samplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
+    samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
+    samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
+    samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
+    samplerDesc.MaxLOD = 10000.0f;
+    samplerDesc.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
+
+    D3D12_VERSIONED_ROOT_SIGNATURE_DESC rsDesc = {};
+    rsDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1;
+    rsDesc.Desc_1_1.NumParameters = 3;
+    rsDesc.Desc_1_1.pParameters = rootParams;
+    rsDesc.Desc_1_1.NumStaticSamplers = 1;
+    rsDesc.Desc_1_1.pStaticSamplers = &samplerDesc;
+
+    ID3DBlob *signature = nullptr;
+    HRESULT hr = D3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
+    if (FAILED(hr)) {
+        qWarning("Failed to serialize root signature: %s", qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+    ID3D12RootSignature *rootSig = nullptr;
+    hr = rhiD->dev->CreateRootSignature(0,
+                                        signature->GetBufferPointer(),
+                                        signature->GetBufferSize(),
+                                        __uuidof(ID3D12RootSignature),
+                                        reinterpret_cast<void **>(&rootSig));
+    signature->Release();
+    if (FAILED(hr)) {
+        qWarning("Failed to create root signature: %s",
+                 qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+
+    rootSigHandle = QD3D12RootSignature::addToPool(&rhiD->rootSignaturePool, rootSig);
+
+    D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {};
+    psoDesc.pRootSignature = rootSig;
+    psoDesc.CS.pShaderBytecode = g_csMipmap;
+    psoDesc.CS.BytecodeLength = sizeof(g_csMipmap);
+    ID3D12PipelineState *pso = nullptr;
+    hr = rhiD->dev->CreateComputePipelineState(&psoDesc,
+                                               __uuidof(ID3D12PipelineState),
+                                               reinterpret_cast<void **>(&pso));
+    if (FAILED(hr)) {
+        qWarning("Failed to create compute pipeline state: %s",
+                 qPrintable(QSystemError::windowsComString(hr)));
+        rhiD->rootSignaturePool.remove(rootSigHandle);
+        rootSigHandle = {};
+        return false;
+    }
+
+    pipelineHandle = QD3D12Pipeline::addToPool(&rhiD->pipelinePool, QD3D12Pipeline::Compute, pso);
+
+    return true;
+}
+
+void QD3D12MipmapGenerator::destroy()
+{
+    rhiD->pipelinePool.remove(pipelineHandle);
+    pipelineHandle = {};
+    rhiD->rootSignaturePool.remove(rootSigHandle);
+    rootSigHandle = {};
+}
+
+void QD3D12MipmapGenerator::generate(QD3D12CommandBuffer *cbD, const QD3D12ObjectHandle &textureHandle)
+{
+    QD3D12Pipeline *pipeline = rhiD->pipelinePool.lookupRef(pipelineHandle);
+    if (!pipeline)
+        return;
+    QD3D12RootSignature *rootSig = rhiD->rootSignaturePool.lookupRef(rootSigHandle);
+    if (!rootSig)
+        return;
+    QD3D12Resource *res = rhiD->resourcePool.lookupRef(textureHandle);
+    if (!res)
+        return;
+
+    const quint32 mipLevelCount = res->desc.MipLevels;
+    if (mipLevelCount < 2)
+        return;
+
+    if (res->desc.SampleDesc.Count > 1) {
+        qWarning("Cannot generate mipmaps for MSAA texture");
+        return;
+    }
+
+    const bool is1D = res->desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE1D;
+    if (is1D) {
+        qWarning("Cannot generate mipmaps for 1D texture");
+        return;
+    }
+
+    const bool is3D = res->desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D;
+    const bool isCubeOrArray = res->desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D
+            && res->desc.DepthOrArraySize > 1;
+    const quint32 layerCount = isCubeOrArray ? res->desc.DepthOrArraySize : 1;
+
+    if (is3D) {
+        // ### needs its own shader and maybe a different solution
+        qWarning("3D texture mipmapping is not implemented for D3D12 atm");
+        return;
+    }
+
+    rhiD->barrierGen.addTransitionBarrier(textureHandle, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
+    rhiD->barrierGen.enqueueBufferedTransitionBarriers(cbD);
+
+    cbD->cmdList->SetPipelineState(pipeline->pso);
+    cbD->cmdList->SetComputeRootSignature(rootSig->rootSig);
+
+    const quint32 descriptorByteSize = rhiD->shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[rhiD->currentFrameSlot].descriptorByteSize;
+
+    struct CBufData {
+        quint32 srcMipLevel;
+        quint32 numMipLevels;
+        float texelWidth;
+        float texelHeight;
+    };
+
+    const quint32 allocSize = QD3D12StagingArea::allocSizeForArray(sizeof(CBufData), mipLevelCount * layerCount);
+    std::optional<QD3D12StagingArea> ownStagingArea;
+    if (rhiD->smallStagingAreas[rhiD->currentFrameSlot].remainingCapacity() < allocSize) {
+        ownStagingArea = QD3D12StagingArea();
+        if (!ownStagingArea->create(rhiD, allocSize, D3D12_HEAP_TYPE_UPLOAD)) {
+            qWarning("Could not create staging area for mipmap generation");
+            return;
+        }
+    }
+    QD3D12StagingArea *workArea = ownStagingArea.has_value()
+            ? &ownStagingArea.value()
+            : &rhiD->smallStagingAreas[rhiD->currentFrameSlot];
+
+    bool gotNewHeap = false;
+    if (!rhiD->ensureShaderVisibleDescriptorHeapCapacity(&rhiD->shaderVisibleCbvSrvUavHeap,
+                                                         D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
+                                                         rhiD->currentFrameSlot,
+                                                         (1 + 4) * mipLevelCount * layerCount,
+                                                         &gotNewHeap))
+    {
+        qWarning("Could not ensure enough space in descriptor heap for mipmap generation");
+        return;
+    }
+    if (gotNewHeap)
+        rhiD->bindShaderVisibleHeaps(cbD);
+
+    for (quint32 layer = 0; layer < layerCount; ++layer) {
+        for (quint32 level = 0; level < mipLevelCount ;) {
+            UINT subresource = calcSubresource(level, layer, res->desc.MipLevels);
+            rhiD->barrierGen.enqueueSubresourceTransitionBarrier(cbD, textureHandle, subresource,
+                                                                 D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
+                                                                 D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
+
+            quint32 levelPlusOneMipWidth = res->desc.Width >> (level + 1);
+            quint32 levelPlusOneMipHeight = res->desc.Height >> (level + 1);
+            const quint32 dw = levelPlusOneMipWidth == 1 ? levelPlusOneMipHeight : levelPlusOneMipWidth;
+            const quint32 dh = levelPlusOneMipHeight == 1 ? levelPlusOneMipWidth : levelPlusOneMipHeight;
+            // number of times the size can be halved while still resulting in an even dimension
+            const quint32 additionalMips = qCountTrailingZeroBits(dw | dh);
+            const quint32 numGenMips = qMin(1u + qMin(3u, additionalMips), res->desc.MipLevels - level);
+            levelPlusOneMipWidth = qMax(1u, levelPlusOneMipWidth);
+            levelPlusOneMipHeight = qMax(1u, levelPlusOneMipHeight);
+
+            CBufData cbufData = {
+                level,
+                numGenMips,
+                1.0f / float(levelPlusOneMipWidth),
+                1.0f / float(levelPlusOneMipHeight)
+            };
+
+            QD3D12StagingArea::Allocation cbuf = workArea->get(sizeof(cbufData));
+            memcpy(cbuf.p, &cbufData, sizeof(cbufData));
+            cbD->cmdList->SetComputeRootConstantBufferView(0, cbuf.gpuAddr);
+
+            QD3D12Descriptor srv = rhiD->shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[rhiD->currentFrameSlot].get(1);
+            D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
+            srvDesc.Format = res->desc.Format;
+            srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
+            if (isCubeOrArray) {
+                srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
+                srvDesc.Texture2DArray.MostDetailedMip = level;
+                srvDesc.Texture2DArray.MipLevels = 1;
+                srvDesc.Texture2DArray.FirstArraySlice = layer;
+                srvDesc.Texture2DArray.ArraySize = 1;
+            } else if (is3D) {
+                srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D;
+                srvDesc.Texture3D.MostDetailedMip = level;
+                srvDesc.Texture3D.MipLevels = 1;
+            } else {
+                srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
+                srvDesc.Texture2D.MostDetailedMip = level;
+                srvDesc.Texture2D.MipLevels = 1;
+            }
+            rhiD->dev->CreateShaderResourceView(res->resource, &srvDesc, srv.cpuHandle);
+            cbD->cmdList->SetComputeRootDescriptorTable(1, srv.gpuHandle);
+
+            QD3D12Descriptor uavStart = rhiD->shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[rhiD->currentFrameSlot].get(4);
+            D3D12_CPU_DESCRIPTOR_HANDLE uavCpuHandle = uavStart.cpuHandle;
+            // if level is N, then need UAVs for levels N+1, ..., N+4
+            for (quint32 uavIdx = 0; uavIdx < 4; ++uavIdx) {
+                const quint32 uavMipLevel = qMin(level + 1u + uavIdx, res->desc.MipLevels - 1u);
+                D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
+                uavDesc.Format = res->desc.Format;
+                if (isCubeOrArray) {
+                    uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
+                    uavDesc.Texture2DArray.MipSlice = uavMipLevel;
+                    uavDesc.Texture2DArray.FirstArraySlice = layer;
+                    uavDesc.Texture2DArray.ArraySize = 1;
+                } else if (is3D) {
+                    uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D;
+                    uavDesc.Texture3D.MipSlice = uavMipLevel;
+                    uavDesc.Texture3D.FirstWSlice = 0; // depth etc. not implemented yet
+                    uavDesc.Texture3D.WSize = 1;
+                } else {
+                    uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
+                    uavDesc.Texture2D.MipSlice = uavMipLevel;
+                }
+                rhiD->dev->CreateUnorderedAccessView(res->resource, nullptr, &uavDesc, uavCpuHandle);
+                uavCpuHandle.ptr += descriptorByteSize;
+            }
+            cbD->cmdList->SetComputeRootDescriptorTable(2, uavStart.gpuHandle);
+
+            cbD->cmdList->Dispatch(levelPlusOneMipWidth, levelPlusOneMipHeight, 1);
+
+            rhiD->barrierGen.enqueueUavBarrier(cbD, textureHandle);
+            rhiD->barrierGen.enqueueSubresourceTransitionBarrier(cbD, textureHandle, subresource,
+                                                                 D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
+                                                                 D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
+
+            level += numGenMips;
+        }
+    }
+
+    if (ownStagingArea.has_value())
+        ownStagingArea->destroyWithDeferredRelease(&rhiD->releaseQueue);
+}
+
+bool QD3D12MemoryAllocator::create(ID3D12Device *device, IDXGIAdapter1 *adapter)
+{
+    this->device = device;
+
+    // We can function with and without D3D12MA: CreateCommittedResource is
+    // just fine for our purposes and not any complicated API-wise; the memory
+    // allocator is interesting for efficiency mainly since it can suballocate
+    // instead of making everything a committed resource allocation.
+
+    static bool disableMA = qEnvironmentVariableIntValue("QT_D3D_NO_SUBALLOC");
+    if (disableMA)
+        return true;
+
+    DXGI_ADAPTER_DESC1 desc;
+    adapter->GetDesc1(&desc);
+    if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
+        return true;
+
+    D3D12MA::ALLOCATOR_DESC allocatorDesc = {};
+    allocatorDesc.pDevice = device;
+    allocatorDesc.pAdapter = adapter;
+    // A QRhi is supposed to be used from one single thread only. Disable
+    // the allocator's own mutexes. This may give a performance boost.
+    allocatorDesc.Flags = D3D12MA::ALLOCATOR_FLAG_SINGLETHREADED;
+    HRESULT hr = D3D12MA::CreateAllocator(&allocatorDesc, &allocator);
+    if (FAILED(hr)) {
+        qWarning("Failed to initialize D3D12 Memory Allocator: %s",
+                 qPrintable(QSystemError::windowsComString(hr)));
+        return false;
+    }
+    return true;
+}
+
+void QD3D12MemoryAllocator::destroy()
+{
+    if (allocator) {
+        allocator->Release();
+        allocator = nullptr;
+    }
+}
+
+HRESULT QD3D12MemoryAllocator::createResource(D3D12_HEAP_TYPE heapType,
+                                              const D3D12_RESOURCE_DESC *resourceDesc,
+                                              D3D12_RESOURCE_STATES initialState,
+                                              const D3D12_CLEAR_VALUE *optimizedClearValue,
+                                              D3D12MA::Allocation **maybeAllocation,
+                                              REFIID riidResource,
+                                              void **ppvResource)
+{
+    if (allocator) {
+        D3D12MA::ALLOCATION_DESC allocDesc = {};
+        allocDesc.HeapType = heapType;
+        return allocator->CreateResource(&allocDesc,
+                                         resourceDesc,
+                                         initialState,
+                                         optimizedClearValue,
+                                         maybeAllocation,
+                                         riidResource,
+                                         ppvResource);
+    } else {
+        *maybeAllocation = nullptr;
+        D3D12_HEAP_PROPERTIES heapProps = {};
+        heapProps.Type = heapType;
+        return device->CreateCommittedResource(&heapProps,
+                                               D3D12_HEAP_FLAG_NONE,
+                                               resourceDesc,
+                                               initialState,
+                                               optimizedClearValue,
+                                               riidResource,
+                                               ppvResource);
+    }
+}
+
+void QD3D12MemoryAllocator::getBudget(D3D12MA::Budget *localBudget, D3D12MA::Budget *nonLocalBudget)
+{
+    if (allocator) {
+        allocator->GetBudget(localBudget, nonLocalBudget);
+    } else {
+        *localBudget = {};
+        *nonLocalBudget = {};
+    }
+}
+
+void QRhiD3D12::waitGpu()
+{
+    fullFenceCounter += 1u;
+    if (SUCCEEDED(cmdQueue->Signal(fullFence, fullFenceCounter))) {
+        if (SUCCEEDED(fullFence->SetEventOnCompletion(fullFenceCounter, fullFenceEvent)))
+            WaitForSingleObject(fullFenceEvent, INFINITE);
+    }
+}
+
+DXGI_SAMPLE_DESC QRhiD3D12::effectiveSampleDesc(int sampleCount, DXGI_FORMAT format) const
+{
+    DXGI_SAMPLE_DESC desc;
+    desc.Count = 1;
+    desc.Quality = 0;
+
+    const int s = effectiveSampleCount(sampleCount);
+
+    if (s > 1) {
+        D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msaaInfo = {};
+        msaaInfo.Format = format;
+        msaaInfo.SampleCount = UINT(s);
+        if (SUCCEEDED(dev->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msaaInfo, sizeof(msaaInfo)))) {
+            if (msaaInfo.NumQualityLevels > 0) {
+                desc.Count = UINT(s);
+                desc.Quality = msaaInfo.NumQualityLevels - 1;
+            } else {
+                qWarning("No quality levels for multisampling with sample count %d", s);
+            }
+        }
+    }
+
+    return desc;
+}
+
+bool QRhiD3D12::startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList1 **cmdList)
+{
+    ID3D12CommandAllocator *cmdAlloc = cmdAllocators[currentFrameSlot];
+    if (!*cmdList) {
+        HRESULT hr = dev->CreateCommandList(0,
+                                            D3D12_COMMAND_LIST_TYPE_DIRECT,
+                                            cmdAlloc,
+                                            nullptr,
+                                            __uuidof(ID3D12GraphicsCommandList1),
+                                            reinterpret_cast<void **>(cmdList));
+        if (FAILED(hr)) {
+            qWarning("Failed to create command list: %s", qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+    } else {
+        HRESULT hr = (*cmdList)->Reset(cmdAlloc, nullptr);
+        if (FAILED(hr)) {
+            qWarning("Failed to reset command list: %s", qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+    }
+    return true;
+}
+
+static inline QRhiTexture::Format swapchainReadbackTextureFormat(DXGI_FORMAT format, QRhiTexture::Flags *flags)
+{
+    switch (format) {
+    case DXGI_FORMAT_R8G8B8A8_UNORM:
+        return QRhiTexture::RGBA8;
+    case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+        if (flags)
+            (*flags) |= QRhiTexture::sRGB;
+        return QRhiTexture::RGBA8;
+    case DXGI_FORMAT_B8G8R8A8_UNORM:
+        return QRhiTexture::BGRA8;
+    case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
+        if (flags)
+            (*flags) |= QRhiTexture::sRGB;
+        return QRhiTexture::BGRA8;
+    case DXGI_FORMAT_R16G16B16A16_FLOAT:
+        return QRhiTexture::RGBA16F;
+    case DXGI_FORMAT_R32G32B32A32_FLOAT:
+        return QRhiTexture::RGBA32F;
+    case DXGI_FORMAT_R10G10B10A2_UNORM:
+        return QRhiTexture::RGB10A2;
+    default:
+        qWarning("DXGI_FORMAT %d cannot be read back", format);
+        break;
+    }
+    return QRhiTexture::UnknownFormat;
+}
+
+void QRhiD3D12::enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates)
+{
+    QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
+
+    for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) {
+        const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]);
+        if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) {
+            QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, u.buf);
+            Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
+            for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
+                if (u.offset == 0 && u.data.size() == bufD->m_size)
+                    bufD->pendingHostWrites[i].clear();
+                bufD->pendingHostWrites[i].append({ u.offset, u.data });
+            }
+        } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) {
+            QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, u.buf);
+            Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
+            Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
+
+            // The general approach to staging upload data is to first try
+            // using the per-frame "small" staging area, which is a very simple
+            // linear allocator; if that's not big enough then create a
+            // dedicated StagingArea and then deferred-release it to make sure
+            // if stays alive while the frame is possibly still in flight.
+
+            QD3D12StagingArea::Allocation stagingAlloc;
+            const quint32 allocSize = QD3D12StagingArea::allocSizeForArray(bufD->m_size, 1);
+            if (smallStagingAreas[currentFrameSlot].remainingCapacity() >= allocSize)
+                stagingAlloc = smallStagingAreas[currentFrameSlot].get(bufD->m_size);
+
+            std::optional<QD3D12StagingArea> ownStagingArea;
+            if (!stagingAlloc.isValid()) {
+                ownStagingArea = QD3D12StagingArea();
+                if (!ownStagingArea->create(this, allocSize, D3D12_HEAP_TYPE_UPLOAD))
+                    continue;
+                stagingAlloc = ownStagingArea->get(allocSize);
+                if (!stagingAlloc.isValid()) {
+                    ownStagingArea->destroy();
+                    continue;
+                }
+            }
+
+            memcpy(stagingAlloc.p + u.offset, u.data.constData(), u.data.size());
+
+            barrierGen.addTransitionBarrier(bufD->handles[0], D3D12_RESOURCE_STATE_COPY_DEST);
+            barrierGen.enqueueBufferedTransitionBarriers(cbD);
+
+            if (QD3D12Resource *res = resourcePool.lookupRef(bufD->handles[0])) {
+                cbD->cmdList->CopyBufferRegion(res->resource,
+                                               u.offset,
+                                               stagingAlloc.buffer,
+                                               stagingAlloc.bufferOffset + u.offset,
+                                               u.data.size());
+            }
+
+            if (ownStagingArea.has_value())
+                ownStagingArea->destroyWithDeferredRelease(&releaseQueue);
+        } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) {
+            QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, u.buf);
+            if (bufD->m_type == QRhiBuffer::Dynamic) {
+                bufD->executeHostWritesForFrameSlot(currentFrameSlot);
+                if (QD3D12Resource *res = resourcePool.lookupRef(bufD->handles[currentFrameSlot])) {
+                    Q_ASSERT(res->cpuMapPtr);
+                    u.result->data.resize(u.readSize);
+                    memcpy(u.result->data.data(), reinterpret_cast<char *>(res->cpuMapPtr) + u.offset, u.readSize);
+                }
+                if (u.result->completed)
+                    u.result->completed();
+            } else {
+                QD3D12Readback readback;
+                readback.frameSlot = currentFrameSlot;
+                readback.result = u.result;
+                readback.byteSize = u.readSize;
+                const quint32 allocSize = aligned(u.readSize, QD3D12StagingArea::ALIGNMENT);
+                if (!readback.staging.create(this, allocSize, D3D12_HEAP_TYPE_READBACK)) {
+                    if (u.result->completed)
+                        u.result->completed();
+                    continue;
+                }
+                QD3D12StagingArea::Allocation stagingAlloc = readback.staging.get(u.readSize);
+                if (!stagingAlloc.isValid()) {
+                    readback.staging.destroy();
+                    if (u.result->completed)
+                        u.result->completed();
+                    continue;
+                }
+                Q_ASSERT(stagingAlloc.bufferOffset == 0);
+                barrierGen.addTransitionBarrier(bufD->handles[0], D3D12_RESOURCE_STATE_COPY_SOURCE);
+                barrierGen.enqueueBufferedTransitionBarriers(cbD);
+                if (QD3D12Resource *res = resourcePool.lookupRef(bufD->handles[0])) {
+                    cbD->cmdList->CopyBufferRegion(stagingAlloc.buffer, 0, res->resource, u.offset, u.readSize);
+                    activeReadbacks.append(readback);
+                } else {
+                    readback.staging.destroy();
+                    if (u.result->completed)
+                        u.result->completed();
+                }
+            }
+        }
+    }
+
+    for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) {
+        const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]);
+        if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
+            QD3D12Texture *texD = QRHI_RES(QD3D12Texture, u.dst);
+            const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
+            QD3D12Resource *res = resourcePool.lookupRef(texD->handle);
+            if (!res)
+                continue;
+            barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATE_COPY_DEST);
+            barrierGen.enqueueBufferedTransitionBarriers(cbD);
+            for (int layer = 0, maxLayer = u.subresDesc.size(); layer < maxLayer; ++layer) {
+                for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) {
+                    for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(u.subresDesc[layer][level])) {
+                        D3D12_SUBRESOURCE_FOOTPRINT footprint = {};
+                        footprint.Format = res->desc.Format;
+                        footprint.Depth = 1;
+                        quint32 totalBytes = 0;
+
+                        const QSize subresSize = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
+                                                                                   : subresDesc.sourceSize();
+                        const QPoint srcPos = subresDesc.sourceTopLeft();
+                        QPoint dstPos = subresDesc.destinationTopLeft();
+
+                        if (!subresDesc.image().isNull()) {
+                            const QImage img = subresDesc.image();
+                            const int bpl = img.bytesPerLine();
+                            footprint.RowPitch = aligned<UINT>(bpl, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
+                            totalBytes = footprint.RowPitch * img.height();
+                        } else if (!subresDesc.data().isEmpty() && isCompressedFormat(texD->m_format)) {
+                            QSize blockDim;
+                            quint32 bpl = 0;
+                            compressedFormatInfo(texD->m_format, subresSize, &bpl, nullptr, &blockDim);
+                            footprint.RowPitch = aligned<UINT>(bpl, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
+                            const int rowCount = aligned(subresSize.height(), blockDim.height()) / blockDim.height();
+                            totalBytes = footprint.RowPitch * rowCount;
+                        } else if (!subresDesc.data().isEmpty()) {
+                            quint32 bpl = 0;
+                            if (subresDesc.dataStride())
+                                bpl = subresDesc.dataStride();
+                            else
+                                textureFormatInfo(texD->m_format, subresSize, &bpl, nullptr, nullptr);
+                            footprint.RowPitch = aligned<UINT>(bpl, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
+                            totalBytes = footprint.RowPitch * subresSize.height();
+                        } else {
+                            qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
+                            continue;
+                        }
+
+                        const quint32 allocSize = QD3D12StagingArea::allocSizeForArray(totalBytes, 1);
+                        QD3D12StagingArea::Allocation stagingAlloc;
+                        if (smallStagingAreas[currentFrameSlot].remainingCapacity() >= allocSize)
+                            stagingAlloc = smallStagingAreas[currentFrameSlot].get(allocSize);
+
+                        std::optional<QD3D12StagingArea> ownStagingArea;
+                        if (!stagingAlloc.isValid()) {
+                            ownStagingArea = QD3D12StagingArea();
+                            if (!ownStagingArea->create(this, allocSize, D3D12_HEAP_TYPE_UPLOAD))
+                                continue;
+                            stagingAlloc = ownStagingArea->get(allocSize);
+                            if (!stagingAlloc.isValid()) {
+                                ownStagingArea->destroy();
+                                continue;
+                            }
+                        }
+
+                        D3D12_TEXTURE_COPY_LOCATION dst;
+                        dst.pResource = res->resource;
+                        dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
+                        dst.SubresourceIndex = calcSubresource(UINT(level), is3D ? 0u : UINT(layer), texD->mipLevelCount);
+                        D3D12_TEXTURE_COPY_LOCATION src;
+                        src.pResource = stagingAlloc.buffer;
+                        src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
+                        src.PlacedFootprint.Offset = stagingAlloc.bufferOffset;
+
+                        D3D12_BOX srcBox; // back, right, bottom are exclusive
+
+                        if (!subresDesc.image().isNull()) {
+                            const QImage img = subresDesc.image();
+                            const int bpc = qMax(1, img.depth() / 8);
+                            const int bpl = img.bytesPerLine();
+
+                            QSize size = subresDesc.sourceSize().isEmpty() ? img.size() : subresDesc.sourceSize();
+                            size.setWidth(qMin(size.width(), img.width() - srcPos.x()));
+                            size.setHeight(qMin(size.height(), img.height() - srcPos.y()));
+
+                            footprint.Width = size.width();
+                            footprint.Height = size.height();
+
+                            srcBox.left = 0;
+                            srcBox.top = 0;
+                            srcBox.right = UINT(size.width());
+                            srcBox.bottom = UINT(size.height());
+                            srcBox.front = 0;
+                            srcBox.back = 1;
+
+                            const uchar *imgPtr = img.constBits();
+                            const quint32 lineBytes = size.width() * bpc;
+                            for (int y = 0, h = size.height(); y < h; ++y) {
+                                memcpy(stagingAlloc.p + y * footprint.RowPitch,
+                                       imgPtr + srcPos.x() * bpc + (y + srcPos.y()) * bpl,
+                                       lineBytes);
+                            }
+                        } else if (!subresDesc.data().isEmpty() && isCompressedFormat(texD->m_format)) {
+                            QSize blockDim;
+                            quint32 bpl = 0;
+                            compressedFormatInfo(texD->m_format, subresSize, &bpl, nullptr, &blockDim);
+                            // x and y must be multiples of the block width and height
+                            dstPos.setX(aligned(dstPos.x(), blockDim.width()));
+                            dstPos.setY(aligned(dstPos.y(), blockDim.height()));
+
+                            srcBox.left = 0;
+                            srcBox.top = 0;
+                            // width and height must be multiples of the block width and height
+                            srcBox.right = aligned(subresSize.width(), blockDim.width());
+                            srcBox.bottom = aligned(subresSize.height(), blockDim.height());
+
+                            srcBox.front = 0;
+                            srcBox.back = 1;
+
+                            footprint.Width = aligned(subresSize.width(), blockDim.width());
+                            footprint.Height = aligned(subresSize.height(), blockDim.height());
+
+                            const quint32 copyBytes = qMin(bpl, footprint.RowPitch);
+                            const QByteArray imgData = subresDesc.data();
+                            const char *imgPtr = imgData.constData();
+                            const int rowCount = aligned(subresSize.height(), blockDim.height()) / blockDim.height();
+                            for (int y = 0; y < rowCount; ++y)
+                                memcpy(stagingAlloc.p + y * footprint.RowPitch, imgPtr + y * bpl, copyBytes);
+                        } else if (!subresDesc.data().isEmpty()) {
+                            srcBox.left = 0;
+                            srcBox.top = 0;
+                            srcBox.right = subresSize.width();
+                            srcBox.bottom = subresSize.height();
+                            srcBox.front = 0;
+                            srcBox.back = 1;
+
+                            footprint.Width = subresSize.width();
+                            footprint.Height = subresSize.height();
+
+                            quint32 bpl = 0;
+                            if (subresDesc.dataStride())
+                                bpl = subresDesc.dataStride();
+                            else
+                                textureFormatInfo(texD->m_format, subresSize, &bpl, nullptr, nullptr);
+
+                            const quint32 copyBytes = qMin(bpl, footprint.RowPitch);
+                            const QByteArray data = subresDesc.data();
+                            const char *imgPtr = data.constData();
+                            for (int y = 0, h = subresSize.height(); y < h; ++y)
+                                memcpy(stagingAlloc.p + y * footprint.RowPitch, imgPtr + y * bpl, copyBytes);
+                        }
+
+                        src.PlacedFootprint.Footprint = footprint;
+
+                        cbD->cmdList->CopyTextureRegion(&dst,
+                                                        UINT(dstPos.x()),
+                                                        UINT(dstPos.y()),
+                                                        is3D ? UINT(layer) : 0u,
+                                                        &src,
+                                                        &srcBox);
+
+                        if (ownStagingArea.has_value())
+                            ownStagingArea->destroyWithDeferredRelease(&releaseQueue);
+                    }
+                }
+            }
+        } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
+            Q_ASSERT(u.src && u.dst);
+            QD3D12Texture *srcD = QRHI_RES(QD3D12Texture, u.src);
+            QD3D12Texture *dstD = QRHI_RES(QD3D12Texture, u.dst);
+            const bool srcIs3D = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
+            const bool dstIs3D = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
+            QD3D12Resource *srcRes = resourcePool.lookupRef(srcD->handle);
+            QD3D12Resource *dstRes = resourcePool.lookupRef(dstD->handle);
+            if (!srcRes || !dstRes)
+                continue;
+
+            barrierGen.addTransitionBarrier(srcD->handle, D3D12_RESOURCE_STATE_COPY_SOURCE);
+            barrierGen.addTransitionBarrier(dstD->handle, D3D12_RESOURCE_STATE_COPY_DEST);
+            barrierGen.enqueueBufferedTransitionBarriers(cbD);
+
+            const UINT srcSubresource = calcSubresource(UINT(u.desc.sourceLevel()),
+                                                        srcIs3D ? 0u : UINT(u.desc.sourceLayer()),
+                                                        srcD->mipLevelCount);
+            const UINT dstSubresource = calcSubresource(UINT(u.desc.destinationLevel()),
+                                                        dstIs3D ? 0u : UINT(u.desc.destinationLayer()),
+                                                        dstD->mipLevelCount);
+            const QPoint dp = u.desc.destinationTopLeft();
+            const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
+            const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
+            const QPoint sp = u.desc.sourceTopLeft();
+
+            D3D12_BOX srcBox;
+            srcBox.left = UINT(sp.x());
+            srcBox.top = UINT(sp.y());
+            srcBox.front = srcIs3D ? UINT(u.desc.sourceLayer()) : 0u;
+            // back, right, bottom are exclusive
+            srcBox.right = srcBox.left + UINT(copySize.width());
+            srcBox.bottom = srcBox.top + UINT(copySize.height());
+            srcBox.back = srcBox.front + 1;
+
+            D3D12_TEXTURE_COPY_LOCATION src;
+            src.pResource = srcRes->resource;
+            src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
+            src.SubresourceIndex = srcSubresource;
+            D3D12_TEXTURE_COPY_LOCATION dst;
+            dst.pResource = dstRes->resource;
+            dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
+            dst.SubresourceIndex = dstSubresource;
+
+            cbD->cmdList->CopyTextureRegion(&dst,
+                                            UINT(dp.x()),
+                                            UINT(dp.y()),
+                                            dstIs3D ? UINT(u.desc.destinationLayer()) : 0u,
+                                            &src,
+                                            &srcBox);
+        } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
+            QD3D12Readback readback;
+            readback.frameSlot = currentFrameSlot;
+            readback.result = u.result;
+
+            QD3D12ObjectHandle srcHandle;
+            bool is3D = false;
+            if (u.rb.texture()) {
+                QD3D12Texture *texD = QRHI_RES(QD3D12Texture, u.rb.texture());
+                if (texD->sampleDesc.Count > 1) {
+                    qWarning("Multisample texture cannot be read back");
+                    continue;
+                }
+                is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
+                readback.pixelSize = q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize);
+                readback.format = texD->m_format;
+                srcHandle = texD->handle;
+            } else {
+                Q_ASSERT(currentSwapChain);
+                readback.pixelSize = currentSwapChain->pixelSize;
+                readback.format = swapchainReadbackTextureFormat(currentSwapChain->colorFormat, nullptr);
+                if (readback.format == QRhiTexture::UnknownFormat)
+                    continue;
+                srcHandle = currentSwapChain->colorBuffers[currentSwapChain->currentBackBufferIndex];
+            }
+
+            textureFormatInfo(readback.format,
+                              readback.pixelSize,
+                              &readback.bytesPerLine,
+                              &readback.byteSize,
+                              nullptr);
+
+            QD3D12Resource *srcRes = resourcePool.lookupRef(srcHandle);
+            if (!srcRes)
+                continue;
+
+            const UINT subresource = calcSubresource(UINT(u.rb.level()),
+                                                     is3D ? 0u : UINT(u.rb.layer()),
+                                                     srcRes->desc.MipLevels);
+            D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout;
+            // totalBytes is what we get from D3D, with the 256 aligned stride,
+            // readback.byteSize is the final result that's not relevant here yet
+            UINT64 totalBytes = 0;
+            dev->GetCopyableFootprints(&srcRes->desc, subresource, 1, 0,
+                                       &layout, nullptr, nullptr, &totalBytes);
+            readback.stagingRowPitch = layout.Footprint.RowPitch;
+
+            const quint32 allocSize = aligned<quint32>(totalBytes, QD3D12StagingArea::ALIGNMENT);
+            if (!readback.staging.create(this, allocSize, D3D12_HEAP_TYPE_READBACK)) {
+                if (u.result->completed)
+                    u.result->completed();
+                continue;
+            }
+            QD3D12StagingArea::Allocation stagingAlloc = readback.staging.get(totalBytes);
+            if (!stagingAlloc.isValid()) {
+                readback.staging.destroy();
+                if (u.result->completed)
+                    u.result->completed();
+                continue;
+            }
+            Q_ASSERT(stagingAlloc.bufferOffset == 0);
+
+            barrierGen.addTransitionBarrier(srcHandle, D3D12_RESOURCE_STATE_COPY_SOURCE);
+            barrierGen.enqueueBufferedTransitionBarriers(cbD);
+
+            D3D12_TEXTURE_COPY_LOCATION dst;
+            dst.pResource = stagingAlloc.buffer;
+            dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
+            dst.PlacedFootprint.Offset = 0;
+            dst.PlacedFootprint.Footprint = layout.Footprint;
+
+            D3D12_TEXTURE_COPY_LOCATION src;
+            src.pResource = srcRes->resource;
+            src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
+            src.SubresourceIndex = subresource;
+
+            D3D12_BOX srcBox = {};
+            if (is3D) {
+                srcBox.front = UINT(u.rb.layer());
+                srcBox.back = srcBox.front + 1;
+                srcBox.right = readback.pixelSize.width(); // exclusive
+                srcBox.bottom = readback.pixelSize.height();
+            }
+            cbD->cmdList->CopyTextureRegion(&dst, 0, 0, 0, &src, is3D ? &srcBox : nullptr);
+            activeReadbacks.append(readback);
+        } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
+            QD3D12Texture *texD = QRHI_RES(QD3D12Texture, u.dst);
+            Q_ASSERT(texD->flags().testFlag(QRhiTexture::UsedWithGenerateMips));
+            mipmapGen.generate(cbD, texD->handle);
+        }
+    }
+
+    ud->free();
+}
+
+void QRhiD3D12::finishActiveReadbacks(bool forced)
+{
+    QVarLengthArray<std::function<void()>, 4> completedCallbacks;
+
+    for (int i = activeReadbacks.size() - 1; i >= 0; --i) {
+        QD3D12Readback &readback(activeReadbacks[i]);
+        if (forced || currentFrameSlot == readback.frameSlot || readback.frameSlot < 0) {
+            readback.result->format = readback.format;
+            readback.result->pixelSize = readback.pixelSize;
+            readback.result->data.resize(int(readback.byteSize));
+
+            if (readback.format != QRhiTexture::UnknownFormat) {
+                quint8 *dstPtr = reinterpret_cast<quint8 *>(readback.result->data.data());
+                const quint8 *srcPtr = readback.staging.mem.p;
+                const quint32 lineSize = qMin(readback.bytesPerLine, readback.stagingRowPitch);
+                for (int y = 0, h = readback.pixelSize.height(); y < h; ++y)
+                    memcpy(dstPtr + y * readback.bytesPerLine, srcPtr + y * readback.stagingRowPitch, lineSize);
+            } else {
+                memcpy(readback.result->data.data(), readback.staging.mem.p, readback.byteSize);
+            }
+
+            readback.staging.destroy();
+
+            if (readback.result->completed)
+                completedCallbacks.append(readback.result->completed);
+
+            activeReadbacks.removeLast();
+        }
+    }
+
+    for (auto f : completedCallbacks)
+        f();
+}
+
+bool QRhiD3D12::ensureShaderVisibleDescriptorHeapCapacity(QD3D12ShaderVisibleDescriptorHeap *h,
+                                                          D3D12_DESCRIPTOR_HEAP_TYPE type,
+                                                          int frameSlot,
+                                                          quint32 neededDescriptorCount,
+                                                          bool *gotNew)
+{
+    // Gets a new heap if needed. Note that the capacity we get is clamped
+    // automatically (e.g. to 1 million, or 2048 for samplers), so * 2 does not
+    // mean we can grow indefinitely, then again even using the same size would
+    // work (because we what we are after here is a new heap for the rest of
+    // the commands, not affecting what's already recorded).
+    if (h->perFrameHeapSlice[frameSlot].remainingCapacity() < neededDescriptorCount) {
+        const quint32 newPerFrameSize = qMax(h->perFrameHeapSlice[frameSlot].capacity * 2,
+                                             neededDescriptorCount);
+        QD3D12ShaderVisibleDescriptorHeap newHeap;
+        if (!newHeap.create(dev, type, newPerFrameSize)) {
+            qWarning("Could not create new shader-visible descriptor heap");
+            return false;
+        }
+        h->destroyWithDeferredRelease(&releaseQueue);
+        *h = newHeap;
+        *gotNew = true;
+    }
+    return true;
+}
+
+void QRhiD3D12::bindShaderVisibleHeaps(QD3D12CommandBuffer *cbD)
+{
+    ID3D12DescriptorHeap *heaps[] = {
+        shaderVisibleCbvSrvUavHeap.heap.heap,
+        samplerMgr.shaderVisibleSamplerHeap.heap.heap
+    };
+    cbD->cmdList->SetDescriptorHeaps(2, heaps);
+}
+
+QD3D12Buffer::QD3D12Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size)
+    : QRhiBuffer(rhi, type, usage, size)
+{
+}
+
+QD3D12Buffer::~QD3D12Buffer()
+{
+    destroy();
+}
+
+void QD3D12Buffer::destroy()
+{
+    if (handles[0].isNull())
+        return;
+
+    QRHI_RES_RHI(QRhiD3D12);
+
+    // destroy() implementations, unlike other functions, are expected to test
+    // for m_rhi (rhiD) being null, to allow surviving in case one attempts to
+    // destroy a (leaked) resource after the QRhi.
+    //
+    // If there is no QRhi anymore, we do not deferred-release but that's fine
+    // since the QRhi already released everything that was in the resourcePool.
+
+    for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
+        if (rhiD)
+            rhiD->releaseQueue.deferredReleaseResource(handles[i]);
+        handles[i] = {};
+        pendingHostWrites[i].clear();
+    }
+
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+bool QD3D12Buffer::create()
+{
+    if (!handles[0].isNull())
+        destroy();
+
+    if (m_usage.testFlag(QRhiBuffer::UniformBuffer) && m_type != Dynamic) {
+        qWarning("UniformBuffer must always be Dynamic");
+        return false;
+    }
+
+    if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
+        qWarning("StorageBuffer cannot be combined with Dynamic");
+        return false;
+    }
+
+    const quint32 nonZeroSize = m_size <= 0 ? 256 : m_size;
+    const quint32 roundedSize = aligned(nonZeroSize, m_usage.testFlag(QRhiBuffer::UniformBuffer) ? 256u : 4u);
+
+    UINT resourceFlags = D3D12_RESOURCE_FLAG_NONE;
+    if (m_usage.testFlag(QRhiBuffer::StorageBuffer))
+        resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
+
+    QRHI_RES_RHI(QRhiD3D12);
+    HRESULT hr = 0;
+    for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
+        if (i == 0 || m_type == Dynamic) {
+            D3D12_RESOURCE_DESC resourceDesc = {};
+            resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
+            resourceDesc.Width = roundedSize;
+            resourceDesc.Height = 1;
+            resourceDesc.DepthOrArraySize = 1;
+            resourceDesc.MipLevels = 1;
+            resourceDesc.Format = DXGI_FORMAT_UNKNOWN;
+            resourceDesc.SampleDesc = { 1, 0 };
+            resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
+            resourceDesc.Flags = D3D12_RESOURCE_FLAGS(resourceFlags);
+            ID3D12Resource *resource = nullptr;
+            D3D12MA::Allocation *allocation = nullptr;
+            // Dynamic == host (CPU) visible
+            D3D12_HEAP_TYPE heapType = m_type == Dynamic
+                    ? D3D12_HEAP_TYPE_UPLOAD
+                    : D3D12_HEAP_TYPE_DEFAULT;
+            D3D12_RESOURCE_STATES resourceState = m_type == Dynamic
+                    ? D3D12_RESOURCE_STATE_GENERIC_READ
+                    : D3D12_RESOURCE_STATE_COMMON;
+            hr = rhiD->vma.createResource(heapType,
+                                          &resourceDesc,
+                                          resourceState,
+                                          nullptr,
+                                          &allocation,
+                                          __uuidof(resource),
+                                          reinterpret_cast<void **>(&resource));
+            if (FAILED(hr))
+                break;
+            if (!m_objectName.isEmpty()) {
+                QString decoratedName = QString::fromUtf8(m_objectName);
+                if (m_type == Dynamic) {
+                    decoratedName += QLatin1Char('/');
+                    decoratedName += QString::number(i);
+                }
+                resource->SetName(reinterpret_cast<LPCWSTR>(decoratedName.utf16()));
+            }
+            void *cpuMemPtr = nullptr;
+            if (m_type == Dynamic) {
+                // will be mapped for ever on the CPU, this makes future host write operations very simple
+                hr = resource->Map(0, nullptr, &cpuMemPtr);
+                if (FAILED(hr)) {
+                    qWarning("Map() failed to dynamic buffer");
+                    resource->Release();
+                    if (allocation)
+                        allocation->Release();
+                    break;
+                }
+            }
+            handles[i] = QD3D12Resource::addToPool(&rhiD->resourcePool,
+                                                   resource,
+                                                   resourceState,
+                                                   allocation,
+                                                   cpuMemPtr);
+        }
+    }
+    if (FAILED(hr)) {
+        qWarning("Failed to create buffer: '%s' Type was %d, size was %u, using D3D12MA was %d.",
+                 qPrintable(QSystemError::windowsComString(hr)),
+                 int(m_type),
+                 roundedSize,
+                 int(rhiD->vma.isUsingD3D12MA()));
+        return false;
+    }
+
+    rhiD->registerResource(this);
+    return true;
+}
+
+QRhiBuffer::NativeBuffer QD3D12Buffer::nativeBuffer()
+{
+    NativeBuffer b;
+    Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QD3D12_FRAMES_IN_FLIGHT));
+    QRHI_RES_RHI(QRhiD3D12);
+    if (m_type == Dynamic) {
+        for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
+            executeHostWritesForFrameSlot(i);
+            if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handles[i]))
+                b.objects[i] = res->resource;
+            else
+                b.objects[i] = nullptr;
+        }
+        b.slotCount = QD3D12_FRAMES_IN_FLIGHT;
+        return b;
+    }
+    if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handles[0]))
+        b.objects[0] = res->resource;
+    else
+        b.objects[0] = nullptr;
+    b.slotCount = 1;
+    return b;
+}
+
+char *QD3D12Buffer::beginFullDynamicBufferUpdateForCurrentFrame()
+{
+    // Shortcut the entire buffer update mechanism and allow the client to do
+    // the host writes directly to the buffer. This will lead to unexpected
+    // results when combined with QRhiResourceUpdateBatch-based updates for the
+    // buffer, but provides a fast path for dynamic buffers that have all their
+    // content changed in every frame.
+
+    Q_ASSERT(m_type == Dynamic);
+    QRHI_RES_RHI(QRhiD3D12);
+    Q_ASSERT(rhiD->inFrame);
+    if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handles[rhiD->currentFrameSlot]))
+        return static_cast<char *>(res->cpuMapPtr);
+
+    return nullptr;
+}
+
+void QD3D12Buffer::endFullDynamicBufferUpdateForCurrentFrame()
+{
+    // nothing to do here
+}
+
+void QD3D12Buffer::executeHostWritesForFrameSlot(int frameSlot)
+{
+    if (pendingHostWrites[frameSlot].isEmpty())
+        return;
+
+    Q_ASSERT(m_type == QRhiBuffer::Dynamic);
+    QRHI_RES_RHI(QRhiD3D12);
+    if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handles[frameSlot])) {
+        Q_ASSERT(res->cpuMapPtr);
+        for (const QD3D12Buffer::HostWrite &u : std::as_const(pendingHostWrites[frameSlot]))
+            memcpy(static_cast<char *>(res->cpuMapPtr) + u.offset, u.data.constData(), u.data.size());
+    }
+    pendingHostWrites[frameSlot].clear();
+}
+
+static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
+{
+    const bool srgb = flags.testFlag(QRhiTexture::sRGB);
+    switch (format) {
+    case QRhiTexture::RGBA8:
+        return srgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM;
+    case QRhiTexture::BGRA8:
+        return srgb ? DXGI_FORMAT_B8G8R8A8_UNORM_SRGB : DXGI_FORMAT_B8G8R8A8_UNORM;
+    case QRhiTexture::R8:
+        return DXGI_FORMAT_R8_UNORM;
+    case QRhiTexture::RG8:
+        return DXGI_FORMAT_R8G8_UNORM;
+    case QRhiTexture::R16:
+        return DXGI_FORMAT_R16_UNORM;
+    case QRhiTexture::RG16:
+        return DXGI_FORMAT_R16G16_UNORM;
+    case QRhiTexture::RED_OR_ALPHA8:
+        return DXGI_FORMAT_R8_UNORM;
+
+    case QRhiTexture::RGBA16F:
+        return DXGI_FORMAT_R16G16B16A16_FLOAT;
+    case QRhiTexture::RGBA32F:
+        return DXGI_FORMAT_R32G32B32A32_FLOAT;
+    case QRhiTexture::R16F:
+        return DXGI_FORMAT_R16_FLOAT;
+    case QRhiTexture::R32F:
+        return DXGI_FORMAT_R32_FLOAT;
+
+    case QRhiTexture::RGB10A2:
+        return DXGI_FORMAT_R10G10B10A2_UNORM;
+
+    case QRhiTexture::D16:
+        return DXGI_FORMAT_R16_TYPELESS;
+    case QRhiTexture::D24:
+        return DXGI_FORMAT_R24G8_TYPELESS;
+    case QRhiTexture::D24S8:
+        return DXGI_FORMAT_R24G8_TYPELESS;
+    case QRhiTexture::D32F:
+        return DXGI_FORMAT_R32_TYPELESS;
+
+    case QRhiTexture::BC1:
+        return srgb ? DXGI_FORMAT_BC1_UNORM_SRGB : DXGI_FORMAT_BC1_UNORM;
+    case QRhiTexture::BC2:
+        return srgb ? DXGI_FORMAT_BC2_UNORM_SRGB : DXGI_FORMAT_BC2_UNORM;
+    case QRhiTexture::BC3:
+        return srgb ? DXGI_FORMAT_BC3_UNORM_SRGB : DXGI_FORMAT_BC3_UNORM;
+    case QRhiTexture::BC4:
+        return DXGI_FORMAT_BC4_UNORM;
+    case QRhiTexture::BC5:
+        return DXGI_FORMAT_BC5_UNORM;
+    case QRhiTexture::BC6H:
+        return DXGI_FORMAT_BC6H_UF16;
+    case QRhiTexture::BC7:
+        return srgb ? DXGI_FORMAT_BC7_UNORM_SRGB : DXGI_FORMAT_BC7_UNORM;
+
+    case QRhiTexture::ETC2_RGB8:
+    case QRhiTexture::ETC2_RGB8A1:
+    case QRhiTexture::ETC2_RGBA8:
+        qWarning("QRhiD3D12 does not support ETC2 textures");
+        return DXGI_FORMAT_R8G8B8A8_UNORM;
+
+    case QRhiTexture::ASTC_4x4:
+    case QRhiTexture::ASTC_5x4:
+    case QRhiTexture::ASTC_5x5:
+    case QRhiTexture::ASTC_6x5:
+    case QRhiTexture::ASTC_6x6:
+    case QRhiTexture::ASTC_8x5:
+    case QRhiTexture::ASTC_8x6:
+    case QRhiTexture::ASTC_8x8:
+    case QRhiTexture::ASTC_10x5:
+    case QRhiTexture::ASTC_10x6:
+    case QRhiTexture::ASTC_10x8:
+    case QRhiTexture::ASTC_10x10:
+    case QRhiTexture::ASTC_12x10:
+    case QRhiTexture::ASTC_12x12:
+        qWarning("QRhiD3D12 does not support ASTC textures");
+        return DXGI_FORMAT_R8G8B8A8_UNORM;
+
+    default:
+        break;
+    }
+    return DXGI_FORMAT_R8G8B8A8_UNORM;
+}
+
+QD3D12RenderBuffer::QD3D12RenderBuffer(QRhiImplementation *rhi,
+                                       Type type,
+                                       const QSize &pixelSize,
+                                       int sampleCount,
+                                       Flags flags,
+                                       QRhiTexture::Format backingFormatHint)
+    : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags, backingFormatHint)
+{
+}
+
+QD3D12RenderBuffer::~QD3D12RenderBuffer()
+{
+    destroy();
+}
+
+void QD3D12RenderBuffer::destroy()
+{
+    if (handle.isNull())
+        return;
+
+    QRHI_RES_RHI(QRhiD3D12);
+    if (rhiD) {
+        if (rtv.isValid())
+            rhiD->releaseQueue.deferredReleaseResourceWithViews(handle, &rhiD->rtvPool, rtv, 1);
+        else if (dsv.isValid())
+            rhiD->releaseQueue.deferredReleaseResourceWithViews(handle, &rhiD->dsvPool, dsv, 1);
+    }
+
+    handle = {};
+    rtv = {};
+    dsv = {};
+
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+bool QD3D12RenderBuffer::create()
+{
+    if (!handle.isNull())
+        destroy();
+
+    if (m_pixelSize.isEmpty())
+        return false;
+
+    QRHI_RES_RHI(QRhiD3D12);
+
+    switch (m_type) {
+    case QRhiRenderBuffer::Color:
+    {
+        dxgiFormat = toD3DTextureFormat(backingFormat(), {});
+        sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, dxgiFormat);
+        D3D12_RESOURCE_DESC resourceDesc = {};
+        resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
+        resourceDesc.Width = UINT64(m_pixelSize.width());
+        resourceDesc.Height = UINT(m_pixelSize.height());
+        resourceDesc.DepthOrArraySize = 1;
+        resourceDesc.MipLevels = 1;
+        resourceDesc.Format = dxgiFormat;
+        resourceDesc.SampleDesc = sampleDesc;
+        resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
+        resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
+        D3D12_CLEAR_VALUE clearValue = {};
+        clearValue.Format = dxgiFormat;
+        // have a separate allocation and resource object (meaning both will need its own Release())
+        ID3D12Resource *resource = nullptr;
+        D3D12MA::Allocation *allocation = nullptr;
+        HRESULT hr = rhiD->vma.createResource(D3D12_HEAP_TYPE_DEFAULT,
+                                              &resourceDesc,
+                                              D3D12_RESOURCE_STATE_RENDER_TARGET,
+                                              &clearValue,
+                                              &allocation,
+                                              __uuidof(ID3D12Resource),
+                                              reinterpret_cast<void **>(&resource));
+        if (FAILED(hr)) {
+            qWarning("Failed to create color buffer: %s", qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+        handle = QD3D12Resource::addToPool(&rhiD->resourcePool, resource, D3D12_RESOURCE_STATE_RENDER_TARGET, allocation);
+        rtv = rhiD->rtvPool.allocate(1);
+        if (!rtv.isValid())
+            return false;
+        D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
+        rtvDesc.Format = dxgiFormat;
+        rtvDesc.ViewDimension = sampleDesc.Count > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMS
+                                                     : D3D12_RTV_DIMENSION_TEXTURE2D;
+        rhiD->dev->CreateRenderTargetView(resource, &rtvDesc, rtv.cpuHandle);
+    }
+        break;
+    case QRhiRenderBuffer::DepthStencil:
+    {
+        dxgiFormat = DS_FORMAT;
+        sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, dxgiFormat);
+        D3D12_RESOURCE_DESC resourceDesc = {};
+        resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
+        resourceDesc.Width = UINT64(m_pixelSize.width());
+        resourceDesc.Height = UINT(m_pixelSize.height());
+        resourceDesc.DepthOrArraySize = 1;
+        resourceDesc.MipLevels = 1;
+        resourceDesc.Format = dxgiFormat;
+        resourceDesc.SampleDesc = sampleDesc;
+        resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
+        resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
+        if (m_flags.testFlag(UsedWithSwapChainOnly))
+            resourceDesc.Flags |= D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE;
+        D3D12_CLEAR_VALUE clearValue = {};
+        clearValue.Format = dxgiFormat;
+        clearValue.DepthStencil.Depth = 1.0f;
+        clearValue.DepthStencil.Stencil = 0;
+        ID3D12Resource *resource = nullptr;
+        D3D12MA::Allocation *allocation = nullptr;
+        HRESULT hr = rhiD->vma.createResource(D3D12_HEAP_TYPE_DEFAULT,
+                                              &resourceDesc,
+                                              D3D12_RESOURCE_STATE_DEPTH_WRITE,
+                                              &clearValue,
+                                              &allocation,
+                                              __uuidof(ID3D12Resource),
+                                              reinterpret_cast<void **>(&resource));
+        if (FAILED(hr)) {
+            qWarning("Failed to create depth-stencil buffer: %s", qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+        handle = QD3D12Resource::addToPool(&rhiD->resourcePool, resource, D3D12_RESOURCE_STATE_DEPTH_WRITE, allocation);
+        dsv = rhiD->dsvPool.allocate(1);
+        if (!dsv.isValid())
+            return false;
+        D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
+        dsvDesc.Format = dxgiFormat;
+        dsvDesc.ViewDimension = sampleDesc.Count > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMS
+                                                     : D3D12_DSV_DIMENSION_TEXTURE2D;
+        rhiD->dev->CreateDepthStencilView(resource, &dsvDesc, dsv.cpuHandle);
+    }
+        break;
+    }
+
+    if (!m_objectName.isEmpty()) {
+        if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handle)) {
+            const QString name = QString::fromUtf8(m_objectName);
+            res->resource->SetName(reinterpret_cast<LPCWSTR>(name.utf16()));
+        }
+    }
+
+    generation += 1;
+    rhiD->registerResource(this);
+    return true;
+}
+
+QRhiTexture::Format QD3D12RenderBuffer::backingFormat() const
+{
+    if (m_backingFormatHint != QRhiTexture::UnknownFormat)
+        return m_backingFormatHint;
+    else
+        return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
+}
+
+QD3D12Texture::QD3D12Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
+                             int arraySize, int sampleCount, Flags flags)
+    : QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags)
+{
+}
+
+QD3D12Texture::~QD3D12Texture()
+{
+    destroy();
+}
+
+void QD3D12Texture::destroy()
+{
+    if (handle.isNull())
+        return;
+
+    QRHI_RES_RHI(QRhiD3D12);
+    if (rhiD)
+        rhiD->releaseQueue.deferredReleaseResourceWithViews(handle, &rhiD->cbvSrvUavPool, srv, 1);
+
+    handle = {};
+    srv = {};
+
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+static inline DXGI_FORMAT toD3DDepthTextureSRVFormat(QRhiTexture::Format format)
+{
+    switch (format) {
+    case QRhiTexture::Format::D16:
+        return DXGI_FORMAT_R16_FLOAT;
+    case QRhiTexture::Format::D24:
+        return DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
+    case QRhiTexture::Format::D24S8:
+        return DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
+    case QRhiTexture::Format::D32F:
+        return DXGI_FORMAT_R32_FLOAT;
+    default:
+        break;
+    }
+    Q_UNREACHABLE_RETURN(DXGI_FORMAT_R32_FLOAT);
+}
+
+static inline DXGI_FORMAT toD3DDepthTextureDSVFormat(QRhiTexture::Format format)
+{
+    // here the result cannot be typeless
+    switch (format) {
+    case QRhiTexture::Format::D16:
+        return DXGI_FORMAT_D16_UNORM;
+    case QRhiTexture::Format::D24:
+        return DXGI_FORMAT_D24_UNORM_S8_UINT;
+    case QRhiTexture::Format::D24S8:
+        return DXGI_FORMAT_D24_UNORM_S8_UINT;
+    case QRhiTexture::Format::D32F:
+        return DXGI_FORMAT_D32_FLOAT;
+    default:
+        break;
+    }
+    Q_UNREACHABLE_RETURN(DXGI_FORMAT_D32_FLOAT);
+}
+
+static inline bool isDepthTextureFormat(QRhiTexture::Format format)
+{
+    switch (format) {
+    case QRhiTexture::Format::D16:
+    case QRhiTexture::Format::D24:
+    case QRhiTexture::Format::D24S8:
+    case QRhiTexture::Format::D32F:
+        return true;
+    default:
+        return false;
+    }
+}
+
+bool QD3D12Texture::prepareCreate(QSize *adjustedSize)
+{
+    if (!handle.isNull())
+        destroy();
+
+    const bool isDepth = isDepthTextureFormat(m_format);
+    const bool isCube = m_flags.testFlag(CubeMap);
+    const bool is3D = m_flags.testFlag(ThreeDimensional);
+    const bool isArray = m_flags.testFlag(TextureArray);
+    const bool hasMipMaps = m_flags.testFlag(MipMapped);
+    const bool is1D = m_flags.testFlag(OneDimensional);
+
+    const QSize size = is1D ? QSize(qMax(1, m_pixelSize.width()), 1)
+                            : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize);
+
+    QRHI_RES_RHI(QRhiD3D12);
+    dxgiFormat = toD3DTextureFormat(m_format, m_flags);
+    mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1);
+    sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, dxgiFormat);
+    if (sampleDesc.Count > 1) {
+        if (isCube) {
+            qWarning("Cubemap texture cannot be multisample");
+            return false;
+        }
+        if (is3D) {
+            qWarning("3D texture cannot be multisample");
+            return false;
+        }
+        if (hasMipMaps) {
+            qWarning("Multisample texture cannot have mipmaps");
+            return false;
+        }
+    }
+    if (isDepth && hasMipMaps) {
+        qWarning("Depth texture cannot have mipmaps");
+        return false;
+    }
+    if (isCube && is3D) {
+        qWarning("Texture cannot be both cube and 3D");
+        return false;
+    }
+    if (isArray && is3D) {
+        qWarning("Texture cannot be both array and 3D");
+        return false;
+    }
+    if (isCube && is1D) {
+        qWarning("Texture cannot be both cube and 1D");
+        return false;
+    }
+    if (is1D && is3D) {
+        qWarning("Texture cannot be both 1D and 3D");
+        return false;
+    }
+    if (m_depth > 1 && !is3D) {
+        qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth);
+        return false;
+    }
+    if (m_arraySize > 0 && !isArray) {
+        qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize);
+        return false;
+    }
+    if (m_arraySize < 1 && isArray) {
+        qWarning("Texture is an array but array size is %d", m_arraySize);
+        return false;
+    }
+
+    if (adjustedSize)
+        *adjustedSize = size;
+
+    return true;
+}
+
+bool QD3D12Texture::finishCreate()
+{
+    QRHI_RES_RHI(QRhiD3D12);
+    const bool isDepth = isDepthTextureFormat(m_format);
+    const bool isCube = m_flags.testFlag(CubeMap);
+    const bool is3D = m_flags.testFlag(ThreeDimensional);
+    const bool isArray = m_flags.testFlag(TextureArray);
+    const bool is1D = m_flags.testFlag(OneDimensional);
+
+    D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
+    srvDesc.Format = isDepth ? toD3DDepthTextureSRVFormat(m_format) : dxgiFormat;
+    srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
+
+    if (isCube) {
+        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
+        srvDesc.TextureCube.MipLevels = mipLevelCount;
+    } else {
+        if (is1D) {
+            if (isArray) {
+                srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY;
+                srvDesc.Texture1DArray.MipLevels = mipLevelCount;
+                if (m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) {
+                    srvDesc.Texture1DArray.FirstArraySlice = UINT(m_arrayRangeStart);
+                    srvDesc.Texture1DArray.ArraySize = UINT(m_arrayRangeLength);
+                } else {
+                    srvDesc.Texture1DArray.FirstArraySlice = 0;
+                    srvDesc.Texture1DArray.ArraySize = UINT(qMax(0, m_arraySize));
+                }
+            } else {
+                srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D;
+                srvDesc.Texture1D.MipLevels = mipLevelCount;
+            }
+        } else if (isArray) {
+            if (sampleDesc.Count > 1) {
+                srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY;
+                if (m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) {
+                    srvDesc.Texture2DMSArray.FirstArraySlice = UINT(m_arrayRangeStart);
+                    srvDesc.Texture2DMSArray.ArraySize = UINT(m_arrayRangeLength);
+                } else {
+                    srvDesc.Texture2DMSArray.FirstArraySlice = 0;
+                    srvDesc.Texture2DMSArray.ArraySize = UINT(qMax(0, m_arraySize));
+                }
+            } else {
+                srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
+                srvDesc.Texture2DArray.MipLevels = mipLevelCount;
+                if (m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) {
+                    srvDesc.Texture2DArray.FirstArraySlice = UINT(m_arrayRangeStart);
+                    srvDesc.Texture2DArray.ArraySize = UINT(m_arrayRangeLength);
+                } else {
+                    srvDesc.Texture2DArray.FirstArraySlice = 0;
+                    srvDesc.Texture2DArray.ArraySize = UINT(qMax(0, m_arraySize));
+                }
+            }
+        } else {
+            if (sampleDesc.Count > 1) {
+                srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS;
+            } else if (is3D) {
+                srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D;
+                srvDesc.Texture3D.MipLevels = mipLevelCount;
+            } else {
+                srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
+                srvDesc.Texture2D.MipLevels = mipLevelCount;
+            }
+        }
+    }
+
+    srv = rhiD->cbvSrvUavPool.allocate(1);
+    if (!srv.isValid())
+        return false;
+
+    if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handle)) {
+        rhiD->dev->CreateShaderResourceView(res->resource, &srvDesc, srv.cpuHandle);
+        if (!m_objectName.isEmpty()) {
+            const QString name = QString::fromUtf8(m_objectName);
+            res->resource->SetName(reinterpret_cast<LPCWSTR>(name.utf16()));
+        }
+    } else {
+        return false;
+    }
+
+    generation += 1;
+    return true;
+}
+
+bool QD3D12Texture::create()
+{
+    QSize size;
+    if (!prepareCreate(&size))
+        return false;
+
+    const bool isDepth = isDepthTextureFormat(m_format);
+    const bool isCube = m_flags.testFlag(CubeMap);
+    const bool is3D = m_flags.testFlag(ThreeDimensional);
+    const bool isArray = m_flags.testFlag(TextureArray);
+    const bool is1D = m_flags.testFlag(OneDimensional);
+
+    QRHI_RES_RHI(QRhiD3D12);
+
+    bool needsOptimizedClearValueSpecified = false;
+    UINT resourceFlags = 0;
+    if (m_flags.testFlag(RenderTarget) || sampleDesc.Count > 1) {
+        if (isDepth)
+            resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
+        else
+            resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
+        needsOptimizedClearValueSpecified = true;
+    }
+    if (m_flags.testFlag(UsedWithGenerateMips)) {
+        if (isDepth) {
+            qWarning("Depth texture cannot have mipmaps generated");
+            return false;
+        }
+        resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
+    }
+    if (m_flags.testFlag(UsedWithLoadStore))
+        resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
+
+    D3D12_RESOURCE_DESC resourceDesc = {};
+    resourceDesc.Dimension = is1D ? D3D12_RESOURCE_DIMENSION_TEXTURE1D
+                                  : (is3D ? D3D12_RESOURCE_DIMENSION_TEXTURE3D
+                                          : D3D12_RESOURCE_DIMENSION_TEXTURE2D);
+    resourceDesc.Width = UINT64(size.width());
+    resourceDesc.Height = UINT(size.height());
+    resourceDesc.DepthOrArraySize = isCube ? 6
+                                           : (isArray ? UINT(qMax(0, m_arraySize))
+                                                      : (is3D ? qMax(1, m_depth)
+                                                              : 1));
+    resourceDesc.MipLevels = mipLevelCount;
+    resourceDesc.Format = dxgiFormat;
+    resourceDesc.SampleDesc = sampleDesc;
+    resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
+    resourceDesc.Flags = D3D12_RESOURCE_FLAGS(resourceFlags);
+    D3D12_CLEAR_VALUE clearValue = {};
+    clearValue.Format = dxgiFormat;
+    if (isDepth) {
+        clearValue.Format = toD3DDepthTextureDSVFormat(m_format);
+        clearValue.DepthStencil.Depth = 1.0f;
+        clearValue.DepthStencil.Stencil = 0;
+    }
+    ID3D12Resource *resource = nullptr;
+    D3D12MA::Allocation *allocation = nullptr;
+    HRESULT hr = rhiD->vma.createResource(D3D12_HEAP_TYPE_DEFAULT,
+                                          &resourceDesc,
+                                          D3D12_RESOURCE_STATE_COMMON,
+                                          needsOptimizedClearValueSpecified ? &clearValue : nullptr,
+                                          &allocation,
+                                          __uuidof(ID3D12Resource),
+                                          reinterpret_cast<void **>(&resource));
+    if (FAILED(hr)) {
+        qWarning("Failed to create texture: '%s'"
+                 " Dim was %d Size was %ux%u Depth/ArraySize was %u MipLevels was %u Format was %d Sample count was %d",
+                 qPrintable(QSystemError::windowsComString(hr)),
+                 int(resourceDesc.Dimension),
+                 uint(resourceDesc.Width),
+                 uint(resourceDesc.Height),
+                 uint(resourceDesc.DepthOrArraySize),
+                 uint(resourceDesc.MipLevels),
+                 int(resourceDesc.Format),
+                 int(resourceDesc.SampleDesc.Count));
+        return false;
+    }
+
+    handle = QD3D12Resource::addToPool(&rhiD->resourcePool, resource, D3D12_RESOURCE_STATE_COMMON, allocation);
+
+    if (!finishCreate())
+        return false;
+
+    rhiD->registerResource(this);
+    return true;
+}
+
+bool QD3D12Texture::createFrom(QRhiTexture::NativeTexture src)
+{
+    if (!src.object)
+        return false;
+
+    if (!prepareCreate())
+        return false;
+
+    ID3D12Resource *resource = reinterpret_cast<ID3D12Resource *>(src.object);
+    D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATES(src.layout);
+
+    QRHI_RES_RHI(QRhiD3D12);
+    handle = QD3D12Resource::addNonOwningToPool(&rhiD->resourcePool, resource, state);
+
+    if (!finishCreate())
+        return false;
+
+    rhiD->registerResource(this);
+    return true;
+}
+
+QRhiTexture::NativeTexture QD3D12Texture::nativeTexture()
+{
+    QRHI_RES_RHI(QRhiD3D12);
+    if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handle))
+        return { quint64(res->resource), int(res->state) };
+
+    return {};
+}
+
+void QD3D12Texture::setNativeLayout(int layout)
+{
+    QRHI_RES_RHI(QRhiD3D12);
+    if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handle))
+        res->state = D3D12_RESOURCE_STATES(layout);
+}
+
+QD3D12Sampler::QD3D12Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
+                             AddressMode u, AddressMode v, AddressMode w)
+    : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w)
+{
+}
+
+QD3D12Sampler::~QD3D12Sampler()
+{
+    destroy();
+}
+
+void QD3D12Sampler::destroy()
+{
+    shaderVisibleDescriptor = {};
+
+    QRHI_RES_RHI(QRhiD3D12);
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+static inline D3D12_FILTER toD3DFilter(QRhiSampler::Filter minFilter, QRhiSampler::Filter magFilter, QRhiSampler::Filter mipFilter)
+{
+    if (minFilter == QRhiSampler::Nearest) {
+        if (magFilter == QRhiSampler::Nearest) {
+            if (mipFilter == QRhiSampler::Linear)
+                return D3D12_FILTER_MIN_MAG_POINT_MIP_LINEAR;
+            else
+                return D3D12_FILTER_MIN_MAG_MIP_POINT;
+        } else {
+            if (mipFilter == QRhiSampler::Linear)
+                return D3D12_FILTER_MIN_POINT_MAG_MIP_LINEAR;
+            else
+                return D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT;
+        }
+    } else {
+        if (magFilter == QRhiSampler::Nearest) {
+            if (mipFilter == QRhiSampler::Linear)
+                return D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR;
+            else
+                return D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT;
+        } else {
+            if (mipFilter == QRhiSampler::Linear)
+                return D3D12_FILTER_MIN_MAG_MIP_LINEAR;
+            else
+                return D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT;
+        }
+    }
+    Q_UNREACHABLE_RETURN(D3D12_FILTER_MIN_MAG_MIP_LINEAR);
+}
+
+static inline D3D12_TEXTURE_ADDRESS_MODE toD3DAddressMode(QRhiSampler::AddressMode m)
+{
+    switch (m) {
+    case QRhiSampler::Repeat:
+        return D3D12_TEXTURE_ADDRESS_MODE_WRAP;
+    case QRhiSampler::ClampToEdge:
+        return D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
+    case QRhiSampler::Mirror:
+        return D3D12_TEXTURE_ADDRESS_MODE_MIRROR;
+    }
+    Q_UNREACHABLE_RETURN(D3D12_TEXTURE_ADDRESS_MODE_CLAMP);
+}
+
+static inline D3D12_COMPARISON_FUNC toD3DTextureComparisonFunc(QRhiSampler::CompareOp op)
+{
+    switch (op) {
+    case QRhiSampler::Never:
+        return D3D12_COMPARISON_FUNC_NEVER;
+    case QRhiSampler::Less:
+        return D3D12_COMPARISON_FUNC_LESS;
+    case QRhiSampler::Equal:
+        return D3D12_COMPARISON_FUNC_EQUAL;
+    case QRhiSampler::LessOrEqual:
+        return D3D12_COMPARISON_FUNC_LESS_EQUAL;
+    case QRhiSampler::Greater:
+        return D3D12_COMPARISON_FUNC_GREATER;
+    case QRhiSampler::NotEqual:
+        return D3D12_COMPARISON_FUNC_NOT_EQUAL;
+    case QRhiSampler::GreaterOrEqual:
+        return D3D12_COMPARISON_FUNC_GREATER_EQUAL;
+    case QRhiSampler::Always:
+        return D3D12_COMPARISON_FUNC_ALWAYS;
+    }
+    Q_UNREACHABLE_RETURN(D3D12_COMPARISON_FUNC_NEVER);
+}
+
+bool QD3D12Sampler::create()
+{
+    desc = {};
+    desc.Filter = toD3DFilter(m_minFilter, m_magFilter, m_mipmapMode);
+    if (m_compareOp != Never)
+        desc.Filter = D3D12_FILTER(desc.Filter | 0x80);
+    desc.AddressU = toD3DAddressMode(m_addressU);
+    desc.AddressV = toD3DAddressMode(m_addressV);
+    desc.AddressW = toD3DAddressMode(m_addressW);
+    desc.MaxAnisotropy = 1.0f;
+    desc.ComparisonFunc = toD3DTextureComparisonFunc(m_compareOp);
+    desc.MaxLOD = m_mipmapMode == None ? 0.0f : 10000.0f;
+
+    QRHI_RES_RHI(QRhiD3D12);
+    rhiD->registerResource(this, false);
+    return true;
+}
+
+QD3D12Descriptor QD3D12Sampler::lookupOrCreateShaderVisibleDescriptor()
+{
+    if (!shaderVisibleDescriptor.isValid()) {
+        QRHI_RES_RHI(QRhiD3D12);
+        shaderVisibleDescriptor = rhiD->samplerMgr.getShaderVisibleDescriptor(desc);
+    }
+    return shaderVisibleDescriptor;
+}
+
+QD3D12TextureRenderTarget::QD3D12TextureRenderTarget(QRhiImplementation *rhi,
+                                                     const QRhiTextureRenderTargetDescription &desc,
+                                                     Flags flags)
+    : QRhiTextureRenderTarget(rhi, desc, flags),
+      d(rhi)
+{
+}
+
+QD3D12TextureRenderTarget::~QD3D12TextureRenderTarget()
+{
+    destroy();
+}
+
+void QD3D12TextureRenderTarget::destroy()
+{
+    if (!rtv[0].isValid() && !dsv.isValid())
+        return;
+
+    QRHI_RES_RHI(QRhiD3D12);
+    if (dsv.isValid()) {
+        if (ownsDsv && rhiD)
+            rhiD->releaseQueue.deferredReleaseViews(&rhiD->dsvPool, dsv, 1);
+        dsv = {};
+    }
+
+    for (int i = 0; i < QD3D12RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i) {
+        if (rtv[i].isValid()) {
+            if (ownsRtv[i] && rhiD)
+                rhiD->releaseQueue.deferredReleaseViews(&rhiD->rtvPool, rtv[i], 1);
+            rtv[i] = {};
+        }
+    }
+
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+QRhiRenderPassDescriptor *QD3D12TextureRenderTarget::newCompatibleRenderPassDescriptor()
+{
+    // not yet built so cannot rely on data computed in create()
+
+    QD3D12RenderPassDescriptor *rpD = new QD3D12RenderPassDescriptor(m_rhi);
+
+    rpD->colorAttachmentCount = 0;
+    for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it) {
+        QD3D12Texture *texD = QRHI_RES(QD3D12Texture, it->texture());
+        QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, it->renderBuffer());
+        if (texD)
+            rpD->colorFormat[rpD->colorAttachmentCount] = texD->dxgiFormat;
+        else if (rbD)
+            rpD->colorFormat[rpD->colorAttachmentCount] = rbD->dxgiFormat;
+        rpD->colorAttachmentCount += 1;
+    }
+
+    rpD->hasDepthStencil = false;
+    if (m_desc.depthStencilBuffer()) {
+        rpD->hasDepthStencil = true;
+        rpD->dsFormat = QD3D12RenderBuffer::DS_FORMAT;
+    } else if (m_desc.depthTexture()) {
+        QD3D12Texture *depthTexD = QRHI_RES(QD3D12Texture, m_desc.depthTexture());
+        rpD->hasDepthStencil = true;
+        rpD->dsFormat = toD3DDepthTextureDSVFormat(depthTexD->format()); // cannot be a typeless format
+    }
+
+    rpD->updateSerializedFormat();
+
+    QRHI_RES_RHI(QRhiD3D12);
+    rhiD->registerResource(rpD);
+    return rpD;
+}
+
+bool QD3D12TextureRenderTarget::create()
+{
+    if (rtv[0].isValid() || dsv.isValid())
+        destroy();
+
+    QRHI_RES_RHI(QRhiD3D12);
+    Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture());
+    Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
+    const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
+    d.colorAttCount = 0;
+    int attIndex = 0;
+
+    for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
+        d.colorAttCount += 1;
+        const QRhiColorAttachment &colorAtt(*it);
+        QRhiTexture *texture = colorAtt.texture();
+        QRhiRenderBuffer *rb = colorAtt.renderBuffer();
+        Q_ASSERT(texture || rb);
+        if (texture) {
+            QD3D12Texture *texD = QRHI_RES(QD3D12Texture, texture);
+            QD3D12Resource *res = rhiD->resourcePool.lookupRef(texD->handle);
+            if (!res) {
+                qWarning("Could not look up texture handle for render target");
+                return false;
+            }
+            const bool isMultiView = it->multiViewCount() >= 2;
+            UINT layerCount = isMultiView ? UINT(it->multiViewCount()) : 1;
+            D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
+            rtvDesc.Format = toD3DTextureFormat(texD->format(), texD->flags());
+            if (texD->flags().testFlag(QRhiTexture::CubeMap)) {
+                rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
+                rtvDesc.Texture2DArray.MipSlice = UINT(colorAtt.level());
+                rtvDesc.Texture2DArray.FirstArraySlice = UINT(colorAtt.layer());
+                rtvDesc.Texture2DArray.ArraySize = layerCount;
+            } else if (texD->flags().testFlag(QRhiTexture::OneDimensional)) {
+                if (texD->flags().testFlag(QRhiTexture::TextureArray)) {
+                    rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE1DARRAY;
+                    rtvDesc.Texture1DArray.MipSlice = UINT(colorAtt.level());
+                    rtvDesc.Texture1DArray.FirstArraySlice = UINT(colorAtt.layer());
+                    rtvDesc.Texture1DArray.ArraySize = layerCount;
+                } else {
+                    rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE1D;
+                    rtvDesc.Texture1D.MipSlice = UINT(colorAtt.level());
+                }
+            } else if (texD->flags().testFlag(QRhiTexture::TextureArray)) {
+                if (texD->sampleDesc.Count > 1) {
+                    rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY;
+                    rtvDesc.Texture2DMSArray.FirstArraySlice = UINT(colorAtt.layer());
+                    rtvDesc.Texture2DMSArray.ArraySize = layerCount;
+                } else {
+                    rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
+                    rtvDesc.Texture2DArray.MipSlice = UINT(colorAtt.level());
+                    rtvDesc.Texture2DArray.FirstArraySlice = UINT(colorAtt.layer());
+                    rtvDesc.Texture2DArray.ArraySize = layerCount;
+                }
+            } else if (texD->flags().testFlag(QRhiTexture::ThreeDimensional)) {
+                rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D;
+                rtvDesc.Texture3D.MipSlice = UINT(colorAtt.level());
+                rtvDesc.Texture3D.FirstWSlice = UINT(colorAtt.layer());
+                rtvDesc.Texture3D.WSize = layerCount;
+            } else {
+                if (texD->sampleDesc.Count > 1) {
+                    rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMS;
+                } else {
+                    rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
+                    rtvDesc.Texture2D.MipSlice = UINT(colorAtt.level());
+                }
+            }
+            rtv[attIndex] = rhiD->rtvPool.allocate(1);
+            if (!rtv[attIndex].isValid()) {
+                qWarning("Failed to allocate RTV for texture render target");
+                return false;
+            }
+            rhiD->dev->CreateRenderTargetView(res->resource, &rtvDesc, rtv[attIndex].cpuHandle);
+            ownsRtv[attIndex] = true;
+            if (attIndex == 0) {
+                d.pixelSize = rhiD->q->sizeForMipLevel(colorAtt.level(), texD->pixelSize());
+                d.sampleCount = int(texD->sampleDesc.Count);
+            }
+        } else if (rb) {
+            QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, rb);
+            ownsRtv[attIndex] = false;
+            rtv[attIndex] = rbD->rtv;
+            if (attIndex == 0) {
+                d.pixelSize = rbD->pixelSize();
+                d.sampleCount = int(rbD->sampleDesc.Count);
+            }
+        }
+    }
+
+    d.dpr = 1;
+
+    if (hasDepthStencil) {
+        if (m_desc.depthTexture()) {
+            ownsDsv = true;
+            QD3D12Texture *depthTexD = QRHI_RES(QD3D12Texture, m_desc.depthTexture());
+            QD3D12Resource *res = rhiD->resourcePool.lookupRef(depthTexD->handle);
+            if (!res) {
+                qWarning("Could not look up depth texture handle");
+                return false;
+            }
+            D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
+            dsvDesc.Format = toD3DDepthTextureDSVFormat(depthTexD->format());
+            dsvDesc.ViewDimension = depthTexD->sampleDesc.Count > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMS
+                                                                    : D3D12_DSV_DIMENSION_TEXTURE2D;
+            if (depthTexD->flags().testFlag(QRhiTexture::TextureArray)) {
+                if (depthTexD->sampleDesc.Count > 1) {
+                    dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY;
+                    if (depthTexD->arrayRangeStart() >= 0 && depthTexD->arrayRangeLength() >= 0) {
+                        dsvDesc.Texture2DMSArray.FirstArraySlice = UINT(depthTexD->arrayRangeStart());
+                        dsvDesc.Texture2DMSArray.ArraySize = UINT(depthTexD->arrayRangeLength());
+                    } else {
+                        dsvDesc.Texture2DMSArray.FirstArraySlice = 0;
+                        dsvDesc.Texture2DMSArray.ArraySize = UINT(qMax(0, depthTexD->arraySize()));
+                    }
+                } else {
+                    dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY;
+                    if (depthTexD->arrayRangeStart() >= 0 && depthTexD->arrayRangeLength() >= 0) {
+                        dsvDesc.Texture2DArray.FirstArraySlice = UINT(depthTexD->arrayRangeStart());
+                        dsvDesc.Texture2DArray.ArraySize = UINT(depthTexD->arrayRangeLength());
+                    } else {
+                        dsvDesc.Texture2DArray.FirstArraySlice = 0;
+                        dsvDesc.Texture2DArray.ArraySize = UINT(qMax(0, depthTexD->arraySize()));
+                    }
+                }
+            }
+            dsv = rhiD->dsvPool.allocate(1);
+            if (!dsv.isValid()) {
+                qWarning("Failed to allocate DSV for texture render target");
+                return false;
+            }
+            rhiD->dev->CreateDepthStencilView(res->resource, &dsvDesc, dsv.cpuHandle);
+            if (d.colorAttCount == 0) {
+                d.pixelSize = depthTexD->pixelSize();
+                d.sampleCount = int(depthTexD->sampleDesc.Count);
+            }
+        } else {
+            ownsDsv = false;
+            QD3D12RenderBuffer *depthRbD = QRHI_RES(QD3D12RenderBuffer, m_desc.depthStencilBuffer());
+            dsv = depthRbD->dsv;
+            if (d.colorAttCount == 0) {
+                d.pixelSize = m_desc.depthStencilBuffer()->pixelSize();
+                d.sampleCount = int(depthRbD->sampleDesc.Count);
+            }
+        }
+        d.dsAttCount = 1;
+    } else {
+        d.dsAttCount = 0;
+    }
+
+    D3D12_CPU_DESCRIPTOR_HANDLE nullDescHandle = { 0 };
+    for (int i = 0; i < QD3D12RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i)
+        d.rtv[i] = i < d.colorAttCount ? rtv[i].cpuHandle : nullDescHandle;
+    d.dsv = dsv.cpuHandle;
+    d.rp = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc);
+
+    QRhiRenderTargetAttachmentTracker::updateResIdList<QD3D12Texture, QD3D12RenderBuffer>(m_desc, &d.currentResIdList);
+
+    rhiD->registerResource(this);
+    return true;
+}
+
+QSize QD3D12TextureRenderTarget::pixelSize() const
+{
+    if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QD3D12Texture, QD3D12RenderBuffer>(m_desc, d.currentResIdList))
+        const_cast<QD3D12TextureRenderTarget *>(this)->create();
+
+    return d.pixelSize;
+}
+
+float QD3D12TextureRenderTarget::devicePixelRatio() const
+{
+    return d.dpr;
+}
+
+int QD3D12TextureRenderTarget::sampleCount() const
+{
+    return d.sampleCount;
+}
+
+QD3D12ShaderResourceBindings::QD3D12ShaderResourceBindings(QRhiImplementation *rhi)
+    : QRhiShaderResourceBindings(rhi)
+{
+}
+
+QD3D12ShaderResourceBindings::~QD3D12ShaderResourceBindings()
+{
+    destroy();
+}
+
+void QD3D12ShaderResourceBindings::destroy()
+{
+    QRHI_RES_RHI(QRhiD3D12);
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+bool QD3D12ShaderResourceBindings::create()
+{
+    QRHI_RES_RHI(QRhiD3D12);
+    if (!rhiD->sanityCheckShaderResourceBindings(this))
+        return false;
+
+    rhiD->updateLayoutDesc(this);
+
+    hasDynamicOffset = false;
+    for (const QRhiShaderResourceBinding &b : std::as_const(m_bindings)) {
+        const QRhiShaderResourceBinding::Data *bd = QRhiImplementation::shaderResourceBindingData(b);
+        if (bd->type == QRhiShaderResourceBinding::UniformBuffer && bd->u.ubuf.hasDynamicOffset) {
+            hasDynamicOffset = true;
+            break;
+        }
+    }
+
+    // The root signature is not part of the srb. Unintuitive, but the shader
+    // translation pipeline ties our hands: as long as the per-shader (so per
+    // stage!) nativeResourceBindingMap exist, meaning f.ex. that a SPIR-V
+    // combined image sampler binding X passed in here may map to the tY and sY
+    // HLSL registers, where Y is known only once the mapping table from the
+    // shader is looked up. Creating a root parameters at this stage is
+    // therefore impossible.
+
+    generation += 1;
+    rhiD->registerResource(this, false);
+    return true;
+}
+
+void QD3D12ShaderResourceBindings::updateResources(UpdateFlags flags)
+{
+    Q_UNUSED(flags);
+    generation += 1;
+}
+
+// Accessing the QRhiBuffer/Texture/Sampler resources must be avoided in the
+// callbacks; that would only be possible if the srb had those specified, and
+// that's not required at the time of srb and pipeline create() time, and
+// createRootSignature is called from the pipeline create().
+
+void QD3D12ShaderResourceBindings::visitUniformBuffer(QD3D12Stage s,
+                                                      const QRhiShaderResourceBinding::Data::UniformBufferData &,
+                                                      int shaderRegister,
+                                                      int)
+{
+    D3D12_ROOT_PARAMETER1 rootParam = {};
+    rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
+    rootParam.ShaderVisibility = qd3d12_stageToVisibility(s);
+    rootParam.Descriptor.ShaderRegister = shaderRegister;
+    rootParam.Descriptor.Flags = D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC;
+    visitorData.cbParams[s].append(rootParam);
+}
+
+void QD3D12ShaderResourceBindings::visitTexture(QD3D12Stage s,
+                                                const QRhiShaderResourceBinding::TextureAndSampler &,
+                                                int shaderRegister)
+{
+    D3D12_DESCRIPTOR_RANGE1 range = {};
+    range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
+    range.NumDescriptors = 1;
+    range.BaseShaderRegister = shaderRegister;
+    range.OffsetInDescriptorsFromTableStart = visitorData.currentSrvRangeOffset[s];
+    visitorData.currentSrvRangeOffset[s] += 1;
+    visitorData.srvRanges[s].append(range);
+    if (visitorData.srvRanges[s].count() == 1) {
+        visitorData.srvTables[s].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
+        visitorData.srvTables[s].ShaderVisibility = qd3d12_stageToVisibility(s);
+    }
+}
+
+void QD3D12ShaderResourceBindings::visitSampler(QD3D12Stage s,
+                                                const QRhiShaderResourceBinding::TextureAndSampler &,
+                                                int shaderRegister)
+{
+    // Unlike SRVs and UAVs, samplers are handled so that each sampler becomes
+    // a root parameter with its own descriptor table.
+
+    int &rangeStoreIdx(visitorData.samplerRangeHeads[s]);
+    if (rangeStoreIdx == 16) {
+        qWarning("Sampler count in QD3D12Stage %d exceeds the limit of 16, this is disallowed by QRhi", s);
+        return;
+    }
+    D3D12_DESCRIPTOR_RANGE1 range = {};
+    range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
+    range.NumDescriptors = 1;
+    range.BaseShaderRegister = shaderRegister;
+    visitorData.samplerRanges[s][rangeStoreIdx] = range;
+    D3D12_ROOT_PARAMETER1 param = {};
+    param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
+    param.ShaderVisibility = qd3d12_stageToVisibility(s);
+    param.DescriptorTable.NumDescriptorRanges = 1;
+    param.DescriptorTable.pDescriptorRanges = &visitorData.samplerRanges[s][rangeStoreIdx];
+    rangeStoreIdx += 1;
+    visitorData.samplerTables[s].append(param);
+}
+
+void QD3D12ShaderResourceBindings::visitStorageBuffer(QD3D12Stage s,
+                                                      const QRhiShaderResourceBinding::Data::StorageBufferData &,
+                                                      QD3D12ShaderResourceVisitor::StorageOp,
+                                                      int shaderRegister)
+{
+    D3D12_DESCRIPTOR_RANGE1 range = {};
+    range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
+    range.NumDescriptors = 1;
+    range.BaseShaderRegister = shaderRegister;
+    range.OffsetInDescriptorsFromTableStart = visitorData.currentUavRangeOffset[s];
+    visitorData.currentUavRangeOffset[s] += 1;
+    visitorData.uavRanges[s].append(range);
+    if (visitorData.uavRanges[s].count() == 1) {
+        visitorData.uavTables[s].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
+        visitorData.uavTables[s].ShaderVisibility = qd3d12_stageToVisibility(s);
+    }
+}
+
+void QD3D12ShaderResourceBindings::visitStorageImage(QD3D12Stage s,
+                                                     const QRhiShaderResourceBinding::Data::StorageImageData &,
+                                                     QD3D12ShaderResourceVisitor::StorageOp,
+                                                     int shaderRegister)
+{
+    D3D12_DESCRIPTOR_RANGE1 range = {};
+    range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
+    range.NumDescriptors = 1;
+    range.BaseShaderRegister = shaderRegister;
+    range.OffsetInDescriptorsFromTableStart = visitorData.currentUavRangeOffset[s];
+    visitorData.currentUavRangeOffset[s] += 1;
+    visitorData.uavRanges[s].append(range);
+    if (visitorData.uavRanges[s].count() == 1) {
+        visitorData.uavTables[s].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
+        visitorData.uavTables[s].ShaderVisibility = qd3d12_stageToVisibility(s);
+    }
+}
+
+QD3D12ObjectHandle QD3D12ShaderResourceBindings::createRootSignature(const QD3D12ShaderStageData *stageData,
+                                                                     int stageCount)
+{
+    QRHI_RES_RHI(QRhiD3D12);
+
+    // It's not just that the root signature has to be tied to the pipeline
+    // (cannot just freely create it like e.g. with Vulkan where one just
+    // creates a descriptor layout 1:1 with the QRhiShaderResourceBindings'
+    // data), due to not knowing the shader-specific resource binding mapping
+    // tables at the point of srb creation, but each shader stage may have a
+    // different mapping table. (ugh!)
+    //
+    // Hence we set up everything per-stage, even if it means the root
+    // signature gets unnecessarily big. (note that the magic is in the
+    // ShaderVisibility: even though the register range is the same in the
+    // descriptor tables, the visibility is different)
+
+    QD3D12ShaderResourceVisitor visitor(this, stageData, stageCount);
+
+    visitorData = {};
+
+    using namespace std::placeholders;
+    visitor.uniformBuffer = std::bind(&QD3D12ShaderResourceBindings::visitUniformBuffer, this, _1, _2, _3, _4);
+    visitor.texture = std::bind(&QD3D12ShaderResourceBindings::visitTexture, this, _1, _2, _3);
+    visitor.sampler = std::bind(&QD3D12ShaderResourceBindings::visitSampler, this, _1, _2, _3);
+    visitor.storageBuffer = std::bind(&QD3D12ShaderResourceBindings::visitStorageBuffer, this, _1, _2, _3, _4);
+    visitor.storageImage = std::bind(&QD3D12ShaderResourceBindings::visitStorageImage, this, _1, _2, _3, _4);
+
+    visitor.visit();
+
+    // The maximum size of a root signature is 256 bytes, where a descriptor
+    // table is 4, a root descriptor (e.g. CBV) is 8. We have 5 stages at most
+    // (or 1 with compute) and a separate descriptor table for SRVs (->
+    // textures) and UAVs (-> storage buffers and images) per stage, plus each
+    // uniform buffer counts as a CBV in the stages it is visible.
+    //
+    // Due to the limited maximum size of a shader-visible sampler heap (2048)
+    // and the potential costly switching of descriptor heaps, each sampler is
+    // declared as a separate root parameter / descriptor table (meaning that
+    // two samplers in the same stage are two parameters and two tables, not
+    // just one). QRhi documents a hard limit of 16 on texture/sampler bindings
+    // in a shader (matching D3D11), so we can hopefully get away with this.
+    //
+    // This means that e.g. a vertex+fragment shader with a uniform buffer
+    // visible in both and one texture+sampler in the fragment shader would
+    // consume 2*8 + 4 + 4 = 24 bytes. This also implies that clients
+    // specifying the minimal stage bit mask for each entry in
+    // QRhiShaderResourceBindings are ideal for this backend since it helps
+    // reducing the chance of hitting the size limit.
+
+    QVarLengthArray<D3D12_ROOT_PARAMETER1, 4> rootParams;
+    for (int s = 0; s < 6; ++s) {
+        if (!visitorData.cbParams[s].isEmpty())
+            rootParams.append(visitorData.cbParams[s].constData(), visitorData.cbParams[s].count());
+    }
+    for (int s = 0; s < 6; ++s) {
+        if (!visitorData.srvRanges[s].isEmpty()) {
+            visitorData.srvTables[s].DescriptorTable.NumDescriptorRanges = visitorData.srvRanges[s].count();
+            visitorData.srvTables[s].DescriptorTable.pDescriptorRanges = visitorData.srvRanges[s].constData();
+            rootParams.append(visitorData.srvTables[s]);
+        }
+    }
+    for (int s = 0; s < 6; ++s) {
+        if (!visitorData.samplerTables[s].isEmpty())
+            rootParams.append(visitorData.samplerTables[s].constData(), visitorData.samplerTables[s].count());
+    }
+    for (int s = 0; s < 6; ++s) {
+        if (!visitorData.uavRanges[s].isEmpty()) {
+            visitorData.uavTables[s].DescriptorTable.NumDescriptorRanges = visitorData.uavRanges[s].count();
+            visitorData.uavTables[s].DescriptorTable.pDescriptorRanges = visitorData.uavRanges[s].constData();
+            rootParams.append(visitorData.uavTables[s]);
+        }
+    }
+
+    D3D12_VERSIONED_ROOT_SIGNATURE_DESC rsDesc = {};
+    rsDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1;
+    if (!rootParams.isEmpty()) {
+        rsDesc.Desc_1_1.NumParameters = rootParams.count();
+        rsDesc.Desc_1_1.pParameters = rootParams.constData();
+    }
+
+    UINT rsFlags = 0;
+    for (int stageIdx = 0; stageIdx < stageCount; ++stageIdx) {
+        if (stageData[stageIdx].valid && stageData[stageIdx].stage == VS)
+            rsFlags |= D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
+    }
+    rsDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAGS(rsFlags);
+
+    ID3DBlob *signature = nullptr;
+    HRESULT hr = D3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
+    if (FAILED(hr)) {
+        qWarning("Failed to serialize root signature: %s", qPrintable(QSystemError::windowsComString(hr)));
+        return {};
+    }
+    ID3D12RootSignature *rootSig = nullptr;
+    hr = rhiD->dev->CreateRootSignature(0,
+                                        signature->GetBufferPointer(),
+                                        signature->GetBufferSize(),
+                                        __uuidof(ID3D12RootSignature),
+                                        reinterpret_cast<void **>(&rootSig));
+    signature->Release();
+    if (FAILED(hr)) {
+        qWarning("Failed to create root signature: %s", qPrintable(QSystemError::windowsComString(hr)));
+        return {};
+    }
+
+    return QD3D12RootSignature::addToPool(&rhiD->rootSignaturePool, rootSig);
+}
+
+// For shader model < 6.0 we do the same as the D3D11 backend: use the old
+// compiler (D3DCompile) to generate DXBC, just as qsb does (when -c is passed)
+// by invoking fxc, not dxc. For SM >= 6.0 we have to use the new compiler and
+// work with DXIL. And that involves IDxcCompiler and needs the presence of
+// dxcompiler.dll and dxil.dll at runtime. Plus there's a chance we have
+// ancient SDK headers when not using MSVC. So this is heavily optional,
+// meaning support for dxc can be disabled both at build time (no dxcapi.h) and
+// at run time (no DLLs).
+
+static inline void makeHlslTargetString(char target[7], const char stage[3], int version)
+{
+    const int smMajor = version / 10;
+    const int smMinor = version % 10;
+    target[0] = stage[0];
+    target[1] = stage[1];
+    target[2] = '_';
+    target[3] = '0' + smMajor;
+    target[4] = '_';
+    target[5] = '0' + smMinor;
+    target[6] = '\0';
+}
+
+enum class HlslCompileFlag
+{
+    WithDebugInfo = 0x01
+};
+
+static QByteArray legacyCompile(const QShaderCode &hlslSource, const char *target, int flags, QString *error)
+{
+    static const pD3DCompile d3dCompile = QRhiD3D::resolveD3DCompile();
+    if (!d3dCompile) {
+        qWarning("Unable to resolve function D3DCompile()");
+        return QByteArray();
+    }
+
+    ID3DBlob *bytecode = nullptr;
+    ID3DBlob *errors = nullptr;
+    UINT d3dCompileFlags = 0;
+    if (flags & int(HlslCompileFlag::WithDebugInfo))
+        d3dCompileFlags |= D3DCOMPILE_DEBUG;
+
+    HRESULT hr = d3dCompile(hlslSource.shader().constData(), SIZE_T(hlslSource.shader().size()),
+                            nullptr, nullptr, nullptr,
+                            hlslSource.entryPoint().constData(), target, d3dCompileFlags, 0, &bytecode, &errors);
+    if (FAILED(hr) || !bytecode) {
+        qWarning("HLSL shader compilation failed: 0x%x", uint(hr));
+        if (errors) {
+            *error = QString::fromUtf8(static_cast<const char *>(errors->GetBufferPointer()),
+                                       int(errors->GetBufferSize()));
+            errors->Release();
+        }
+        return QByteArray();
+    }
+
+    QByteArray result;
+    result.resize(int(bytecode->GetBufferSize()));
+    memcpy(result.data(), bytecode->GetBufferPointer(), size_t(result.size()));
+    bytecode->Release();
+    return result;
+}
+
+#ifdef QRHI_D3D12_HAS_DXC
+
+#ifndef DXC_CP_UTF8
+#define DXC_CP_UTF8 65001
+#endif
+
+#ifndef DXC_ARG_DEBUG
+#define DXC_ARG_DEBUG L"-Zi"
+#endif
+
+static QByteArray dxcCompile(const QShaderCode &hlslSource, const char *target, int flags, QString *error)
+{
+    static std::pair<IDxcCompiler *, IDxcLibrary *> dxc = QRhiD3D::createDxcCompiler();
+    IDxcCompiler *compiler = dxc.first;
+    if (!compiler) {
+        qWarning("Unable to instantiate IDxcCompiler. Likely no dxcompiler.dll and dxil.dll present. "
+                 "Use windeployqt or try https://github.com/microsoft/DirectXShaderCompiler/releases");
+        return QByteArray();
+    }
+    IDxcLibrary *library = dxc.second;
+    if (!library)
+        return QByteArray();
+
+    IDxcBlobEncoding *sourceBlob = nullptr;
+    HRESULT hr = library->CreateBlobWithEncodingOnHeapCopy(hlslSource.shader().constData(),
+                                                           UINT32(hlslSource.shader().size()),
+                                                           DXC_CP_UTF8,
+                                                           &sourceBlob);
+    if (FAILED(hr)) {
+        qWarning("Failed to create source blob for dxc: 0x%x (%s)",
+                 uint(hr),
+                 qPrintable(QSystemError::windowsComString(hr)));
+        return QByteArray();
+    }
+
+    const QString entryPointStr = QString::fromLatin1(hlslSource.entryPoint());
+    const QString targetStr = QString::fromLatin1(target);
+
+    QVarLengthArray<LPCWSTR, 4> argPtrs;
+    QString debugArg;
+    if (flags & int(HlslCompileFlag::WithDebugInfo)) {
+        debugArg = QString::fromUtf16(reinterpret_cast<const char16_t *>(DXC_ARG_DEBUG));
+        argPtrs.append(reinterpret_cast<LPCWSTR>(debugArg.utf16()));
+    }
+
+    IDxcOperationResult *result = nullptr;
+    hr = compiler->Compile(sourceBlob,
+                           nullptr,
+                           reinterpret_cast<LPCWSTR>(entryPointStr.utf16()),
+                           reinterpret_cast<LPCWSTR>(targetStr.utf16()),
+                           argPtrs.data(), argPtrs.count(),
+                           nullptr, 0,
+                           nullptr,
+                           &result);
+    sourceBlob->Release();
+    if (SUCCEEDED(hr))
+        result->GetStatus(&hr);
+    if (FAILED(hr)) {
+        qWarning("HLSL shader compilation failed: 0x%x (%s)",
+                 uint(hr),
+                 qPrintable(QSystemError::windowsComString(hr)));
+        if (result) {
+            IDxcBlobEncoding *errorsBlob = nullptr;
+            if (SUCCEEDED(result->GetErrorBuffer(&errorsBlob))) {
+                if (errorsBlob) {
+                    *error = QString::fromUtf8(static_cast<const char *>(errorsBlob->GetBufferPointer()),
+                                               int(errorsBlob->GetBufferSize()));
+                    errorsBlob->Release();
+                }
+            }
+        }
+        return QByteArray();
+    }
+
+    IDxcBlob *bytecode = nullptr;
+    if FAILED(result->GetResult(&bytecode)) {
+        qWarning("No result from IDxcCompiler: 0x%x (%s)",
+                 uint(hr),
+                 qPrintable(QSystemError::windowsComString(hr)));
+        return QByteArray();
+    }
+
+    QByteArray ba;
+    ba.resize(int(bytecode->GetBufferSize()));
+    memcpy(ba.data(), bytecode->GetBufferPointer(), size_t(ba.size()));
+    bytecode->Release();
+    return ba;
+}
+
+#endif // QRHI_D3D12_HAS_DXC
+
+static QByteArray compileHlslShaderSource(const QShader &shader,
+                                          QShader::Variant shaderVariant,
+                                          int flags,
+                                          QString *error,
+                                          QShaderKey *usedShaderKey)
+{
+    // look for SM 6.7, 6.6, .., 5.0
+    const int shaderModelMax = 67;
+    for (int sm = shaderModelMax; sm >= 50; --sm) {
+        for (QShader::Source type : { QShader::DxilShader, QShader::DxbcShader }) {
+            QShaderKey key = { type, sm, shaderVariant };
+            QShaderCode intermediateBytecodeShader = shader.shader(key);
+            if (!intermediateBytecodeShader.shader().isEmpty()) {
+                if (usedShaderKey)
+                    *usedShaderKey = key;
+                return intermediateBytecodeShader.shader();
+            }
+        }
+    }
+
+    QShaderCode hlslSource;
+    QShaderKey key;
+    for (int sm = shaderModelMax; sm >= 50; --sm) {
+        key = { QShader::HlslShader, sm, shaderVariant };
+        hlslSource = shader.shader(key);
+        if (!hlslSource.shader().isEmpty())
+            break;
+    }
+
+    if (hlslSource.shader().isEmpty()) {
+        qWarning() << "No HLSL (shader model 6.7..5.0) code found in baked shader" << shader;
+        return QByteArray();
+    }
+
+    if (usedShaderKey)
+        *usedShaderKey = key;
+
+    char target[7];
+    switch (shader.stage()) {
+    case QShader::VertexStage:
+        makeHlslTargetString(target, "vs", key.sourceVersion().version());
+        break;
+    case QShader::TessellationControlStage:
+        makeHlslTargetString(target, "hs", key.sourceVersion().version());
+        break;
+    case QShader::TessellationEvaluationStage:
+        makeHlslTargetString(target, "ds", key.sourceVersion().version());
+        break;
+    case QShader::GeometryStage:
+        makeHlslTargetString(target, "gs", key.sourceVersion().version());
+        break;
+    case QShader::FragmentStage:
+        makeHlslTargetString(target, "ps", key.sourceVersion().version());
+        break;
+    case QShader::ComputeStage:
+        makeHlslTargetString(target, "cs", key.sourceVersion().version());
+        break;
+    }
+
+    if (key.sourceVersion().version() >= 60) {
+#ifdef QRHI_D3D12_HAS_DXC
+        return dxcCompile(hlslSource, target, flags, error);
+#else
+        qWarning("Attempted to runtime-compile HLSL source code for shader model >= 6.0 "
+                 "but the Qt build has no support for DXC. "
+                 "Rebuild Qt with a recent Windows SDK or switch to an MSVC build.");
+#endif
+    }
+
+    return legacyCompile(hlslSource, target, flags, error);
+}
+
+static inline UINT8 toD3DColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
+{
+    UINT8 f = 0;
+    if (c.testFlag(QRhiGraphicsPipeline::R))
+        f |= D3D12_COLOR_WRITE_ENABLE_RED;
+    if (c.testFlag(QRhiGraphicsPipeline::G))
+        f |= D3D12_COLOR_WRITE_ENABLE_GREEN;
+    if (c.testFlag(QRhiGraphicsPipeline::B))
+        f |= D3D12_COLOR_WRITE_ENABLE_BLUE;
+    if (c.testFlag(QRhiGraphicsPipeline::A))
+        f |= D3D12_COLOR_WRITE_ENABLE_ALPHA;
+    return f;
+}
+
+static inline D3D12_BLEND toD3DBlendFactor(QRhiGraphicsPipeline::BlendFactor f, bool rgb)
+{
+    // SrcBlendAlpha and DstBlendAlpha do not accept *_COLOR. With other APIs
+    // this is handled internally (so that e.g. VK_BLEND_FACTOR_SRC_COLOR is
+    // accepted and is in effect equivalent to VK_BLEND_FACTOR_SRC_ALPHA when
+    // set as an alpha src/dest factor), but for D3D we have to take care of it
+    // ourselves. Hence the rgb argument.
+
+    switch (f) {
+    case QRhiGraphicsPipeline::Zero:
+        return D3D12_BLEND_ZERO;
+    case QRhiGraphicsPipeline::One:
+        return D3D12_BLEND_ONE;
+    case QRhiGraphicsPipeline::SrcColor:
+        return rgb ? D3D12_BLEND_SRC_COLOR : D3D12_BLEND_SRC_ALPHA;
+    case QRhiGraphicsPipeline::OneMinusSrcColor:
+        return rgb ? D3D12_BLEND_INV_SRC_COLOR : D3D12_BLEND_INV_SRC_ALPHA;
+    case QRhiGraphicsPipeline::DstColor:
+        return rgb ? D3D12_BLEND_DEST_COLOR : D3D12_BLEND_DEST_ALPHA;
+    case QRhiGraphicsPipeline::OneMinusDstColor:
+        return rgb ? D3D12_BLEND_INV_DEST_COLOR : D3D12_BLEND_INV_DEST_ALPHA;
+    case QRhiGraphicsPipeline::SrcAlpha:
+        return D3D12_BLEND_SRC_ALPHA;
+    case QRhiGraphicsPipeline::OneMinusSrcAlpha:
+        return D3D12_BLEND_INV_SRC_ALPHA;
+    case QRhiGraphicsPipeline::DstAlpha:
+        return D3D12_BLEND_DEST_ALPHA;
+    case QRhiGraphicsPipeline::OneMinusDstAlpha:
+        return D3D12_BLEND_INV_DEST_ALPHA;
+    case QRhiGraphicsPipeline::ConstantColor:
+    case QRhiGraphicsPipeline::ConstantAlpha:
+        return D3D12_BLEND_BLEND_FACTOR;
+    case QRhiGraphicsPipeline::OneMinusConstantColor:
+    case QRhiGraphicsPipeline::OneMinusConstantAlpha:
+        return D3D12_BLEND_INV_BLEND_FACTOR;
+    case QRhiGraphicsPipeline::SrcAlphaSaturate:
+        return D3D12_BLEND_SRC_ALPHA_SAT;
+    case QRhiGraphicsPipeline::Src1Color:
+        return rgb ? D3D12_BLEND_SRC1_COLOR : D3D12_BLEND_SRC1_ALPHA;
+    case QRhiGraphicsPipeline::OneMinusSrc1Color:
+        return rgb ? D3D12_BLEND_INV_SRC1_COLOR : D3D12_BLEND_INV_SRC1_ALPHA;
+    case QRhiGraphicsPipeline::Src1Alpha:
+        return D3D12_BLEND_SRC1_ALPHA;
+    case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
+        return D3D12_BLEND_INV_SRC1_ALPHA;
+    }
+    Q_UNREACHABLE_RETURN(D3D12_BLEND_ZERO);
+}
+
+static inline D3D12_BLEND_OP toD3DBlendOp(QRhiGraphicsPipeline::BlendOp op)
+{
+    switch (op) {
+    case QRhiGraphicsPipeline::Add:
+        return D3D12_BLEND_OP_ADD;
+    case QRhiGraphicsPipeline::Subtract:
+        return D3D12_BLEND_OP_SUBTRACT;
+    case QRhiGraphicsPipeline::ReverseSubtract:
+        return D3D12_BLEND_OP_REV_SUBTRACT;
+    case QRhiGraphicsPipeline::Min:
+        return D3D12_BLEND_OP_MIN;
+    case QRhiGraphicsPipeline::Max:
+        return D3D12_BLEND_OP_MAX;
+    }
+    Q_UNREACHABLE_RETURN(D3D12_BLEND_OP_ADD);
+}
+
+static inline D3D12_CULL_MODE toD3DCullMode(QRhiGraphicsPipeline::CullMode c)
+{
+    switch (c) {
+    case QRhiGraphicsPipeline::None:
+        return D3D12_CULL_MODE_NONE;
+    case QRhiGraphicsPipeline::Front:
+        return D3D12_CULL_MODE_FRONT;
+    case QRhiGraphicsPipeline::Back:
+        return D3D12_CULL_MODE_BACK;
+    }
+    Q_UNREACHABLE_RETURN(D3D12_CULL_MODE_NONE);
+}
+
+static inline D3D12_FILL_MODE toD3DFillMode(QRhiGraphicsPipeline::PolygonMode mode)
+{
+    switch (mode) {
+    case QRhiGraphicsPipeline::Fill:
+        return D3D12_FILL_MODE_SOLID;
+    case QRhiGraphicsPipeline::Line:
+        return D3D12_FILL_MODE_WIREFRAME;
+    }
+    Q_UNREACHABLE_RETURN(D3D12_FILL_MODE_SOLID);
+}
+
+static inline D3D12_COMPARISON_FUNC toD3DCompareOp(QRhiGraphicsPipeline::CompareOp op)
+{
+    switch (op) {
+    case QRhiGraphicsPipeline::Never:
+        return D3D12_COMPARISON_FUNC_NEVER;
+    case QRhiGraphicsPipeline::Less:
+        return D3D12_COMPARISON_FUNC_LESS;
+    case QRhiGraphicsPipeline::Equal:
+        return D3D12_COMPARISON_FUNC_EQUAL;
+    case QRhiGraphicsPipeline::LessOrEqual:
+        return D3D12_COMPARISON_FUNC_LESS_EQUAL;
+    case QRhiGraphicsPipeline::Greater:
+        return D3D12_COMPARISON_FUNC_GREATER;
+    case QRhiGraphicsPipeline::NotEqual:
+        return D3D12_COMPARISON_FUNC_NOT_EQUAL;
+    case QRhiGraphicsPipeline::GreaterOrEqual:
+        return D3D12_COMPARISON_FUNC_GREATER_EQUAL;
+    case QRhiGraphicsPipeline::Always:
+        return D3D12_COMPARISON_FUNC_ALWAYS;
+    }
+    Q_UNREACHABLE_RETURN(D3D12_COMPARISON_FUNC_ALWAYS);
+}
+
+static inline D3D12_STENCIL_OP toD3DStencilOp(QRhiGraphicsPipeline::StencilOp op)
+{
+    switch (op) {
+    case QRhiGraphicsPipeline::StencilZero:
+        return D3D12_STENCIL_OP_ZERO;
+    case QRhiGraphicsPipeline::Keep:
+        return D3D12_STENCIL_OP_KEEP;
+    case QRhiGraphicsPipeline::Replace:
+        return D3D12_STENCIL_OP_REPLACE;
+    case QRhiGraphicsPipeline::IncrementAndClamp:
+        return D3D12_STENCIL_OP_INCR_SAT;
+    case QRhiGraphicsPipeline::DecrementAndClamp:
+        return D3D12_STENCIL_OP_DECR_SAT;
+    case QRhiGraphicsPipeline::Invert:
+        return D3D12_STENCIL_OP_INVERT;
+    case QRhiGraphicsPipeline::IncrementAndWrap:
+        return D3D12_STENCIL_OP_INCR;
+    case QRhiGraphicsPipeline::DecrementAndWrap:
+        return D3D12_STENCIL_OP_DECR;
+    }
+    Q_UNREACHABLE_RETURN(D3D12_STENCIL_OP_KEEP);
+}
+
+static inline D3D12_PRIMITIVE_TOPOLOGY toD3DTopology(QRhiGraphicsPipeline::Topology t, int patchControlPointCount)
+{
+    switch (t) {
+    case QRhiGraphicsPipeline::Triangles:
+        return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+    case QRhiGraphicsPipeline::TriangleStrip:
+        return D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
+    case QRhiGraphicsPipeline::TriangleFan:
+        qWarning("Triangle fans are not supported with D3D");
+        return D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
+    case QRhiGraphicsPipeline::Lines:
+        return D3D_PRIMITIVE_TOPOLOGY_LINELIST;
+    case QRhiGraphicsPipeline::LineStrip:
+        return D3D_PRIMITIVE_TOPOLOGY_LINESTRIP;
+    case QRhiGraphicsPipeline::Points:
+        return D3D_PRIMITIVE_TOPOLOGY_POINTLIST;
+    case QRhiGraphicsPipeline::Patches:
+        Q_ASSERT(patchControlPointCount >= 1 && patchControlPointCount <= 32);
+        return D3D_PRIMITIVE_TOPOLOGY(D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + (patchControlPointCount - 1));
+    }
+    Q_UNREACHABLE_RETURN(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+}
+
+static inline D3D12_PRIMITIVE_TOPOLOGY_TYPE toD3DTopologyType(QRhiGraphicsPipeline::Topology t)
+{
+    switch (t) {
+    case QRhiGraphicsPipeline::Triangles:
+    case QRhiGraphicsPipeline::TriangleStrip:
+    case QRhiGraphicsPipeline::TriangleFan:
+        return D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
+    case QRhiGraphicsPipeline::Lines:
+    case QRhiGraphicsPipeline::LineStrip:
+        return D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE;
+    case QRhiGraphicsPipeline::Points:
+        return D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT;
+    case QRhiGraphicsPipeline::Patches:
+        return D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH;
+    }
+    Q_UNREACHABLE_RETURN(D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE);
+}
+
+static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format format)
+{
+    switch (format) {
+    case QRhiVertexInputAttribute::Float4:
+        return DXGI_FORMAT_R32G32B32A32_FLOAT;
+    case QRhiVertexInputAttribute::Float3:
+        return DXGI_FORMAT_R32G32B32_FLOAT;
+    case QRhiVertexInputAttribute::Float2:
+        return DXGI_FORMAT_R32G32_FLOAT;
+    case QRhiVertexInputAttribute::Float:
+        return DXGI_FORMAT_R32_FLOAT;
+    case QRhiVertexInputAttribute::UNormByte4:
+        return DXGI_FORMAT_R8G8B8A8_UNORM;
+    case QRhiVertexInputAttribute::UNormByte2:
+        return DXGI_FORMAT_R8G8_UNORM;
+    case QRhiVertexInputAttribute::UNormByte:
+        return DXGI_FORMAT_R8_UNORM;
+    case QRhiVertexInputAttribute::UInt4:
+        return DXGI_FORMAT_R32G32B32A32_UINT;
+    case QRhiVertexInputAttribute::UInt3:
+        return DXGI_FORMAT_R32G32B32_UINT;
+    case QRhiVertexInputAttribute::UInt2:
+        return DXGI_FORMAT_R32G32_UINT;
+    case QRhiVertexInputAttribute::UInt:
+        return DXGI_FORMAT_R32_UINT;
+     case QRhiVertexInputAttribute::SInt4:
+        return DXGI_FORMAT_R32G32B32A32_SINT;
+    case QRhiVertexInputAttribute::SInt3:
+        return DXGI_FORMAT_R32G32B32_SINT;
+    case QRhiVertexInputAttribute::SInt2:
+        return DXGI_FORMAT_R32G32_SINT;
+    case QRhiVertexInputAttribute::SInt:
+        return DXGI_FORMAT_R32_SINT;
+    case QRhiVertexInputAttribute::Half4:
+    // Note: D3D does not support half3.  Pass through half3 as half4.
+    case QRhiVertexInputAttribute::Half3:
+        return DXGI_FORMAT_R16G16B16A16_FLOAT;
+    case QRhiVertexInputAttribute::Half2:
+        return DXGI_FORMAT_R16G16_FLOAT;
+    case QRhiVertexInputAttribute::Half:
+         return DXGI_FORMAT_R16_FLOAT;
+    }
+    Q_UNREACHABLE_RETURN(DXGI_FORMAT_R32G32B32A32_FLOAT);
+}
+
+QD3D12GraphicsPipeline::QD3D12GraphicsPipeline(QRhiImplementation *rhi)
+    : QRhiGraphicsPipeline(rhi)
+{
+}
+
+QD3D12GraphicsPipeline::~QD3D12GraphicsPipeline()
+{
+    destroy();
+}
+
+void QD3D12GraphicsPipeline::destroy()
+{
+    if (handle.isNull())
+        return;
+
+    QRHI_RES_RHI(QRhiD3D12);
+    if (rhiD) {
+        rhiD->releaseQueue.deferredReleasePipeline(handle);
+        rhiD->releaseQueue.deferredReleaseRootSignature(rootSigHandle);
+    }
+
+    handle = {};
+    stageData = {};
+
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+bool QD3D12GraphicsPipeline::create()
+{
+    if (!handle.isNull())
+        destroy();
+
+    QRHI_RES_RHI(QRhiD3D12);
+    if (!rhiD->sanityCheckGraphicsPipeline(this))
+        return false;
+
+    rhiD->pipelineCreationStart();
+
+    QByteArray shaderBytecode[5];
+    for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) {
+        const QD3D12Stage d3dStage = qd3d12_stage(shaderStage.type());
+        stageData[d3dStage].valid = true;
+        stageData[d3dStage].stage = d3dStage;
+        auto cacheIt = rhiD->shaderBytecodeCache.data.constFind(shaderStage);
+        if (cacheIt != rhiD->shaderBytecodeCache.data.constEnd()) {
+            shaderBytecode[d3dStage] = cacheIt->bytecode;
+            stageData[d3dStage].nativeResourceBindingMap = cacheIt->nativeResourceBindingMap;
+        } else {
+            QString error;
+            QShaderKey shaderKey;
+            int compileFlags = 0;
+            if (m_flags.testFlag(CompileShadersWithDebugInfo))
+                compileFlags |= int(HlslCompileFlag::WithDebugInfo);
+            const QByteArray bytecode = compileHlslShaderSource(shaderStage.shader(),
+                                                                shaderStage.shaderVariant(),
+                                                                compileFlags,
+                                                                &error,
+                                                                &shaderKey);
+            if (bytecode.isEmpty()) {
+                qWarning("HLSL graphics shader compilation failed: %s", qPrintable(error));
+                return false;
+            }
+
+            shaderBytecode[d3dStage] = bytecode;
+            stageData[d3dStage].nativeResourceBindingMap = shaderStage.shader().nativeResourceBindingMap(shaderKey);
+            rhiD->shaderBytecodeCache.insertWithCapacityLimit(shaderStage,
+                                                              { bytecode, stageData[d3dStage].nativeResourceBindingMap });
+        }
+    }
+
+    QD3D12ShaderResourceBindings *srbD = QRHI_RES(QD3D12ShaderResourceBindings, m_shaderResourceBindings);
+    if (srbD) {
+        rootSigHandle = srbD->createRootSignature(stageData.data(), 5);
+        if (rootSigHandle.isNull()) {
+            qWarning("Failed to create root signature");
+            return false;
+        }
+    }
+    ID3D12RootSignature *rootSig = nullptr;
+    if (QD3D12RootSignature *rs = rhiD->rootSignaturePool.lookupRef(rootSigHandle))
+        rootSig = rs->rootSig;
+    if (!rootSig) {
+        qWarning("Cannot create graphics pipeline state without root signature");
+        return false;
+    }
+
+    QD3D12RenderPassDescriptor *rpD = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc);
+    const DXGI_SAMPLE_DESC sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, DXGI_FORMAT(rpD->colorFormat[0]));
+
+    struct {
+        QD3D12PipelineStateSubObject<ID3D12RootSignature *, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE> rootSig;
+        QD3D12PipelineStateSubObject<D3D12_INPUT_LAYOUT_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_INPUT_LAYOUT> inputLayout;
+        QD3D12PipelineStateSubObject<D3D12_PRIMITIVE_TOPOLOGY_TYPE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PRIMITIVE_TOPOLOGY> primitiveTopology;
+        QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VS> VS;
+        QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_HS> HS;
+        QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DS> DS;
+        QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_GS> GS;
+        QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PS> PS;
+        QD3D12PipelineStateSubObject<D3D12_RASTERIZER_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RASTERIZER> rasterizerState;
+        QD3D12PipelineStateSubObject<D3D12_DEPTH_STENCIL_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL> depthStencilState;
+        QD3D12PipelineStateSubObject<D3D12_BLEND_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_BLEND> blendState;
+        QD3D12PipelineStateSubObject<D3D12_RT_FORMAT_ARRAY, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RENDER_TARGET_FORMATS> rtFormats;
+        QD3D12PipelineStateSubObject<DXGI_FORMAT, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL_FORMAT> dsFormat;
+        QD3D12PipelineStateSubObject<DXGI_SAMPLE_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_DESC> sampleDesc;
+        QD3D12PipelineStateSubObject<UINT, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_MASK> sampleMask;
+        QD3D12PipelineStateSubObject<D3D12_VIEW_INSTANCING_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VIEW_INSTANCING> viewInstancingDesc;
+    } stream;
+
+    stream.rootSig.object = rootSig;
+
+    QVarLengthArray<D3D12_INPUT_ELEMENT_DESC, 4> inputDescs;
+    QByteArrayList matrixSliceSemantics;
+    if (!shaderBytecode[VS].isEmpty()) {
+        for (auto it = m_vertexInputLayout.cbeginAttributes(), itEnd = m_vertexInputLayout.cendAttributes();
+             it != itEnd; ++it)
+        {
+            D3D12_INPUT_ELEMENT_DESC desc = {};
+            // The output from SPIRV-Cross uses TEXCOORD<location> as the
+            // semantic, except for matrices that are unrolled into consecutive
+            // vec2/3/4s attributes and need TEXCOORD<location>_ as
+            // SemanticName and row/column index as SemanticIndex.
+            const int matrixSlice = it->matrixSlice();
+            if (matrixSlice < 0) {
+                desc.SemanticName = "TEXCOORD";
+                desc.SemanticIndex = UINT(it->location());
+            } else {
+                QByteArray sem;
+                sem.resize(16);
+                qsnprintf(sem.data(), sem.size(), "TEXCOORD%d_", it->location() - matrixSlice);
+                matrixSliceSemantics.append(sem);
+                desc.SemanticName = matrixSliceSemantics.last().constData();
+                desc.SemanticIndex = UINT(matrixSlice);
+            }
+            desc.Format = toD3DAttributeFormat(it->format());
+            desc.InputSlot = UINT(it->binding());
+            desc.AlignedByteOffset = it->offset();
+            const QRhiVertexInputBinding *inputBinding = m_vertexInputLayout.bindingAt(it->binding());
+            if (inputBinding->classification() == QRhiVertexInputBinding::PerInstance) {
+                desc.InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA;
+                desc.InstanceDataStepRate = inputBinding->instanceStepRate();
+            } else {
+                desc.InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
+            }
+            inputDescs.append(desc);
+        }
+    }
+
+    stream.inputLayout.object.NumElements = inputDescs.count();
+    stream.inputLayout.object.pInputElementDescs = inputDescs.isEmpty() ? nullptr : inputDescs.constData();
+
+    stream.primitiveTopology.object = toD3DTopologyType(m_topology);
+    topology = toD3DTopology(m_topology, m_patchControlPointCount);
+
+    for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) {
+        const int d3dStage = qd3d12_stage(shaderStage.type());
+        switch (d3dStage) {
+        case VS:
+            stream.VS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
+            stream.VS.object.BytecodeLength = shaderBytecode[d3dStage].size();
+            break;
+        case HS:
+            stream.HS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
+            stream.HS.object.BytecodeLength = shaderBytecode[d3dStage].size();
+            break;
+        case DS:
+            stream.DS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
+            stream.DS.object.BytecodeLength = shaderBytecode[d3dStage].size();
+            break;
+        case GS:
+            stream.GS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
+            stream.GS.object.BytecodeLength = shaderBytecode[d3dStage].size();
+            break;
+        case PS:
+            stream.PS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
+            stream.PS.object.BytecodeLength = shaderBytecode[d3dStage].size();
+            break;
+        default:
+            Q_UNREACHABLE();
+            break;
+        }
+    }
+
+    stream.rasterizerState.object.FillMode = toD3DFillMode(m_polygonMode);
+    stream.rasterizerState.object.CullMode = toD3DCullMode(m_cullMode);
+    stream.rasterizerState.object.FrontCounterClockwise = m_frontFace == CCW;
+    stream.rasterizerState.object.DepthBias = m_depthBias;
+    stream.rasterizerState.object.SlopeScaledDepthBias = m_slopeScaledDepthBias;
+    stream.rasterizerState.object.DepthClipEnable = TRUE;
+    stream.rasterizerState.object.MultisampleEnable = sampleDesc.Count > 1;
+
+    stream.depthStencilState.object.DepthEnable = m_depthTest;
+    stream.depthStencilState.object.DepthWriteMask = m_depthWrite ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO;
+    stream.depthStencilState.object.DepthFunc = toD3DCompareOp(m_depthOp);
+    stream.depthStencilState.object.StencilEnable = m_stencilTest;
+    if (m_stencilTest) {
+        stream.depthStencilState.object.StencilReadMask = UINT8(m_stencilReadMask);
+        stream.depthStencilState.object.StencilWriteMask = UINT8(m_stencilWriteMask);
+        stream.depthStencilState.object.FrontFace.StencilFailOp = toD3DStencilOp(m_stencilFront.failOp);
+        stream.depthStencilState.object.FrontFace.StencilDepthFailOp = toD3DStencilOp(m_stencilFront.depthFailOp);
+        stream.depthStencilState.object.FrontFace.StencilPassOp = toD3DStencilOp(m_stencilFront.passOp);
+        stream.depthStencilState.object.FrontFace.StencilFunc = toD3DCompareOp(m_stencilFront.compareOp);
+        stream.depthStencilState.object.BackFace.StencilFailOp = toD3DStencilOp(m_stencilBack.failOp);
+        stream.depthStencilState.object.BackFace.StencilDepthFailOp = toD3DStencilOp(m_stencilBack.depthFailOp);
+        stream.depthStencilState.object.BackFace.StencilPassOp = toD3DStencilOp(m_stencilBack.passOp);
+        stream.depthStencilState.object.BackFace.StencilFunc = toD3DCompareOp(m_stencilBack.compareOp);
+    }
+
+    stream.blendState.object.IndependentBlendEnable = m_targetBlends.count() > 1;
+    for (int i = 0, ie = m_targetBlends.count(); i != ie; ++i) {
+        const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[i]);
+        D3D12_RENDER_TARGET_BLEND_DESC blend = {};
+        blend.BlendEnable = b.enable;
+        blend.SrcBlend = toD3DBlendFactor(b.srcColor, true);
+        blend.DestBlend = toD3DBlendFactor(b.dstColor, true);
+        blend.BlendOp = toD3DBlendOp(b.opColor);
+        blend.SrcBlendAlpha = toD3DBlendFactor(b.srcAlpha, false);
+        blend.DestBlendAlpha = toD3DBlendFactor(b.dstAlpha, false);
+        blend.BlendOpAlpha = toD3DBlendOp(b.opAlpha);
+        blend.RenderTargetWriteMask = toD3DColorWriteMask(b.colorWrite);
+        stream.blendState.object.RenderTarget[i] = blend;
+    }
+    if (m_targetBlends.isEmpty()) {
+        D3D12_RENDER_TARGET_BLEND_DESC blend = {};
+        blend.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
+        stream.blendState.object.RenderTarget[0] = blend;
+    }
+
+    stream.rtFormats.object.NumRenderTargets = rpD->colorAttachmentCount;
+    for (int i = 0; i < rpD->colorAttachmentCount; ++i)
+        stream.rtFormats.object.RTFormats[i] = DXGI_FORMAT(rpD->colorFormat[i]);
+
+    stream.dsFormat.object = rpD->hasDepthStencil ? DXGI_FORMAT(rpD->dsFormat) : DXGI_FORMAT_UNKNOWN;
+
+    stream.sampleDesc.object = sampleDesc;
+
+    stream.sampleMask.object = 0xFFFFFFFF;
+
+    viewInstanceMask = 0;
+    const bool isMultiView = m_multiViewCount >= 2;
+    stream.viewInstancingDesc.object.ViewInstanceCount = isMultiView ? m_multiViewCount : 0;
+    QVarLengthArray<D3D12_VIEW_INSTANCE_LOCATION, 4> viewInstanceLocations;
+    if (isMultiView) {
+        for (int i = 0; i < m_multiViewCount; ++i) {
+            viewInstanceMask |= (1 << i);
+            viewInstanceLocations.append({ 0, UINT(i) });
+        }
+        stream.viewInstancingDesc.object.pViewInstanceLocations = viewInstanceLocations.constData();
+    }
+
+    const D3D12_PIPELINE_STATE_STREAM_DESC streamDesc = { sizeof(stream), &stream };
+
+    ID3D12PipelineState *pso = nullptr;
+    HRESULT hr = rhiD->dev->CreatePipelineState(&streamDesc, __uuidof(ID3D12PipelineState), reinterpret_cast<void **>(&pso));
+    if (FAILED(hr)) {
+        qWarning("Failed to create graphics pipeline state: %s",
+                 qPrintable(QSystemError::windowsComString(hr)));
+        rhiD->rootSignaturePool.remove(rootSigHandle);
+        rootSigHandle = {};
+        return false;
+    }
+
+    handle = QD3D12Pipeline::addToPool(&rhiD->pipelinePool, QD3D12Pipeline::Graphics, pso);
+
+    rhiD->pipelineCreationEnd();
+    generation += 1;
+    rhiD->registerResource(this);
+    return true;
+}
+
+QD3D12ComputePipeline::QD3D12ComputePipeline(QRhiImplementation *rhi)
+    : QRhiComputePipeline(rhi)
+{
+}
+
+QD3D12ComputePipeline::~QD3D12ComputePipeline()
+{
+    destroy();
+}
+
+void QD3D12ComputePipeline::destroy()
+{
+    if (handle.isNull())
+        return;
+
+    QRHI_RES_RHI(QRhiD3D12);
+    if (rhiD) {
+        rhiD->releaseQueue.deferredReleasePipeline(handle);
+        rhiD->releaseQueue.deferredReleaseRootSignature(rootSigHandle);
+    }
+
+    handle = {};
+    stageData = {};
+
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+bool QD3D12ComputePipeline::create()
+{
+    if (!handle.isNull())
+        destroy();
+
+    QRHI_RES_RHI(QRhiD3D12);
+    rhiD->pipelineCreationStart();
+
+    stageData.valid = true;
+    stageData.stage = CS;
+
+    QByteArray shaderBytecode;
+    auto cacheIt = rhiD->shaderBytecodeCache.data.constFind(m_shaderStage);
+    if (cacheIt != rhiD->shaderBytecodeCache.data.constEnd()) {
+        shaderBytecode = cacheIt->bytecode;
+        stageData.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap;
+    } else {
+        QString error;
+        QShaderKey shaderKey;
+        int compileFlags = 0;
+        if (m_flags.testFlag(CompileShadersWithDebugInfo))
+            compileFlags |= int(HlslCompileFlag::WithDebugInfo);
+        const QByteArray bytecode = compileHlslShaderSource(m_shaderStage.shader(),
+                                                            m_shaderStage.shaderVariant(),
+                                                            compileFlags,
+                                                            &error,
+                                                            &shaderKey);
+        if (bytecode.isEmpty()) {
+            qWarning("HLSL compute shader compilation failed: %s", qPrintable(error));
+            return false;
+        }
+
+        shaderBytecode = bytecode;
+        stageData.nativeResourceBindingMap = m_shaderStage.shader().nativeResourceBindingMap(shaderKey);
+        rhiD->shaderBytecodeCache.insertWithCapacityLimit(m_shaderStage, { bytecode,
+                                                                           stageData.nativeResourceBindingMap });
+    }
+
+    QD3D12ShaderResourceBindings *srbD = QRHI_RES(QD3D12ShaderResourceBindings, m_shaderResourceBindings);
+    if (srbD) {
+        rootSigHandle = srbD->createRootSignature(&stageData, 1);
+        if (rootSigHandle.isNull()) {
+            qWarning("Failed to create root signature");
+            return false;
+        }
+    }
+    ID3D12RootSignature *rootSig = nullptr;
+    if (QD3D12RootSignature *rs = rhiD->rootSignaturePool.lookupRef(rootSigHandle))
+        rootSig = rs->rootSig;
+    if (!rootSig) {
+        qWarning("Cannot create compute pipeline state without root signature");
+        return false;
+    }
+
+    struct {
+        QD3D12PipelineStateSubObject<ID3D12RootSignature *, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE> rootSig;
+        QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CS> CS;
+    } stream;
+    stream.rootSig.object = rootSig;
+    stream.CS.object.pShaderBytecode = shaderBytecode.constData();
+    stream.CS.object.BytecodeLength = shaderBytecode.size();
+    const D3D12_PIPELINE_STATE_STREAM_DESC streamDesc = { sizeof(stream), &stream };
+    ID3D12PipelineState *pso = nullptr;
+    HRESULT hr = rhiD->dev->CreatePipelineState(&streamDesc, __uuidof(ID3D12PipelineState), reinterpret_cast<void **>(&pso));
+    if (FAILED(hr)) {
+        qWarning("Failed to create compute pipeline state: %s",
+                 qPrintable(QSystemError::windowsComString(hr)));
+        rhiD->rootSignaturePool.remove(rootSigHandle);
+        rootSigHandle = {};
+        return false;
+    }
+
+    handle = QD3D12Pipeline::addToPool(&rhiD->pipelinePool, QD3D12Pipeline::Compute, pso);
+
+    rhiD->pipelineCreationEnd();
+    generation += 1;
+    rhiD->registerResource(this);
+    return true;
+}
+
+// This is a lot like in the Metal backend: we need to now the rtv and dsv
+// formats to create a graphics pipeline, and that's exactly what our
+// "renderpass descriptor" is going to hold.
+QD3D12RenderPassDescriptor::QD3D12RenderPassDescriptor(QRhiImplementation *rhi)
+    : QRhiRenderPassDescriptor(rhi)
+{
+    serializedFormatData.reserve(16);
+}
+
+QD3D12RenderPassDescriptor::~QD3D12RenderPassDescriptor()
+{
+    destroy();
+}
+
+void QD3D12RenderPassDescriptor::destroy()
+{
+    QRHI_RES_RHI(QRhiD3D12);
+    if (rhiD)
+        rhiD->unregisterResource(this);
+}
+
+bool QD3D12RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
+{
+    if (!other)
+        return false;
+
+    const QD3D12RenderPassDescriptor *o = QRHI_RES(const QD3D12RenderPassDescriptor, other);
+
+    if (colorAttachmentCount != o->colorAttachmentCount)
+        return false;
+
+    if (hasDepthStencil != o->hasDepthStencil)
+         return false;
+
+    for (int i = 0; i < colorAttachmentCount; ++i) {
+        if (colorFormat[i] != o->colorFormat[i])
+            return false;
+    }
+
+    if (hasDepthStencil) {
+        if (dsFormat != o->dsFormat)
+            return false;
+    }
+
+    return true;
+}
+
+void QD3D12RenderPassDescriptor::updateSerializedFormat()
+{
+    serializedFormatData.clear();
+    auto p = std::back_inserter(serializedFormatData);
+
+    *p++ = colorAttachmentCount;
+    *p++ = hasDepthStencil;
+    for (int i = 0; i < colorAttachmentCount; ++i)
+      *p++ = colorFormat[i];
+    *p++ = hasDepthStencil ? dsFormat : 0;
+}
+
+QRhiRenderPassDescriptor *QD3D12RenderPassDescriptor::newCompatibleRenderPassDescriptor() const
+{
+    QD3D12RenderPassDescriptor *rpD = new QD3D12RenderPassDescriptor(m_rhi);
+    rpD->colorAttachmentCount = colorAttachmentCount;
+    rpD->hasDepthStencil = hasDepthStencil;
+    memcpy(rpD->colorFormat, colorFormat, sizeof(colorFormat));
+    rpD->dsFormat = dsFormat;
+
+    rpD->updateSerializedFormat();
+
+    QRHI_RES_RHI(QRhiD3D12);
+    rhiD->registerResource(rpD);
+    return rpD;
+}
+
+QVector<quint32> QD3D12RenderPassDescriptor::serializedFormat() const
+{
+    return serializedFormatData;
+}
+
+QD3D12CommandBuffer::QD3D12CommandBuffer(QRhiImplementation *rhi)
+    : QRhiCommandBuffer(rhi)
+{
+    resetState();
+}
+
+QD3D12CommandBuffer::~QD3D12CommandBuffer()
+{
+    destroy();
+}
+
+void QD3D12CommandBuffer::destroy()
+{
+    // nothing to do here, the command list is not owned by us
+}
+
+const QRhiNativeHandles *QD3D12CommandBuffer::nativeHandles()
+{
+    nativeHandlesStruct.commandList = cmdList;
+    return &nativeHandlesStruct;
+}
+
+QD3D12SwapChainRenderTarget::QD3D12SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain)
+    : QRhiSwapChainRenderTarget(rhi, swapchain),
+      d(rhi)
+{
+}
+
+QD3D12SwapChainRenderTarget::~QD3D12SwapChainRenderTarget()
+{
+    destroy();
+}
+
+void QD3D12SwapChainRenderTarget::destroy()
+{
+    // nothing to do here
+}
+
+QSize QD3D12SwapChainRenderTarget::pixelSize() const
+{
+    return d.pixelSize;
+}
+
+float QD3D12SwapChainRenderTarget::devicePixelRatio() const
+{
+    return d.dpr;
+}
+
+int QD3D12SwapChainRenderTarget::sampleCount() const
+{
+    return d.sampleCount;
+}
+
+QD3D12SwapChain::QD3D12SwapChain(QRhiImplementation *rhi)
+    : QRhiSwapChain(rhi),
+      rtWrapper(rhi, this),
+      rtWrapperRight(rhi, this),
+      cbWrapper(rhi)
+{
+}
+
+QD3D12SwapChain::~QD3D12SwapChain()
+{
+    destroy();
+}
+
+void QD3D12SwapChain::destroy()
+{
+    if (!swapChain)
+        return;
+
+    releaseBuffers();
+
+    swapChain->Release();
+    swapChain = nullptr;
+    sourceSwapChain1->Release();
+    sourceSwapChain1 = nullptr;
+
+    for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
+        FrameResources &fr(frameRes[i]);
+        if (fr.fence)
+            fr.fence->Release();
+        if (fr.fenceEvent)
+            CloseHandle(fr.fenceEvent);
+        if (fr.cmdList)
+            fr.cmdList->Release();
+        fr = {};
+    }
+
+    if (dcompVisual) {
+        dcompVisual->Release();
+        dcompVisual = nullptr;
+    }
+
+    if (dcompTarget) {
+        dcompTarget->Release();
+        dcompTarget = nullptr;
+    }
+
+    QRHI_RES_RHI(QRhiD3D12);
+    if (rhiD) {
+        rhiD->swapchains.remove(this);
+        rhiD->unregisterResource(this);
+    }
+}
+
+void QD3D12SwapChain::releaseBuffers()
+{
+    QRHI_RES_RHI(QRhiD3D12);
+    rhiD->waitGpu();
+    for (UINT i = 0; i < BUFFER_COUNT; ++i) {
+        rhiD->resourcePool.remove(colorBuffers[i]);
+        rhiD->rtvPool.release(rtvs[i], 1);
+        if (stereo)
+            rhiD->rtvPool.release(rtvsRight[i], 1);
+        if (!msaaBuffers[i].isNull())
+            rhiD->resourcePool.remove(msaaBuffers[i]);
+        if (msaaRtvs[i].isValid())
+            rhiD->rtvPool.release(msaaRtvs[i], 1);
+    }
+}
+
+void QD3D12SwapChain::waitCommandCompletionForFrameSlot(int frameSlot)
+{
+    FrameResources &fr(frameRes[frameSlot]);
+    if (fr.fence->GetCompletedValue() < fr.fenceCounter) {
+        fr.fence->SetEventOnCompletion(fr.fenceCounter, fr.fenceEvent);
+        WaitForSingleObject(fr.fenceEvent, INFINITE);
+    }
+}
+
+void QD3D12SwapChain::addCommandCompletionSignalForCurrentFrameSlot()
+{
+    QRHI_RES_RHI(QRhiD3D12);
+    FrameResources &fr(frameRes[currentFrameSlot]);
+    fr.fenceCounter += 1u;
+    rhiD->cmdQueue->Signal(fr.fence, fr.fenceCounter);
+}
+
+QRhiCommandBuffer *QD3D12SwapChain::currentFrameCommandBuffer()
+{
+    return &cbWrapper;
+}
+
+QRhiRenderTarget *QD3D12SwapChain::currentFrameRenderTarget()
+{
+    return &rtWrapper;
+}
+
+QRhiRenderTarget *QD3D12SwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer)
+{
+    return !stereo || targetBuffer == StereoTargetBuffer::LeftBuffer ? &rtWrapper : &rtWrapperRight;
+}
+
+QSize QD3D12SwapChain::surfacePixelSize()
+{
+    Q_ASSERT(m_window);
+    return m_window->size() * m_window->devicePixelRatio();
+}
+
+bool QD3D12SwapChain::isFormatSupported(Format f)
+{
+    if (f == SDR)
+        return true;
+
+    if (!m_window) {
+        qWarning("Attempted to call isFormatSupported() without a window set");
+        return false;
+    }
+
+    QRHI_RES_RHI(QRhiD3D12);
+    DXGI_OUTPUT_DESC1 desc1;
+    if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &desc1)) {
+        if (desc1.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)
+            return f == QRhiSwapChain::HDRExtendedSrgbLinear || f == QRhiSwapChain::HDR10;
+    }
+
+    return false;
+}
+
+QRhiSwapChainHdrInfo QD3D12SwapChain::hdrInfo()
+{
+    QRhiSwapChainHdrInfo info = QRhiSwapChain::hdrInfo();
+    // Must use m_window, not window, given this may be called before createOrResize().
+    if (m_window) {
+        QRHI_RES_RHI(QRhiD3D12);
+        DXGI_OUTPUT_DESC1 hdrOutputDesc;
+        if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) {
+            info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits;
+            info.limits.luminanceInNits.minLuminance = hdrOutputDesc.MinLuminance;
+            info.limits.luminanceInNits.maxLuminance = hdrOutputDesc.MaxLuminance;
+            info.luminanceBehavior = QRhiSwapChainHdrInfo::SceneReferred; // 1.0 = 80 nits
+            info.sdrWhiteLevel = QRhiD3D::sdrWhiteLevelInNits(hdrOutputDesc);
+        }
+    }
+    return info;
+}
+
+QRhiRenderPassDescriptor *QD3D12SwapChain::newCompatibleRenderPassDescriptor()
+{
+    // not yet built so cannot rely on data computed in createOrResize()
+    chooseFormats();
+
+    QD3D12RenderPassDescriptor *rpD = new QD3D12RenderPassDescriptor(m_rhi);
+    rpD->colorAttachmentCount = 1;
+    rpD->hasDepthStencil = m_depthStencil != nullptr;
+    rpD->colorFormat[0] = int(srgbAdjustedColorFormat);
+    rpD->dsFormat = QD3D12RenderBuffer::DS_FORMAT;
+    rpD->updateSerializedFormat();
+
+    QRHI_RES_RHI(QRhiD3D12);
+    rhiD->registerResource(rpD);
+    return rpD;
+}
+
+bool QRhiD3D12::ensureDirectCompositionDevice()
+{
+    if (dcompDevice)
+        return true;
+
+    qCDebug(QRHI_LOG_INFO, "Creating Direct Composition device (needed for semi-transparent windows)");
+    dcompDevice = QRhiD3D::createDirectCompositionDevice();
+    return dcompDevice ? true : false;
+}
+
+static const DXGI_FORMAT DEFAULT_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;
+static const DXGI_FORMAT DEFAULT_SRGB_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
+
+void QD3D12SwapChain::chooseFormats()
+{
+    colorFormat = DEFAULT_FORMAT;
+    srgbAdjustedColorFormat = m_flags.testFlag(sRGB) ? DEFAULT_SRGB_FORMAT : DEFAULT_FORMAT;
+    hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; // SDR
+    DXGI_OUTPUT_DESC1 hdrOutputDesc;
+    QRHI_RES_RHI(QRhiD3D12);
+    if (QRhiD3D::outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc) && m_format != SDR) {
+        // https://docs.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range
+        if (hdrOutputDesc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
+            switch (m_format) {
+            case HDRExtendedSrgbLinear:
+                colorFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
+                hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
+                srgbAdjustedColorFormat = colorFormat;
+                break;
+            case HDR10:
+                colorFormat = DXGI_FORMAT_R10G10B10A2_UNORM;
+                hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
+                srgbAdjustedColorFormat = colorFormat;
+                break;
+            default:
+                break;
+            }
+        } else {
+            // This happens also when Use HDR is set to Off in the Windows
+            // Display settings. Show a helpful warning, but continue with the
+            // default non-HDR format.
+            qWarning("The output associated with the window is not HDR capable "
+                     "(or Use HDR is Off in the Display Settings), ignoring HDR format request");
+        }
+    }
+    sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, colorFormat);
+}
+
+bool QD3D12SwapChain::createOrResize()
+{
+    // Can be called multiple times due to window resizes - that is not the
+    // same as a simple destroy+create (as with other resources). Just need to
+    // resize the buffers then.
+
+    const bool needsRegistration = !window || window != m_window;
+
+    // except if the window actually changes
+    if (window && window != m_window)
+        destroy();
+
+    window = m_window;
+    m_currentPixelSize = surfacePixelSize();
+    pixelSize = m_currentPixelSize;
+
+    if (pixelSize.isEmpty())
+        return false;
+
+    HWND hwnd = reinterpret_cast<HWND>(window->winId());
+    HRESULT hr;
+    QRHI_RES_RHI(QRhiD3D12);
+    stereo = m_window->format().stereo() && rhiD->dxgiFactory->IsWindowedStereoEnabled();
+
+    if (m_flags.testFlag(SurfaceHasPreMulAlpha) || m_flags.testFlag(SurfaceHasNonPreMulAlpha)) {
+        if (rhiD->ensureDirectCompositionDevice()) {
+            if (!dcompTarget) {
+                hr = rhiD->dcompDevice->CreateTargetForHwnd(hwnd, true, &dcompTarget);
+                if (FAILED(hr)) {
+                    qWarning("Failed to create Direct Composition target for the window: %s",
+                             qPrintable(QSystemError::windowsComString(hr)));
+                }
+            }
+            if (dcompTarget && !dcompVisual) {
+                hr = rhiD->dcompDevice->CreateVisual(&dcompVisual);
+                if (FAILED(hr)) {
+                    qWarning("Failed to create DirectComposition visual: %s",
+                             qPrintable(QSystemError::windowsComString(hr)));
+                }
+            }
+        }
+        // simple consistency check
+        if (window->requestedFormat().alphaBufferSize() <= 0)
+            qWarning("Swapchain says surface has alpha but the window has no alphaBufferSize set. "
+                     "This may lead to problems.");
+    }
+
+    swapInterval = m_flags.testFlag(QRhiSwapChain::NoVSync) ? 0 : 1;
+    swapChainFlags = 0;
+    if (swapInterval == 0 && rhiD->supportsAllowTearing)
+        swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
+
+    if (!swapChain) {
+        chooseFormats();
+
+        DXGI_SWAP_CHAIN_DESC1 desc = {};
+        desc.Width = UINT(pixelSize.width());
+        desc.Height = UINT(pixelSize.height());
+        desc.Format = colorFormat;
+        desc.SampleDesc.Count = 1;
+        desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+        desc.BufferCount = BUFFER_COUNT;
+        desc.Flags = swapChainFlags;
+        desc.Scaling = DXGI_SCALING_NONE;
+        desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+        desc.Stereo = stereo;
+
+        if (dcompVisual) {
+            // With DirectComposition setting AlphaMode to STRAIGHT fails the
+            // swapchain creation, whereas the result seems to be identical
+            // with any of the other values, including IGNORE. (?)
+            desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
+
+            // DirectComposition has its own limitations, cannot use
+            // SCALING_NONE. So with semi-transparency requested we are forced
+            // to SCALING_STRETCH.
+            desc.Scaling = DXGI_SCALING_STRETCH;
+        }
+
+        if (dcompVisual)
+            hr = rhiD->dxgiFactory->CreateSwapChainForComposition(rhiD->cmdQueue, &desc, nullptr, &sourceSwapChain1);
+        else
+            hr = rhiD->dxgiFactory->CreateSwapChainForHwnd(rhiD->cmdQueue, hwnd, &desc, nullptr, nullptr, &sourceSwapChain1);
+
+        // If failed and we tried a HDR format, then try with SDR. This
+        // matches other backends, such as Vulkan where if the format is
+        // not supported, the default one is used instead.
+        if (FAILED(hr) && m_format != SDR) {
+            colorFormat = DEFAULT_FORMAT;
+            desc.Format = DEFAULT_FORMAT;
+            if (dcompVisual)
+                hr = rhiD->dxgiFactory->CreateSwapChainForComposition(rhiD->cmdQueue, &desc, nullptr, &sourceSwapChain1);
+            else
+                hr = rhiD->dxgiFactory->CreateSwapChainForHwnd(rhiD->cmdQueue, hwnd, &desc, nullptr, nullptr, &sourceSwapChain1);
+        }
+
+        if (SUCCEEDED(hr)) {
+            if (FAILED(sourceSwapChain1->QueryInterface(__uuidof(IDXGISwapChain3), reinterpret_cast<void **>(&swapChain)))) {
+                qWarning("IDXGISwapChain3 not available");
+                return false;
+            }
+            if (m_format != SDR) {
+                hr = swapChain->SetColorSpace1(hdrColorSpace);
+                if (FAILED(hr)) {
+                    qWarning("Failed to set color space on swapchain: %s",
+                             qPrintable(QSystemError::windowsComString(hr)));
+                }
+            }
+            if (dcompVisual) {
+                hr = dcompVisual->SetContent(swapChain);
+                if (SUCCEEDED(hr)) {
+                    hr = dcompTarget->SetRoot(dcompVisual);
+                    if (FAILED(hr)) {
+                        qWarning("Failed to associate Direct Composition visual with the target: %s",
+                                 qPrintable(QSystemError::windowsComString(hr)));
+                    }
+                } else {
+                    qWarning("Failed to set content for Direct Composition visual: %s",
+                             qPrintable(QSystemError::windowsComString(hr)));
+                }
+            } else {
+                // disable Alt+Enter; not relevant when using DirectComposition
+                rhiD->dxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
+            }
+        }
+        if (FAILED(hr)) {
+            qWarning("Failed to create D3D12 swapchain: %s"
+                     " (Width=%u Height=%u Format=%u SampleCount=%u BufferCount=%u Scaling=%u SwapEffect=%u Stereo=%u)",
+                     qPrintable(QSystemError::windowsComString(hr)),
+                     desc.Width, desc.Height, UINT(desc.Format), desc.SampleDesc.Count,
+                     desc.BufferCount, UINT(desc.Scaling), UINT(desc.SwapEffect), UINT(desc.Stereo));
+            return false;
+        }
+
+        for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
+            hr = rhiD->dev->CreateFence(0,
+                                        D3D12_FENCE_FLAG_NONE,
+                                        __uuidof(ID3D12Fence),
+                                        reinterpret_cast<void **>(&frameRes[i].fence));
+            if (FAILED(hr)) {
+                qWarning("Failed to create fence for swapchain: %s",
+                         qPrintable(QSystemError::windowsComString(hr)));
+                return false;
+            }
+            frameRes[i].fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+
+            frameRes[i].fenceCounter = 0;
+        }
+    } else {
+        releaseBuffers();
+        hr = swapChain->ResizeBuffers(BUFFER_COUNT,
+                                      UINT(pixelSize.width()),
+                                      UINT(pixelSize.height()),
+                                      colorFormat,
+                                      swapChainFlags);
+        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 D3D12 swapchain: %s", qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+    }
+
+    for (UINT i = 0; i < BUFFER_COUNT; ++i) {
+        ID3D12Resource *colorBuffer;
+        hr = swapChain->GetBuffer(i, __uuidof(ID3D12Resource), reinterpret_cast<void **>(&colorBuffer));
+        if (FAILED(hr)) {
+            qWarning("Failed to get buffer %u for D3D12 swapchain: %s",
+                     i, qPrintable(QSystemError::windowsComString(hr)));
+            return false;
+        }
+        colorBuffers[i] = QD3D12Resource::addToPool(&rhiD->resourcePool, colorBuffer, D3D12_RESOURCE_STATE_PRESENT);
+        rtvs[i] = rhiD->rtvPool.allocate(1);
+        D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
+        rtvDesc.Format = srgbAdjustedColorFormat;
+        rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
+        rhiD->dev->CreateRenderTargetView(colorBuffer, &rtvDesc, rtvs[i].cpuHandle);
+
+        if (stereo) {
+            rtvsRight[i] = rhiD->rtvPool.allocate(1);
+            D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
+            rtvDesc.Format = srgbAdjustedColorFormat;
+            rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
+            rtvDesc.Texture2DArray.ArraySize = 1;
+            rtvDesc.Texture2DArray.FirstArraySlice = 1;
+            rhiD->dev->CreateRenderTargetView(colorBuffer, &rtvDesc, rtvsRight[i].cpuHandle);
+        }
+    }
+
+    if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
+        qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
+                 m_depthStencil->sampleCount(), m_sampleCount);
+    }
+    if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
+        if (m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly)) {
+            m_depthStencil->setPixelSize(pixelSize);
+            if (!m_depthStencil->create())
+                qWarning("Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d",
+                         pixelSize.width(), pixelSize.height());
+        } else {
+            qWarning("Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems.",
+                     m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
+                     pixelSize.width(), pixelSize.height());
+        }
+    }
+
+    ds = m_depthStencil ? QRHI_RES(QD3D12RenderBuffer, m_depthStencil) : nullptr;
+
+    if (sampleDesc.Count > 1) {
+        for (UINT i = 0; i < BUFFER_COUNT; ++i) {
+            D3D12_RESOURCE_DESC resourceDesc = {};
+            resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
+            resourceDesc.Width = UINT64(pixelSize.width());
+            resourceDesc.Height = UINT(pixelSize.height());
+            resourceDesc.DepthOrArraySize = 1;
+            resourceDesc.MipLevels = 1;
+            resourceDesc.Format = srgbAdjustedColorFormat;
+            resourceDesc.SampleDesc = sampleDesc;
+            resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
+            resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
+            D3D12_CLEAR_VALUE clearValue = {};
+            clearValue.Format = colorFormat;
+            ID3D12Resource *resource = nullptr;
+            D3D12MA::Allocation *allocation = nullptr;
+            HRESULT hr = rhiD->vma.createResource(D3D12_HEAP_TYPE_DEFAULT,
+                                                  &resourceDesc,
+                                                  D3D12_RESOURCE_STATE_RENDER_TARGET,
+                                                  &clearValue,
+                                                  &allocation,
+                                                  __uuidof(ID3D12Resource),
+                                                  reinterpret_cast<void **>(&resource));
+            if (FAILED(hr)) {
+                qWarning("Failed to create MSAA color buffer: %s", qPrintable(QSystemError::windowsComString(hr)));
+                return false;
+            }
+            msaaBuffers[i] = QD3D12Resource::addToPool(&rhiD->resourcePool, resource, D3D12_RESOURCE_STATE_RENDER_TARGET, allocation);
+            msaaRtvs[i] = rhiD->rtvPool.allocate(1);
+            if (!msaaRtvs[i].isValid())
+                return false;
+            D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
+            rtvDesc.Format = srgbAdjustedColorFormat;
+            rtvDesc.ViewDimension = sampleDesc.Count > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMS
+                                                         : D3D12_RTV_DIMENSION_TEXTURE2D;
+            rhiD->dev->CreateRenderTargetView(resource, &rtvDesc, msaaRtvs[i].cpuHandle);
+        }
+    }
+
+    currentBackBufferIndex = swapChain->GetCurrentBackBufferIndex();
+    currentFrameSlot = 0;
+
+    rtWrapper.setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget
+    QD3D12SwapChainRenderTarget *rtD = QRHI_RES(QD3D12SwapChainRenderTarget, &rtWrapper);
+    rtD->d.rp = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc);
+    rtD->d.pixelSize = pixelSize;
+    rtD->d.dpr = float(window->devicePixelRatio());
+    rtD->d.sampleCount = int(sampleDesc.Count);
+    rtD->d.colorAttCount = 1;
+    rtD->d.dsAttCount = m_depthStencil ? 1 : 0;
+
+    rtWrapperRight.setRenderPassDescriptor(m_renderPassDesc);
+    QD3D12SwapChainRenderTarget *rtDr = QRHI_RES(QD3D12SwapChainRenderTarget, &rtWrapperRight);
+    rtDr->d.rp = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc);
+    rtDr->d.pixelSize = pixelSize;
+    rtDr->d.dpr = float(window->devicePixelRatio());
+    rtDr->d.sampleCount = int(sampleDesc.Count);
+    rtDr->d.colorAttCount = 1;
+    rtDr->d.dsAttCount = m_depthStencil ? 1 : 0;
+
+    if (needsRegistration) {
+        rhiD->swapchains.insert(this);
+        rhiD->registerResource(this);
+    }
+
+    return true;
+}
+
+QT_END_NAMESPACE
+
+#endif // __ID3D12Device2_INTERFACE_DEFINED__
diff --git a/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp b/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp
index cef739201b..fe2070a069 100644
--- a/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp
+++ b/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp
@@ -704,7 +704,16 @@ QFont QWindowsFontDatabaseBase::systemDefaultFont()
     // Qt 6: Obtain default GUI font (typically "Segoe UI, 9pt", see QTBUG-58610)
     NONCLIENTMETRICS ncm = {};
     ncm.cbSize = sizeof(ncm);
-    SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0, defaultVerticalDPI());
+
+    typedef BOOL (WINAPI *SystemParametersInfoForDpiFunc) (UINT, UINT, PVOID, UINT, UINT);
+    static SystemParametersInfoForDpiFunc mySystemParametersInfoForDpi = 
+        (SystemParametersInfoForDpiFunc)::GetProcAddress(::GetModuleHandle(L"user32"), "SystemParametersInfoForDpi");
+
+    if (mySystemParametersInfoForDpi)
+        mySystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0, defaultVerticalDPI());
+    else
+        SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize , &ncm, 0);
+
     const QFont systemFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont);
     qCDebug(lcQpaFonts) << __FUNCTION__ << systemFont;
     return systemFont;
diff --git a/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp.orig b/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp.orig
new file mode 100644
index 0000000000..cef739201b
--- /dev/null
+++ b/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp.orig
@@ -0,0 +1,1002 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindowsfontdatabasebase_p.h"
+#include "qwindowsfontdatabase_p.h"
+
+#include <QtCore/QThreadStorage>
+#include <QtCore/QtEndian>
+
+#if QT_CONFIG(directwrite)
+#  if QT_CONFIG(directwrite3)
+#    include <dwrite_3.h>
+#  else
+#    include <dwrite_2.h>
+#  endif
+#  include <d2d1.h>
+#  include "qwindowsfontenginedirectwrite_p.h"
+#endif
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+// Helper classes for creating font engines directly from font data
+namespace {
+
+#   pragma pack(1)
+
+    // Common structure for all formats of the "name" table
+    struct NameTable
+    {
+        quint16 format;
+        quint16 count;
+        quint16 stringOffset;
+    };
+
+    struct NameRecord
+    {
+        quint16 platformID;
+        quint16 encodingID;
+        quint16 languageID;
+        quint16 nameID;
+        quint16 length;
+        quint16 offset;
+    };
+
+    struct OffsetSubTable
+    {
+        quint32 scalerType;
+        quint16 numTables;
+        quint16 searchRange;
+        quint16 entrySelector;
+        quint16 rangeShift;
+    };
+
+    struct TableDirectory : public QWindowsFontDatabaseBase::FontTable
+    {
+        quint32 identifier;
+        quint32 checkSum;
+        quint32 offset;
+        quint32 length;
+    };
+
+    struct OS2Table
+    {
+        quint16 version;
+        qint16  avgCharWidth;
+        quint16 weightClass;
+        quint16 widthClass;
+        quint16 type;
+        qint16  subscriptXSize;
+        qint16  subscriptYSize;
+        qint16  subscriptXOffset;
+        qint16  subscriptYOffset;
+        qint16  superscriptXSize;
+        qint16  superscriptYSize;
+        qint16  superscriptXOffset;
+        qint16  superscriptYOffset;
+        qint16  strikeOutSize;
+        qint16  strikeOutPosition;
+        qint16  familyClass;
+        quint8  panose[10];
+        quint32 unicodeRanges[4];
+        quint8  vendorID[4];
+        quint16 selection;
+        quint16 firstCharIndex;
+        quint16 lastCharIndex;
+        qint16  typoAscender;
+        qint16  typoDescender;
+        qint16  typoLineGap;
+        quint16 winAscent;
+        quint16 winDescent;
+        quint32 codepageRanges[2];
+        qint16  height;
+        qint16  capHeight;
+        quint16 defaultChar;
+        quint16 breakChar;
+        quint16 maxContext;
+    };
+
+#   pragma pack()
+
+} // Anonymous namespace
+
+QWindowsFontDatabaseBase::FontTable *QWindowsFontDatabaseBase::EmbeddedFont::tableDirectoryEntry(const QByteArray &tagName)
+{
+    Q_ASSERT(tagName.size() == 4);
+    quint32 tagId = *(reinterpret_cast<const quint32 *>(tagName.constData()));
+    const size_t fontDataSize = m_fontData.size();
+    if (Q_UNLIKELY(fontDataSize < sizeof(OffsetSubTable)))
+        return nullptr;
+
+    OffsetSubTable *offsetSubTable = reinterpret_cast<OffsetSubTable *>(m_fontData.data());
+    TableDirectory *tableDirectory = reinterpret_cast<TableDirectory *>(offsetSubTable + 1);
+
+    const size_t tableCount = qFromBigEndian<quint16>(offsetSubTable->numTables);
+    if (Q_UNLIKELY(fontDataSize < sizeof(OffsetSubTable) + sizeof(TableDirectory) * tableCount))
+        return nullptr;
+
+    TableDirectory *tableDirectoryEnd = tableDirectory + tableCount;
+    for (TableDirectory *entry = tableDirectory; entry < tableDirectoryEnd; ++entry) {
+        if (entry->identifier == tagId)
+            return entry;
+    }
+
+    return nullptr;
+}
+
+QString QWindowsFontDatabaseBase::EmbeddedFont::familyName(QWindowsFontDatabaseBase::FontTable *directoryEntry)
+{
+    QString name;
+
+    TableDirectory *nameTableDirectoryEntry = static_cast<TableDirectory *>(directoryEntry);
+    if (nameTableDirectoryEntry == nullptr)
+        nameTableDirectoryEntry = static_cast<TableDirectory *>(tableDirectoryEntry("name"));
+
+    if (nameTableDirectoryEntry != nullptr) {
+        quint32 offset = qFromBigEndian<quint32>(nameTableDirectoryEntry->offset);
+        if (Q_UNLIKELY(quint32(m_fontData.size()) < offset + sizeof(NameTable)))
+            return QString();
+
+        NameTable *nameTable = reinterpret_cast<NameTable *>(m_fontData.data() + offset);
+        NameRecord *nameRecord = reinterpret_cast<NameRecord *>(nameTable + 1);
+
+        quint16 nameTableCount = qFromBigEndian<quint16>(nameTable->count);
+        if (Q_UNLIKELY(quint32(m_fontData.size()) < offset + sizeof(NameRecord) * nameTableCount))
+            return QString();
+
+        for (int i = 0; i < nameTableCount; ++i, ++nameRecord) {
+            if (qFromBigEndian<quint16>(nameRecord->nameID) == 1
+             && qFromBigEndian<quint16>(nameRecord->platformID) == 3 // Windows
+             && qFromBigEndian<quint16>(nameRecord->languageID) == 0x0409) { // US English
+                quint16 stringOffset = qFromBigEndian<quint16>(nameTable->stringOffset);
+                quint16 nameOffset = qFromBigEndian<quint16>(nameRecord->offset);
+                quint16 nameLength = qFromBigEndian<quint16>(nameRecord->length);
+
+                if (Q_UNLIKELY(quint32(m_fontData.size()) < offset + stringOffset + nameOffset + nameLength))
+                    return QString();
+
+                const void *ptr = reinterpret_cast<const quint8 *>(nameTable)
+                                                    + stringOffset
+                                                    + nameOffset;
+
+                const quint16 *s = reinterpret_cast<const quint16 *>(ptr);
+                const quint16 *e = s + nameLength / sizeof(quint16);
+                while (s != e)
+                    name += QChar( qFromBigEndian<quint16>(*s++));
+                break;
+            }
+        }
+    }
+
+    return name;
+}
+
+void QWindowsFontDatabaseBase::EmbeddedFont::updateFromOS2Table(QFontEngine *fontEngine)
+{
+    TableDirectory *os2TableEntry = static_cast<TableDirectory *>(tableDirectoryEntry("OS/2"));
+    if (os2TableEntry != nullptr) {
+        const OS2Table *os2Table =
+                reinterpret_cast<const OS2Table *>(m_fontData.constData()
+                                                   + qFromBigEndian<quint32>(os2TableEntry->offset));
+
+        bool italic = qFromBigEndian<quint16>(os2Table->selection)  & (1 << 0);
+        bool oblique = qFromBigEndian<quint16>(os2Table->selection) & (1 << 9);
+
+        if (italic)
+            fontEngine->fontDef.style = QFont::StyleItalic;
+        else if (oblique)
+            fontEngine->fontDef.style = QFont::StyleOblique;
+        else
+            fontEngine->fontDef.style = QFont::StyleNormal;
+
+        fontEngine->fontDef.weight = qFromBigEndian<quint16>(os2Table->weightClass);
+    }
+}
+
+QString QWindowsFontDatabaseBase::EmbeddedFont::changeFamilyName(const QString &newFamilyName)
+{
+    TableDirectory *nameTableDirectoryEntry = static_cast<TableDirectory *>(tableDirectoryEntry("name"));
+    if (nameTableDirectoryEntry == nullptr)
+        return QString();
+
+    QString oldFamilyName = familyName(nameTableDirectoryEntry);
+
+    // Reserve size for name table header, five required name records and string
+    const int requiredRecordCount = 5;
+    quint16 nameIds[requiredRecordCount] = { 1, 2, 3, 4, 6 };
+
+    int sizeOfHeader = sizeof(NameTable) + sizeof(NameRecord) * requiredRecordCount;
+    int newFamilyNameSize = newFamilyName.size() * int(sizeof(quint16));
+
+    const QString regularString = QString::fromLatin1("Regular");
+    int regularStringSize = regularString.size() * int(sizeof(quint16));
+
+    // Align table size of table to 32 bits (pad with 0)
+    int fullSize = ((sizeOfHeader + newFamilyNameSize + regularStringSize) & ~3) + 4;
+
+    QByteArray newNameTable(fullSize, char(0));
+
+    {
+        NameTable *nameTable = reinterpret_cast<NameTable *>(newNameTable.data());
+        nameTable->count = qbswap<quint16>(requiredRecordCount);
+        nameTable->stringOffset = qbswap<quint16>(sizeOfHeader);
+
+        NameRecord *nameRecord = reinterpret_cast<NameRecord *>(nameTable + 1);
+        for (int i = 0; i < requiredRecordCount; ++i, nameRecord++) {
+            nameRecord->nameID = qbswap<quint16>(nameIds[i]);
+            nameRecord->encodingID = qbswap<quint16>(1);
+            nameRecord->languageID = qbswap<quint16>(0x0409);
+            nameRecord->platformID = qbswap<quint16>(3);
+            nameRecord->length = qbswap<quint16>(newFamilyNameSize);
+
+            // Special case for sub-family
+            if (nameIds[i] == 4) {
+                nameRecord->offset = qbswap<quint16>(newFamilyNameSize);
+                nameRecord->length = qbswap<quint16>(regularStringSize);
+            }
+        }
+
+        // nameRecord now points to string data
+        quint16 *stringStorage = reinterpret_cast<quint16 *>(nameRecord);
+        for (QChar ch : newFamilyName)
+            *stringStorage++ = qbswap<quint16>(quint16(ch.unicode()));
+
+        for (QChar ch : regularString)
+            *stringStorage++ = qbswap<quint16>(quint16(ch.unicode()));
+    }
+
+    quint32 *p = reinterpret_cast<quint32 *>(newNameTable.data());
+    quint32 *tableEnd = reinterpret_cast<quint32 *>(newNameTable.data() + fullSize);
+
+    quint32 checkSum = 0;
+    while (p < tableEnd)
+        checkSum +=  qFromBigEndian<quint32>(*(p++));
+
+    nameTableDirectoryEntry->checkSum = qbswap<quint32>(checkSum);
+    nameTableDirectoryEntry->offset = qbswap<quint32>(m_fontData.size());
+    nameTableDirectoryEntry->length = qbswap<quint32>(fullSize);
+
+    m_fontData.append(newNameTable);
+
+    return oldFamilyName;
+}
+
+#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
+
+namespace {
+    class DirectWriteFontFileStream: public IDWriteFontFileStream
+    {
+        Q_DISABLE_COPY(DirectWriteFontFileStream)
+    public:
+        DirectWriteFontFileStream(const QByteArray &fontData)
+            : m_fontData(fontData)
+            , m_referenceCount(0)
+        {
+        }
+        virtual ~DirectWriteFontFileStream()
+        {
+        }
+
+        HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **object) override;
+        ULONG STDMETHODCALLTYPE AddRef() override;
+        ULONG STDMETHODCALLTYPE Release() override;
+
+        HRESULT STDMETHODCALLTYPE ReadFileFragment(const void **fragmentStart, UINT64 fileOffset,
+                                                   UINT64 fragmentSize, OUT void **fragmentContext) override;
+        void STDMETHODCALLTYPE ReleaseFileFragment(void *fragmentContext) override;
+        HRESULT STDMETHODCALLTYPE GetFileSize(OUT UINT64 *fileSize) override;
+        HRESULT STDMETHODCALLTYPE GetLastWriteTime(OUT UINT64 *lastWriteTime) override;
+
+    private:
+        QByteArray m_fontData;
+        ULONG m_referenceCount;
+    };
+
+    HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::QueryInterface(REFIID iid, void **object)
+    {
+        if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileStream)) {
+            *object = this;
+            AddRef();
+            return S_OK;
+        } else {
+            *object = NULL;
+            return E_NOINTERFACE;
+        }
+    }
+
+    ULONG STDMETHODCALLTYPE DirectWriteFontFileStream::AddRef()
+    {
+        return InterlockedIncrement(&m_referenceCount);
+    }
+
+    ULONG STDMETHODCALLTYPE DirectWriteFontFileStream::Release()
+    {
+        ULONG newCount = InterlockedDecrement(&m_referenceCount);
+        if (newCount == 0)
+            delete this;
+        return newCount;
+    }
+
+    HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::ReadFileFragment(
+        const void **fragmentStart,
+        UINT64 fileOffset,
+        UINT64 fragmentSize,
+        OUT void **fragmentContext)
+    {
+        *fragmentContext = NULL;
+        if (fileOffset + fragmentSize <= quint64(m_fontData.size())) {
+            *fragmentStart = m_fontData.data() + fileOffset;
+            return S_OK;
+        } else {
+            *fragmentStart = NULL;
+            return E_FAIL;
+        }
+    }
+
+    void STDMETHODCALLTYPE DirectWriteFontFileStream::ReleaseFileFragment(void *)
+    {
+    }
+
+    HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::GetFileSize(UINT64 *fileSize)
+    {
+        *fileSize = m_fontData.size();
+        return S_OK;
+    }
+
+    HRESULT STDMETHODCALLTYPE DirectWriteFontFileStream::GetLastWriteTime(UINT64 *lastWriteTime)
+    {
+        *lastWriteTime = 0;
+        return E_NOTIMPL;
+    }
+
+    class DirectWriteFontFileLoader: public IDWriteFontFileLoader
+    {
+    public:
+        DirectWriteFontFileLoader() : m_referenceCount(0) {}
+        virtual ~DirectWriteFontFileLoader()
+        {
+        }
+
+        inline void addKey(const QByteArray &fontData)
+        {
+            if (!m_fontDatas.contains(fontData.data()))
+                m_fontDatas.insert(fontData.data(), fontData);
+        }
+
+        inline void removeKey(const void *key)
+        {
+            m_fontDatas.remove(key);
+        }
+
+        HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **object) override;
+        ULONG STDMETHODCALLTYPE AddRef() override;
+        ULONG STDMETHODCALLTYPE Release() override;
+
+        HRESULT STDMETHODCALLTYPE CreateStreamFromKey(void const *fontFileReferenceKey,
+                                                      UINT32 fontFileReferenceKeySize,
+                                                      OUT IDWriteFontFileStream **fontFileStream) override;
+
+        void clear()
+        {
+            m_fontDatas.clear();
+        }
+
+    private:
+        ULONG m_referenceCount;
+        QHash<const void *, QByteArray> m_fontDatas;
+    };
+
+    HRESULT STDMETHODCALLTYPE DirectWriteFontFileLoader::QueryInterface(const IID &iid,
+                                                                        void **object)
+    {
+        if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileLoader)) {
+            *object = this;
+            AddRef();
+            return S_OK;
+        } else {
+            *object = NULL;
+            return E_NOINTERFACE;
+        }
+    }
+
+    ULONG STDMETHODCALLTYPE DirectWriteFontFileLoader::AddRef()
+    {
+        return InterlockedIncrement(&m_referenceCount);
+    }
+
+    ULONG STDMETHODCALLTYPE DirectWriteFontFileLoader::Release()
+    {
+        ULONG newCount = InterlockedDecrement(&m_referenceCount);
+        if (newCount == 0)
+            delete this;
+        return newCount;
+    }
+
+    HRESULT STDMETHODCALLTYPE DirectWriteFontFileLoader::CreateStreamFromKey(
+        void const *fontFileReferenceKey,
+        UINT32 fontFileReferenceKeySize,
+        IDWriteFontFileStream **fontFileStream)
+    {
+        Q_UNUSED(fontFileReferenceKeySize);
+
+        if (fontFileReferenceKeySize != sizeof(const void *)) {
+            qWarning("%s: Wrong key size", __FUNCTION__);
+            return E_FAIL;
+        }
+
+        const void *key = *reinterpret_cast<void * const *>(fontFileReferenceKey);
+        *fontFileStream = NULL;
+        auto it = m_fontDatas.constFind(key);
+        if (it == m_fontDatas.constEnd())
+            return E_FAIL;
+
+        QByteArray fontData = it.value();
+        DirectWriteFontFileStream *stream = new DirectWriteFontFileStream(fontData);
+        stream->AddRef();
+        *fontFileStream = stream;
+
+        return S_OK;
+    }
+
+} // Anonymous namespace
+
+class QCustomFontFileLoader
+{
+public:
+    QCustomFontFileLoader(IDWriteFactory *factory)
+    {
+        m_directWriteFactory = factory;
+
+        if (m_directWriteFactory) {
+            m_directWriteFactory->AddRef();
+
+            m_directWriteFontFileLoader = new DirectWriteFontFileLoader();
+            m_directWriteFactory->RegisterFontFileLoader(m_directWriteFontFileLoader);
+        }
+    }
+
+    ~QCustomFontFileLoader()
+    {
+        clear();
+
+        if (m_directWriteFactory != nullptr && m_directWriteFontFileLoader != nullptr)
+            m_directWriteFactory->UnregisterFontFileLoader(m_directWriteFontFileLoader);
+
+        if (m_directWriteFactory != nullptr)
+            m_directWriteFactory->Release();
+    }
+
+    void addKey(const QByteArray &fontData)
+    {
+        if (m_directWriteFontFileLoader != nullptr)
+            m_directWriteFontFileLoader->addKey(fontData);
+    }
+
+    void removeKey(const void *key)
+    {
+        if (m_directWriteFontFileLoader != nullptr)
+            m_directWriteFontFileLoader->removeKey(key);
+    }
+
+    IDWriteFontFileLoader *loader() const
+    {
+        return m_directWriteFontFileLoader;
+    }
+
+    void clear()
+    {
+        if (m_directWriteFontFileLoader != nullptr)
+            m_directWriteFontFileLoader->clear();
+    }
+
+private:
+    IDWriteFactory *m_directWriteFactory                    = nullptr;
+    DirectWriteFontFileLoader *m_directWriteFontFileLoader  = nullptr;
+};
+
+
+#endif // directwrite && direct2d
+
+
+QWindowsFontEngineData::~QWindowsFontEngineData()
+{
+    if (hdc)
+        DeleteDC(hdc);
+
+#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
+    if (directWriteGdiInterop)
+        directWriteGdiInterop->Release();
+    if (directWriteFactory)
+        directWriteFactory->Release();
+#endif
+}
+
+QWindowsFontDatabaseBase::QWindowsFontDatabaseBase()
+{
+}
+
+QWindowsFontDatabaseBase::~QWindowsFontDatabaseBase()
+{
+}
+
+typedef QSharedPointer<QWindowsFontEngineData> QWindowsFontEngineDataPtr;
+typedef QThreadStorage<QWindowsFontEngineDataPtr> FontEngineThreadLocalData;
+Q_GLOBAL_STATIC(FontEngineThreadLocalData, fontEngineThreadLocalData)
+
+QSharedPointer<QWindowsFontEngineData> QWindowsFontDatabaseBase::data()
+{
+    FontEngineThreadLocalData *data = fontEngineThreadLocalData();
+    if (!data->hasLocalData())
+        data->setLocalData(QSharedPointer<QWindowsFontEngineData>::create());
+
+    if (!init(data->localData()))
+        qCWarning(lcQpaFonts) << "Cannot initialize common font database data";
+
+    return data->localData();
+}
+
+bool QWindowsFontDatabaseBase::init(QSharedPointer<QWindowsFontEngineData> d)
+{
+#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
+    if (!d->directWriteFactory) {
+        createDirectWriteFactory(&d->directWriteFactory);
+        if (!d->directWriteFactory)
+            return false;
+    }
+    if (!d->directWriteGdiInterop) {
+        const HRESULT  hr = d->directWriteFactory->GetGdiInterop(&d->directWriteGdiInterop);
+        if (FAILED(hr)) {
+            qErrnoWarning("%s: GetGdiInterop failed", __FUNCTION__);
+            return false;
+        }
+    }
+#else
+    Q_UNUSED(d);
+#endif // directwrite && direct2d
+    return true;
+}
+
+#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
+void QWindowsFontDatabaseBase::createDirectWriteFactory(IDWriteFactory **factory)
+{
+    *factory = nullptr;
+    IUnknown *result = nullptr;
+
+#  if QT_CONFIG(directwrite3)
+    DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory5), &result);
+
+    if (result == nullptr)
+        DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory3), &result);
+#  endif
+
+    if (result == nullptr)
+        DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory2), &result);
+
+    if (result == nullptr) {
+        if (FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &result))) {
+            qErrnoWarning("DWriteCreateFactory failed");
+            return;
+        }
+    }
+
+    *factory = static_cast<IDWriteFactory *>(result);
+}
+#endif // directwrite && direct2d
+
+int QWindowsFontDatabaseBase::defaultVerticalDPI()
+{
+    return 96;
+}
+
+LOGFONT QWindowsFontDatabaseBase::fontDefToLOGFONT(const QFontDef &request, const QString &faceName)
+{
+    LOGFONT lf;
+    memset(&lf, 0, sizeof(LOGFONT));
+
+    lf.lfHeight = -qRound(request.pixelSize);
+    lf.lfWidth                = 0;
+    lf.lfEscapement        = 0;
+    lf.lfOrientation        = 0;
+    if (request.weight == QFont::Normal)
+        lf.lfWeight = FW_DONTCARE;
+    else
+        lf.lfWeight = request.weight;
+    lf.lfItalic         = request.style != QFont::StyleNormal;
+    lf.lfCharSet        = DEFAULT_CHARSET;
+
+    int strat = OUT_DEFAULT_PRECIS;
+    if (request.styleStrategy & QFont::PreferBitmap) {
+        strat = OUT_RASTER_PRECIS;
+    } else if (request.styleStrategy & QFont::PreferDevice) {
+        strat = OUT_DEVICE_PRECIS;
+    } else if (request.styleStrategy & QFont::PreferOutline) {
+        strat = OUT_OUTLINE_PRECIS;
+    } else if (request.styleStrategy & QFont::ForceOutline) {
+        strat = OUT_TT_ONLY_PRECIS;
+    }
+
+    lf.lfOutPrecision   = strat;
+
+    int qual = DEFAULT_QUALITY;
+
+    if (request.styleStrategy & QFont::PreferMatch)
+        qual = DRAFT_QUALITY;
+    else if (request.styleStrategy & QFont::PreferQuality)
+        qual = PROOF_QUALITY;
+
+    if (request.styleStrategy & QFont::PreferAntialias) {
+        qual = (request.styleStrategy & QFont::NoSubpixelAntialias) == 0
+            ? CLEARTYPE_QUALITY : ANTIALIASED_QUALITY;
+    } else if (request.styleStrategy & QFont::NoAntialias) {
+        qual = NONANTIALIASED_QUALITY;
+    } else if ((request.styleStrategy & QFont::NoSubpixelAntialias) && data()->clearTypeEnabled) {
+        qual = ANTIALIASED_QUALITY;
+    }
+
+    lf.lfQuality        = qual;
+
+    lf.lfClipPrecision  = CLIP_DEFAULT_PRECIS;
+
+    int hint = FF_DONTCARE;
+    switch (request.styleHint) {
+        case QFont::Helvetica:
+            hint = FF_SWISS;
+            break;
+        case QFont::Times:
+            hint = FF_ROMAN;
+            break;
+        case QFont::Courier:
+            hint = FF_MODERN;
+            break;
+        case QFont::OldEnglish:
+            hint = FF_DECORATIVE;
+            break;
+        case QFont::System:
+            hint = FF_MODERN;
+            break;
+        default:
+            break;
+    }
+
+    lf.lfPitchAndFamily = DEFAULT_PITCH | hint;
+
+    QString fam = faceName;
+    if (fam.isEmpty())
+        fam = request.families.first();
+    if (Q_UNLIKELY(fam.size() >= LF_FACESIZE)) {
+        qCritical("%s: Family name '%s' is too long.", __FUNCTION__, qPrintable(fam));
+        fam.truncate(LF_FACESIZE - 1);
+    }
+
+    memcpy(lf.lfFaceName, fam.utf16(), fam.size() * sizeof(wchar_t));
+
+    return lf;
+}
+
+QFont QWindowsFontDatabaseBase::LOGFONT_to_QFont(const LOGFONT& logFont, int verticalDPI_In)
+{
+    if (verticalDPI_In <= 0)
+        verticalDPI_In = defaultVerticalDPI();
+    QFont qFont(QString::fromWCharArray(logFont.lfFaceName));
+    qFont.setItalic(logFont.lfItalic);
+    if (logFont.lfWeight != FW_DONTCARE)
+        qFont.setWeight(QFont::Weight(logFont.lfWeight));
+    const qreal logFontHeight = qAbs(logFont.lfHeight);
+    qFont.setPointSizeF(logFontHeight * 72.0 / qreal(verticalDPI_In));
+    qFont.setUnderline(logFont.lfUnderline);
+    qFont.setOverline(false);
+    qFont.setStrikeOut(logFont.lfStrikeOut);
+    return qFont;
+}
+
+// ### fixme Qt 6 (QTBUG-58610): See comment at QWindowsFontDatabase::systemDefaultFont()
+HFONT QWindowsFontDatabaseBase::systemFont()
+{
+    static const auto stock_sysfont =
+        reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
+    return stock_sysfont;
+}
+
+QFont QWindowsFontDatabaseBase::systemDefaultFont()
+{
+    // Qt 6: Obtain default GUI font (typically "Segoe UI, 9pt", see QTBUG-58610)
+    NONCLIENTMETRICS ncm = {};
+    ncm.cbSize = sizeof(ncm);
+    SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0, defaultVerticalDPI());
+    const QFont systemFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont);
+    qCDebug(lcQpaFonts) << __FUNCTION__ << systemFont;
+    return systemFont;
+}
+
+void QWindowsFontDatabaseBase::invalidate()
+{
+#if QT_CONFIG(directwrite)
+    m_fontFileLoader.reset(nullptr);
+#endif
+}
+
+#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
+IDWriteFontFace *QWindowsFontDatabaseBase::createDirectWriteFace(const QByteArray &fontData)
+{
+    QList<IDWriteFontFace *> faces = createDirectWriteFaces(fontData, false);
+    Q_ASSERT(faces.size() <= 1);
+
+    return faces.isEmpty() ? nullptr : faces.first();
+}
+
+QList<IDWriteFontFace *> QWindowsFontDatabaseBase::createDirectWriteFaces(const QByteArray &fontData,
+                                                                          bool queryVariations) const
+{
+    QList<IDWriteFontFace *> ret;
+    QSharedPointer<QWindowsFontEngineData> fontEngineData = data();
+    if (fontEngineData->directWriteFactory == nullptr) {
+        qCWarning(lcQpaFonts) << "DirectWrite factory not created in QWindowsFontDatabaseBase::createDirectWriteFace()";
+        return ret;
+    }
+
+    if (m_fontFileLoader == nullptr)
+        m_fontFileLoader.reset(new QCustomFontFileLoader(fontEngineData->directWriteFactory));
+
+    m_fontFileLoader->addKey(fontData);
+
+    IDWriteFontFile *fontFile = nullptr;
+    const void *key = fontData.data();
+
+    HRESULT hres = fontEngineData->directWriteFactory->CreateCustomFontFileReference(&key,
+                                                                                     sizeof(void *),
+                                                                                     m_fontFileLoader->loader(),
+                                                                                     &fontFile);
+    if (FAILED(hres)) {
+        qErrnoWarning(hres, "%s: CreateCustomFontFileReference failed", __FUNCTION__);
+        return ret;
+    }
+
+    BOOL isSupportedFontType;
+    DWRITE_FONT_FILE_TYPE fontFileType;
+    DWRITE_FONT_FACE_TYPE fontFaceType;
+    UINT32 numberOfFaces;
+    fontFile->Analyze(&isSupportedFontType, &fontFileType, &fontFaceType, &numberOfFaces);
+    if (!isSupportedFontType) {
+        fontFile->Release();
+        return ret;
+    }
+
+#if QT_CONFIG(directwrite3)
+    IDWriteFactory5 *factory5 = nullptr;
+    if (queryVariations && SUCCEEDED(fontEngineData->directWriteFactory->QueryInterface(__uuidof(IDWriteFactory5),
+                                                                                        reinterpret_cast<void **>(&factory5)))) {
+
+        IDWriteFontSetBuilder1 *builder;
+        if (SUCCEEDED(factory5->CreateFontSetBuilder(&builder))) {
+            if (SUCCEEDED(builder->AddFontFile(fontFile))) {
+                IDWriteFontSet *fontSet;
+                if (SUCCEEDED(builder->CreateFontSet(&fontSet))) {
+                    int count = fontSet->GetFontCount();
+                    qCDebug(lcQpaFonts) << "Found" << count << "variations in font file";
+                    for (int i = 0; i < count; ++i) {
+                        IDWriteFontFaceReference *ref;
+                        if (SUCCEEDED(fontSet->GetFontFaceReference(i, &ref))) {
+                            IDWriteFontFace3 *face;
+                            if (SUCCEEDED(ref->CreateFontFace(&face))) {
+                                ret.append(face);
+                            }
+                            ref->Release();
+                        }
+                    }
+                    fontSet->Release();
+                }
+            }
+
+            builder->Release();
+        }
+
+        factory5->Release();
+    }
+#else
+    Q_UNUSED(queryVariations);
+#endif
+
+    // ### Currently no support for .ttc, but we could easily return a list here.
+    if (ret.isEmpty()) {
+        IDWriteFontFace *directWriteFontFace = nullptr;
+        hres = fontEngineData->directWriteFactory->CreateFontFace(fontFaceType,
+                                                                  1,
+                                                                  &fontFile,
+                                                                  0,
+                                                                  DWRITE_FONT_SIMULATIONS_NONE,
+                                                                  &directWriteFontFace);
+        if (FAILED(hres)) {
+            qErrnoWarning(hres, "%s: CreateFontFace failed", __FUNCTION__);
+            fontFile->Release();
+            return ret;
+        } else {
+            ret.append(directWriteFontFace);
+        }
+    }
+
+    fontFile->Release();
+
+    return ret;
+}
+#endif // directwrite && direct2d
+
+QFontEngine *QWindowsFontDatabaseBase::fontEngine(const QFontDef &fontDef, void *handle)
+{
+    // This function was apparently not used before, and probably isn't now either,
+    // call the base implementation which just prints that it's not supported.
+    return QPlatformFontDatabase::fontEngine(fontDef, handle);
+}
+
+QFontEngine *QWindowsFontDatabaseBase::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
+{
+    QFontEngine *fontEngine = nullptr;
+
+#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d)
+    QSharedPointer<QWindowsFontEngineData> fontEngineData = data();
+    if (fontEngineData->directWriteFactory == nullptr)
+        return nullptr;
+
+    IDWriteFontFace * directWriteFontFace = createDirectWriteFace(fontData);
+    if (directWriteFontFace == nullptr)
+        return nullptr;
+
+    fontEngine = new QWindowsFontEngineDirectWrite(directWriteFontFace,
+                                                   pixelSize,
+                                                   fontEngineData);
+
+    // Get font family from font data
+    EmbeddedFont font(fontData);
+    font.updateFromOS2Table(fontEngine);
+    fontEngine->fontDef.families = QStringList(font.familyName());
+    fontEngine->fontDef.hintingPreference = hintingPreference;
+
+    directWriteFontFace->Release();
+#else // directwrite && direct2d
+    Q_UNUSED(fontData);
+    Q_UNUSED(pixelSize);
+    Q_UNUSED(hintingPreference);
+#endif
+
+    return fontEngine;
+}
+
+QString QWindowsFontDatabaseBase::familyForStyleHint(QFont::StyleHint styleHint)
+{
+    switch (styleHint) {
+    case QFont::Times:
+        return QStringLiteral("Times New Roman");
+    case QFont::Courier:
+        return QStringLiteral("Courier New");
+    case QFont::Monospace:
+        return QStringLiteral("Courier New");
+    case QFont::Cursive:
+        return QStringLiteral("Comic Sans MS");
+    case QFont::Fantasy:
+        return QStringLiteral("Impact");
+    case QFont::Decorative:
+        return QStringLiteral("Old English");
+    case QFont::Helvetica:
+        return QStringLiteral("Arial");
+    case QFont::System:
+    default:
+        break;
+    }
+    return QStringLiteral("Tahoma");
+}
+
+// Creation functions
+
+static const char *other_tryFonts[] = {
+    "Arial",
+    "MS UI Gothic",
+    "Gulim",
+    "SimSun",
+    "PMingLiU",
+    "Arial Unicode MS",
+    0
+};
+
+static const char *jp_tryFonts [] = {
+    "Yu Gothic UI",
+    "MS UI Gothic",
+    "Arial",
+    "Gulim",
+    "SimSun",
+    "PMingLiU",
+    "Arial Unicode MS",
+    0
+};
+
+static const char *ch_CN_tryFonts [] = {
+    "SimSun",
+    "Arial",
+    "PMingLiU",
+    "Gulim",
+    "MS UI Gothic",
+    "Arial Unicode MS",
+    0
+};
+
+static const char *ch_TW_tryFonts [] = {
+    "PMingLiU",
+    "Arial",
+    "SimSun",
+    "Gulim",
+    "MS UI Gothic",
+    "Arial Unicode MS",
+    0
+};
+
+static const char *kr_tryFonts[] = {
+    "Gulim",
+    "Arial",
+    "PMingLiU",
+    "SimSun",
+    "MS UI Gothic",
+    "Arial Unicode MS",
+    0
+};
+
+static const char **tryFonts = nullptr;
+
+QStringList QWindowsFontDatabaseBase::extraTryFontsForFamily(const QString &family)
+{
+    QStringList result;
+    if (!QFontDatabase::writingSystems(family).contains(QFontDatabase::Symbol)) {
+        if (!tryFonts) {
+            LANGID lid = GetUserDefaultLangID();
+            switch (lid&0xff) {
+            case LANG_CHINESE: // Chinese
+                if ( lid == 0x0804 || lid == 0x1004) // China mainland and Singapore
+                    tryFonts = ch_CN_tryFonts;
+                else
+                    tryFonts = ch_TW_tryFonts; // Taiwan, Hong Kong and Macau
+                break;
+            case LANG_JAPANESE:
+                tryFonts = jp_tryFonts;
+                break;
+            case LANG_KOREAN:
+                tryFonts = kr_tryFonts;
+                break;
+            default:
+                tryFonts = other_tryFonts;
+                break;
+            }
+        }
+        const QStringList families = QFontDatabase::families();
+        const char **tf = tryFonts;
+        while (tf && *tf) {
+            // QTBUG-31689, family might be an English alias for a localized font name.
+            const QString family = QString::fromLatin1(*tf);
+            if (families.contains(family) || QFontDatabase::hasFamily(family))
+                result << family;
+            ++tf;
+        }
+    }
+    result.append(QStringLiteral("Segoe UI Emoji"));
+    result.append(QStringLiteral("Segoe UI Symbol"));
+    return result;
+}
+
+QFontDef QWindowsFontDatabaseBase::sanitizeRequest(QFontDef request) const
+{
+    QFontDef req = request;
+    const QString fam = request.families.front();
+    if (fam.isEmpty())
+        req.families[0] = QStringLiteral("MS Sans Serif");
+
+    if (fam == "MS Sans Serif"_L1) {
+        int height = -qRound(request.pixelSize);
+        // MS Sans Serif has bearing problems in italic, and does not scale
+        if (request.style == QFont::StyleItalic || (height > 18 && height != 24))
+            req.families[0] = QStringLiteral("Arial");
+    }
+
+    if (!(request.styleStrategy & QFont::StyleStrategy::PreferBitmap) && fam == u"Courier")
+        req.families[0] = QStringLiteral("Courier New");
+    return req;
+}
+
+QT_END_NAMESPACE
diff --git a/qtbase/src/network/kernel/qdnslookup_win.cpp b/qtbase/src/network/kernel/qdnslookup_win.cpp
index 72d5ae5c86..5410edab88 100644
--- a/qtbase/src/network/kernel/qdnslookup_win.cpp
+++ b/qtbase/src/network/kernel/qdnslookup_win.cpp
@@ -65,35 +65,101 @@ QT_BEGIN_NAMESPACE
 
 void QDnsLookupRunnable::query(QDnsLookupReply *reply)
 {
-    // Perform DNS query.
-    alignas(DNS_ADDR_ARRAY) uchar dnsAddresses[sizeof(DNS_ADDR_ARRAY) + sizeof(DNS_ADDR)];
-    DNS_QUERY_REQUEST request = {};
-    request.Version = 1;
-    request.QueryName = reinterpret_cast<const wchar_t *>(requestName.constData());
-    request.QueryType = requestType;
-    request.QueryOptions = DNS_QUERY_STANDARD | DNS_QUERY_TREAT_AS_FQDN;
-
-    if (!nameserver.isNull()) {
-        memset(dnsAddresses, 0, sizeof(dnsAddresses));
-        request.pDnsServerList = new (dnsAddresses) DNS_ADDR_ARRAY;
-        auto addr = new (request.pDnsServerList->AddrArray) DNS_ADDR[1];
-        auto sa = new (addr[0].MaxSa) sockaddr;
-        request.pDnsServerList->MaxCount = sizeof(dnsAddresses);
-        request.pDnsServerList->AddrCount = 1;
-        // ### setting port 53 seems to cause some systems to fail
-        setSockaddr(sa, nameserver, port == DnsPort ? 0 : port);
-        request.pDnsServerList->Family = sa->sa_family;
+    typedef BOOL (WINAPI *DnsQueryExFunc) (PDNS_QUERY_REQUEST, PDNS_QUERY_RESULT, PDNS_QUERY_CANCEL);
+    static DnsQueryExFunc myDnsQueryEx = 
+        (DnsQueryExFunc)::GetProcAddress(::GetModuleHandle(L"Dnsapi"), "DnsQueryEx");
+
+    PDNS_RECORD ptrStart = nullptr;
+
+    if (myDnsQueryEx)
+    {
+        // Perform DNS query.
+        alignas(DNS_ADDR_ARRAY) uchar dnsAddresses[sizeof(DNS_ADDR_ARRAY) + sizeof(DNS_ADDR)];
+        DNS_QUERY_REQUEST request = {};
+        request.Version = 1;
+        request.QueryName = reinterpret_cast<const wchar_t *>(requestName.constData());
+        request.QueryType = requestType;
+        request.QueryOptions = DNS_QUERY_STANDARD | DNS_QUERY_TREAT_AS_FQDN;
+
+        if (!nameserver.isNull()) {
+            memset(dnsAddresses, 0, sizeof(dnsAddresses));
+            request.pDnsServerList = new (dnsAddresses) DNS_ADDR_ARRAY;
+            auto addr = new (request.pDnsServerList->AddrArray) DNS_ADDR[1];
+            auto sa = new (addr[0].MaxSa) sockaddr;
+            request.pDnsServerList->MaxCount = sizeof(dnsAddresses);
+            request.pDnsServerList->AddrCount = 1;
+            // ### setting port 53 seems to cause some systems to fail
+            setSockaddr(sa, nameserver, port == DnsPort ? 0 : port);
+            request.pDnsServerList->Family = sa->sa_family;
+        }
+
+        DNS_QUERY_RESULT results = {};
+        results.Version = 1;
+        const DNS_STATUS status = myDnsQueryEx(&request, &results, nullptr);
+        if (status >= DNS_ERROR_RCODE_FORMAT_ERROR && status <= DNS_ERROR_RCODE_LAST)
+            return reply->makeDnsRcodeError(status - DNS_ERROR_RCODE_FORMAT_ERROR + 1);
+        else if (status == ERROR_TIMEOUT)
+            return reply->makeTimeoutError();
+        else if (status != ERROR_SUCCESS)
+            return reply->makeResolverSystemError(status);
+
+        ptrStart = results.pQueryRecords;
     }
+    else
+    {
+        // Perform DNS query.
+        PDNS_RECORD dns_records = 0;
+        QByteArray requestNameUTF8 = requestName.toUtf8();
+        const QString requestNameUtf16 = QString::fromUtf8(requestNameUTF8.data(), requestNameUTF8.size());
+        IP4_ARRAY srvList;
+        memset(&srvList, 0, sizeof(IP4_ARRAY));
+        if (!nameserver.isNull()) {
+            if (nameserver.protocol() == QAbstractSocket::IPv4Protocol) {
+                // The below code is referenced from: http://support.microsoft.com/kb/831226
+                srvList.AddrCount = 1;
+                srvList.AddrArray[0] = htonl(nameserver.toIPv4Address());
+            } else if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) {
+                // For supporting IPv6 nameserver addresses, we'll need to switch
+                // from DnsQuey() to DnsQueryEx() as it supports passing an IPv6
+                // address in the nameserver list
+                qWarning("%s", "IPv6 addresses for nameservers are currently not supported");
+                reply->error = QDnsLookup::ResolverError;
+                reply->errorString = tr("IPv6 addresses for nameservers are currently not supported");
+                return;
+            }
+        }
+        const DNS_STATUS status = DnsQuery_W(reinterpret_cast<const wchar_t*>(requestNameUtf16.utf16()), requestType, DNS_QUERY_STANDARD, &srvList, &dns_records, NULL);
+        switch (status) {
+        case ERROR_SUCCESS:
+            break;
+        case DNS_ERROR_RCODE_FORMAT_ERROR:
+            reply->error = QDnsLookup::InvalidRequestError;
+            reply->errorString = tr("Server could not process query");
+            return;
+        case DNS_ERROR_RCODE_SERVER_FAILURE:
+        case DNS_ERROR_RCODE_NOT_IMPLEMENTED:
+            reply->error = QDnsLookup::ServerFailureError;
+            reply->errorString = tr("Server failure");
+            return;
+        case DNS_ERROR_RCODE_NAME_ERROR:
+            reply->error = QDnsLookup::NotFoundError;
+            reply->errorString = tr("Non existent domain");
+            return;
+        case DNS_ERROR_RCODE_REFUSED:
+            reply->error = QDnsLookup::ServerRefusedError;
+            reply->errorString = tr("Server refused to answer");
+            return;
+        default:
+            reply->error = QDnsLookup::InvalidReplyError;
+            reply->errorString = QSystemError(status, QSystemError::NativeError).toString();
+            return;
+        }
 
-    DNS_QUERY_RESULT results = {};
-    results.Version = 1;
-    const DNS_STATUS status = DnsQueryEx(&request, &results, nullptr);
-    if (status >= DNS_ERROR_RCODE_FORMAT_ERROR && status <= DNS_ERROR_RCODE_LAST)
-        return reply->makeDnsRcodeError(status - DNS_ERROR_RCODE_FORMAT_ERROR + 1);
-    else if (status == ERROR_TIMEOUT)
-        return reply->makeTimeoutError();
-    else if (status != ERROR_SUCCESS)
-        return reply->makeResolverSystemError(status);
+        ptrStart = dns_records;
+    }
+
+    if (!ptrStart)
+        return;
 
     QStringView lastEncodedName;
     QString cachedDecodedName;
@@ -105,9 +171,9 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply)
     auto extractMaybeCachedHost = [&](QStringView name) -> const QString & {
         return lastEncodedName == name ? cachedDecodedName : extractAndCacheHost(name);
     };
-
+    
     // Extract results.
-    for (PDNS_RECORD ptr = results.pQueryRecords; ptr != NULL; ptr = ptr->pNext) {
+    for (PDNS_RECORD ptr = ptrStart; ptr != NULL; ptr = ptr->pNext) {
         // warning: always assign name to the record before calling extractXxxHost() again
         const QString &name = extractMaybeCachedHost(ptr->pName);
         if (ptr->wType == QDnsLookup::A) {
@@ -170,7 +236,7 @@ void QDnsLookupRunnable::query(QDnsLookupReply *reply)
         }
     }
 
-    DnsRecordListFree(results.pQueryRecords, DnsFreeRecordList);
+    DnsRecordListFree(ptrStart, DnsFreeRecordList);
 }
 
 QT_END_NAMESPACE
diff --git a/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp b/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp
index 026e81cb0c..2708424c30 100644
--- a/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp
+++ b/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp
@@ -58,43 +58,73 @@ public:
 } // namespace ABI
 #endif // HAS_UI_VIEW_SETTINGS
 
+// based on SDL approach
+// see SDL_windows_gaming_input.c, SDL_windows.c
+
+void * WIN_LoadComBaseFunction(const char *name)
+{
+    static bool s_bLoaded = false;
+    static HMODULE s_hComBase = NULL;
+   
+    if (!s_bLoaded) {
+       s_hComBase = ::LoadLibraryEx(L"combase.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
+       s_bLoaded = true;
+    }
+    if (s_hComBase) {
+        return (void*)::GetProcAddress(s_hComBase, name);
+    } else {
+        return NULL;
+    }
+}
+
 QT_BEGIN_NAMESPACE
 
 // Return tablet mode, note: Does not work for GetDesktopWindow().
 bool qt_windowsIsTabletMode(HWND hwnd)
 {
-    bool result = false;
-
-    const wchar_t uiViewSettingsId[] = L"Windows.UI.ViewManagement.UIViewSettings";
-    HSTRING_HEADER uiViewSettingsIdRefHeader;
-    HSTRING uiViewSettingsIdHs = nullptr;
-    const auto uiViewSettingsIdLen = UINT32(sizeof(uiViewSettingsId) / sizeof(uiViewSettingsId[0]) - 1);
-    if (FAILED(WindowsCreateStringReference(uiViewSettingsId, uiViewSettingsIdLen, &uiViewSettingsIdRefHeader, &uiViewSettingsIdHs)))
-        return false;
-
-    IUIViewSettingsInterop *uiViewSettingsInterop = nullptr;
-    // __uuidof(IUIViewSettingsInterop);
-    const GUID uiViewSettingsInteropRefId = {0x3694dbf9, 0x8f68, 0x44be,{0x8f, 0xf5, 0x19, 0x5c, 0x98, 0xed, 0xe8, 0xa6}};
-
-    HRESULT hr = RoGetActivationFactory(uiViewSettingsIdHs, uiViewSettingsInteropRefId,
-                                                   reinterpret_cast<void **>(&uiViewSettingsInterop));
-    if (FAILED(hr))
-        return false;
-
-    //  __uuidof(ABI::Windows::UI::ViewManagement::IUIViewSettings);
-    const GUID uiViewSettingsRefId = {0xc63657f6, 0x8850, 0x470d,{0x88, 0xf8, 0x45, 0x5e, 0x16, 0xea, 0x2c, 0x26}};
-    ABI::Windows::UI::ViewManagement::IUIViewSettings *viewSettings = nullptr;
-    hr = uiViewSettingsInterop->GetForWindow(hwnd, uiViewSettingsRefId,
-                                             reinterpret_cast<void **>(&viewSettings));
-    if (SUCCEEDED(hr)) {
-        ABI::Windows::UI::ViewManagement::UserInteractionMode currentMode;
-        hr = viewSettings->get_UserInteractionMode(&currentMode);
-        if (SUCCEEDED(hr))
-            result = currentMode == 1; // Touch, 1
-        viewSettings->Release();
+    typedef HRESULT (WINAPI *WindowsCreateStringReference_t)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING* string);
+    typedef HRESULT (WINAPI *RoGetActivationFactory_t)(HSTRING activatableClassId, REFIID iid, void** factory);
+
+    WindowsCreateStringReference_t WindowsCreateStringReferenceFunc = (WindowsCreateStringReference_t)WIN_LoadComBaseFunction("WindowsCreateStringReference");
+    RoGetActivationFactory_t RoGetActivationFactoryFunc = (RoGetActivationFactory_t)WIN_LoadComBaseFunction("RoGetActivationFactory");
+
+    if (WindowsCreateStringReferenceFunc && RoGetActivationFactoryFunc)
+    {
+        bool result = false;
+
+        const wchar_t uiViewSettingsId[] = L"Windows.UI.ViewManagement.UIViewSettings";
+        HSTRING_HEADER uiViewSettingsIdRefHeader;
+        HSTRING uiViewSettingsIdHs = nullptr;
+        const auto uiViewSettingsIdLen = UINT32(sizeof(uiViewSettingsId) / sizeof(uiViewSettingsId[0]) - 1);
+        if (FAILED(WindowsCreateStringReferenceFunc(uiViewSettingsId, uiViewSettingsIdLen, &uiViewSettingsIdRefHeader, &uiViewSettingsIdHs)))
+            return false;
+
+        IUIViewSettingsInterop *uiViewSettingsInterop = nullptr;
+        // __uuidof(IUIViewSettingsInterop);
+        const GUID uiViewSettingsInteropRefId = {0x3694dbf9, 0x8f68, 0x44be,{0x8f, 0xf5, 0x19, 0x5c, 0x98, 0xed, 0xe8, 0xa6}};
+
+        HRESULT hr = RoGetActivationFactoryFunc(uiViewSettingsIdHs, uiViewSettingsInteropRefId,
+                                                       reinterpret_cast<void **>(&uiViewSettingsInterop));
+        if (FAILED(hr))
+            return false;
+
+        //  __uuidof(ABI::Windows::UI::ViewManagement::IUIViewSettings);
+        const GUID uiViewSettingsRefId = {0xc63657f6, 0x8850, 0x470d,{0x88, 0xf8, 0x45, 0x5e, 0x16, 0xea, 0x2c, 0x26}};
+        ABI::Windows::UI::ViewManagement::IUIViewSettings *viewSettings = nullptr;
+        hr = uiViewSettingsInterop->GetForWindow(hwnd, uiViewSettingsRefId,
+                                                 reinterpret_cast<void **>(&viewSettings));
+        if (SUCCEEDED(hr)) {
+            ABI::Windows::UI::ViewManagement::UserInteractionMode currentMode;
+            hr = viewSettings->get_UserInteractionMode(&currentMode);
+            if (SUCCEEDED(hr))
+                result = currentMode == 1; // Touch, 1
+            viewSettings->Release();
+        }
+        uiViewSettingsInterop->Release();
+        return result;
     }
-    uiViewSettingsInterop->Release();
-    return result;
+
+    return false;
 }
 
 QT_END_NAMESPACE
diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
index c363b85cb3..9b24191b26 100644
--- a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
+++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
@@ -44,6 +44,7 @@
 #include <QtCore/qsysinfo.h>
 #include <QtCore/qscopedpointer.h>
 #include <QtCore/quuid.h>
+#include <QtCore/private/qsystemlibrary_p.h>
 #include <QtCore/private/qwinregistry_p.h>
 #if QT_CONFIG(cpp_winrt)
 #  include <QtCore/private/qfactorycacheregistration_p.h>
@@ -121,6 +122,77 @@ static inline bool sessionManagerInteractionBlocked()
 static inline bool sessionManagerInteractionBlocked() { return false; }
 #endif
 
+/*!
+    \class QWindowsUser32DLL
+    \brief Struct that contains dynamically resolved symbols of User32.dll.
+    The stub libraries shipped with the MinGW compiler miss some of the
+    functions. They need to be retrieved dynamically.
+    In addition, touch-related functions are available only from Windows onwards.
+    These need to resolved dynamically for Q_CC_MSVC as well.
+    \sa QWindowsShell32DLL
+    \internal
+*/
+
+void QWindowsUser32DLL::init()
+{
+    QSystemLibrary library(QStringLiteral("user32"));
+    setProcessDPIAware = (SetProcessDPIAware)library.resolve("SetProcessDPIAware");
+    setProcessDpiAwarenessContext = (SetProcessDpiAwarenessContext)library.resolve("SetProcessDpiAwarenessContext");
+    getThreadDpiAwarenessContext = (GetThreadDpiAwarenessContext)library.resolve("GetThreadDpiAwarenessContext");
+    isValidDpiAwarenessContext = (IsValidDpiAwarenessContext)library.resolve("IsValidDpiAwarenessContext");
+    areDpiAwarenessContextsEqual = (AreDpiAwarenessContextsEqual)library.resolve("AreDpiAwarenessContextsEqual");
+
+    addClipboardFormatListener = (AddClipboardFormatListener)library.resolve("AddClipboardFormatListener");
+    removeClipboardFormatListener = (RemoveClipboardFormatListener)library.resolve("RemoveClipboardFormatListener");
+
+    getDisplayAutoRotationPreferences = (GetDisplayAutoRotationPreferences)library.resolve("GetDisplayAutoRotationPreferences");
+    setDisplayAutoRotationPreferences = (SetDisplayAutoRotationPreferences)library.resolve("SetDisplayAutoRotationPreferences");
+
+    if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8) {
+        enableMouseInPointer = (EnableMouseInPointer)library.resolve("EnableMouseInPointer");
+        getPointerType = (GetPointerType)library.resolve("GetPointerType");
+        getPointerInfo = (GetPointerInfo)library.resolve("GetPointerInfo");
+        getPointerDeviceRects = (GetPointerDeviceRects)library.resolve("GetPointerDeviceRects");
+        getPointerTouchInfo = (GetPointerTouchInfo)library.resolve("GetPointerTouchInfo");
+        getPointerFrameTouchInfo = (GetPointerFrameTouchInfo)library.resolve("GetPointerFrameTouchInfo");
+        getPointerFrameTouchInfoHistory = (GetPointerFrameTouchInfoHistory)library.resolve("GetPointerFrameTouchInfoHistory");
+        getPointerPenInfo = (GetPointerPenInfo)library.resolve("GetPointerPenInfo");
+        getPointerPenInfoHistory = (GetPointerPenInfoHistory)library.resolve("GetPointerPenInfoHistory");
+        skipPointerFrameMessages = (SkipPointerFrameMessages)library.resolve("SkipPointerFrameMessages");
+    }
+    
+    if (QOperatingSystemVersion::current()
+        >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 14393)) {
+        adjustWindowRectExForDpi = (AdjustWindowRectExForDpi)library.resolve("AdjustWindowRectExForDpi");
+        enableNonClientDpiScaling = (EnableNonClientDpiScaling)library.resolve("EnableNonClientDpiScaling");
+        getWindowDpiAwarenessContext = (GetWindowDpiAwarenessContext)library.resolve("GetWindowDpiAwarenessContext");
+        getAwarenessFromDpiAwarenessContext = (GetAwarenessFromDpiAwarenessContext)library.resolve("GetAwarenessFromDpiAwarenessContext");
+        systemParametersInfoForDpi = (SystemParametersInfoForDpi)library.resolve("SystemParametersInfoForDpi");
+        getDpiForWindow = (GetDpiForWindow)library.resolve("GetDpiForWindow");
+        getSystemMetricsForDpi = (GetSystemMetricsForDpi)library.resolve("GetSystemMetricsForDpi");
+    }
+}
+
+bool QWindowsUser32DLL::supportsPointerApi()
+{
+    return enableMouseInPointer && getPointerType && getPointerInfo && getPointerDeviceRects
+            && getPointerTouchInfo && getPointerFrameTouchInfo && getPointerFrameTouchInfoHistory
+            && getPointerPenInfo && getPointerPenInfoHistory && skipPointerFrameMessages;
+}
+
+void QWindowsShcoreDLL::init()
+{
+    if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows8_1)
+        return;
+    QSystemLibrary library(QStringLiteral("SHCore"));
+    getProcessDpiAwareness = (GetProcessDpiAwareness)library.resolve("GetProcessDpiAwareness");
+    setProcessDpiAwareness = (SetProcessDpiAwareness)library.resolve("SetProcessDpiAwareness");
+    getDpiForMonitor = (GetDpiForMonitor)library.resolve("GetDpiForMonitor");
+}
+
+QWindowsUser32DLL QWindowsContext::user32dll;
+QWindowsShcoreDLL QWindowsContext::shcoredll;
+
 QWindowsContext *QWindowsContext::m_instance = nullptr;
 
 /*!
@@ -164,6 +236,9 @@ bool QWindowsContextPrivate::m_v2DpiAware = false;
 QWindowsContextPrivate::QWindowsContextPrivate()
     : m_oleInitializeResult(OleInitialize(nullptr))
 {
+    QWindowsContext::user32dll.init();
+    QWindowsContext::shcoredll.init();
+    
     if (m_pointerHandler.touchDevice() || m_mouseHandler.touchDevice())
         m_systemInfo |= QWindowsContext::SI_SupportsTouch;
     m_displayContext = GetDC(nullptr);
@@ -282,6 +357,12 @@ bool QWindowsContext::initPointer(unsigned integrationOptions)
     if (integrationOptions & QWindowsIntegration::DontUseWMPointer)
         return false;
 
+    if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows8)
+        return false;
+
+    if (!QWindowsContext::user32dll.supportsPointerApi())
+        return false;
+
     d->m_systemInfo |= QWindowsContext::SI_SupportsPointer;
     return true;
 }
@@ -352,41 +433,58 @@ void QWindowsContext::setDetectAltGrModifier(bool a)
 [[nodiscard]] static inline QtWindows::DpiAwareness
     dpiAwarenessContextToQtDpiAwareness(DPI_AWARENESS_CONTEXT context)
 {
-    // IsValidDpiAwarenessContext() will handle the NULL pointer case.
-    if (!IsValidDpiAwarenessContext(context))
+    if (QWindowsContext::user32dll.isValidDpiAwarenessContext && QWindowsContext::user32dll.areDpiAwarenessContextsEqual)
+    {
+        // IsValidDpiAwarenessContext() will handle the NULL pointer case.
+        if (!QWindowsContext::user32dll.isValidDpiAwarenessContext(context))
+            return QtWindows::DpiAwareness::Invalid;
+        if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED))
+            return QtWindows::DpiAwareness::Unaware_GdiScaled;
+        if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
+            return QtWindows::DpiAwareness::PerMonitorVersion2;
+        if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
+            return QtWindows::DpiAwareness::PerMonitor;
+        if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_SYSTEM_AWARE))
+            return QtWindows::DpiAwareness::System;
+        if (QWindowsContext::user32dll.areDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE))
+            return QtWindows::DpiAwareness::Unaware;
+
         return QtWindows::DpiAwareness::Invalid;
-    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED))
-        return QtWindows::DpiAwareness::Unaware_GdiScaled;
-    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
-        return QtWindows::DpiAwareness::PerMonitorVersion2;
-    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
-        return QtWindows::DpiAwareness::PerMonitor;
-    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_SYSTEM_AWARE))
-        return QtWindows::DpiAwareness::System;
-    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE))
-        return QtWindows::DpiAwareness::Unaware;
-    return QtWindows::DpiAwareness::Invalid;
+    }
+    
+    return QtWindows::DpiAwareness::Unaware; // Windows 7
 }
 
 QtWindows::DpiAwareness QWindowsContext::windowDpiAwareness(HWND hwnd)
 {
     if (!hwnd)
         return QtWindows::DpiAwareness::Invalid;
-    const auto context = GetWindowDpiAwarenessContext(hwnd);
-    return dpiAwarenessContextToQtDpiAwareness(context);
+
+    if (QWindowsContext::user32dll.getWindowDpiAwarenessContext)
+    {
+        const auto context = QWindowsContext::user32dll.getWindowDpiAwarenessContext(hwnd);
+        return dpiAwarenessContextToQtDpiAwareness(context);
+    }
+
+    return dpiAwarenessContextToQtDpiAwareness(DPI_AWARENESS_CONTEXT_UNAWARE);
 }
 
 QtWindows::DpiAwareness QWindowsContext::processDpiAwareness()
 {
-    // Although we have GetDpiAwarenessContextForProcess(), however,
-    // it's only available on Win10 1903+, which is a little higher
-    // than Qt's minimum supported version (1809), so we can't use it.
-    // Luckily, MS docs said GetThreadDpiAwarenessContext() will also
-    // return the default DPI_AWARENESS_CONTEXT for the process if
-    // SetThreadDpiAwarenessContext() was never called. So we can use
-    // it as an equivalent.
-    const auto context = GetThreadDpiAwarenessContext();
-    return dpiAwarenessContextToQtDpiAwareness(context);
+    if (QWindowsContext::user32dll.getThreadDpiAwarenessContext)
+    {
+        // Although we have GetDpiAwarenessContextForProcess(), however,
+        // it's only available on Win10 1903+, which is a little higher
+        // than Qt's minimum supported version (1809), so we can't use it.
+        // Luckily, MS docs said GetThreadDpiAwarenessContext() will also
+        // return the default DPI_AWARENESS_CONTEXT for the process if
+        // SetThreadDpiAwarenessContext() was never called. So we can use
+        // it as an equivalent.
+        const auto context = QWindowsContext::user32dll.getThreadDpiAwarenessContext();
+        return dpiAwarenessContextToQtDpiAwareness(context);
+    }
+
+    return dpiAwarenessContextToQtDpiAwareness(DPI_AWARENESS_CONTEXT_UNAWARE);
 }
 
 [[nodiscard]] static inline DPI_AWARENESS_CONTEXT
@@ -445,23 +543,29 @@ bool QWindowsContext::setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwarenes
     if (processDpiAwareness() == dpiAwareness)
         return true;
     const auto context = qtDpiAwarenessToDpiAwarenessContext(dpiAwareness);
-    if (!IsValidDpiAwarenessContext(context)) {
-        qCWarning(lcQpaWindow) << dpiAwareness << "is not supported by current system.";
-        return false;
-    }
-    if (!SetProcessDpiAwarenessContext(context)) {
-        qCWarning(lcQpaWindow).noquote().nospace()
-            << "SetProcessDpiAwarenessContext() failed: "
-            << QSystemError::windowsString()
-            << "\nQt's default DPI awareness context is "
-            << "DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2. If you know what you "
-            << "are doing, you can overwrite this default using qt.conf "
-            << "(https://doc.qt.io/qt-6/highdpi.html#configuring-windows).";
-        return false;
+
+    if (QWindowsContext::user32dll.isValidDpiAwarenessContext && QWindowsContext::user32dll.setProcessDpiAwarenessContext)
+    {
+        if (!QWindowsContext::user32dll.isValidDpiAwarenessContext(context)) {
+            qCWarning(lcQpaWindow) << dpiAwareness << "is not supported by current system.";
+            return false;
+        }
+        if (!QWindowsContext::user32dll.setProcessDpiAwarenessContext(context)) {
+            qCWarning(lcQpaWindow).noquote().nospace()
+                << "SetProcessDpiAwarenessContext() failed: "
+                << QSystemError::windowsString()
+                << "\nQt's default DPI awareness context is "
+                << "DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2. If you know what you "
+                << "are doing, you can overwrite this default using qt.conf "
+                << "(https://doc.qt.io/qt-6/highdpi.html#configuring-windows).";
+            return false;
+        }
+        QWindowsContextPrivate::m_v2DpiAware
+            = processDpiAwareness() == QtWindows::DpiAwareness::PerMonitorVersion2;
+        return true;
     }
-    QWindowsContextPrivate::m_v2DpiAware
-        = processDpiAwareness() == QtWindows::DpiAwareness::PerMonitorVersion2;
-    return true;
+
+    return false; // Windows 7
 }
 
 bool QWindowsContext::isDarkMode()
@@ -891,8 +995,8 @@ void QWindowsContext::forceNcCalcSize(HWND hwnd)
 bool QWindowsContext::systemParametersInfo(unsigned action, unsigned param, void *out,
                                            unsigned dpi)
 {
-    const BOOL result = dpi != 0
-        ? SystemParametersInfoForDpi(action, param, out, 0, dpi)
+    const BOOL result = (QWindowsContext::user32dll.systemParametersInfoForDpi != nullptr && dpi != 0)
+        ? QWindowsContext::user32dll.systemParametersInfoForDpi(action, param, out, 0, dpi)
         : SystemParametersInfo(action, param, out, 0);
     return result == TRUE;
 }
@@ -979,8 +1083,8 @@ static inline bool isInputMessage(UINT m)
 static bool enableNonClientDpiScaling(HWND hwnd)
 {
     bool result = false;
-    if (QWindowsContext::windowDpiAwareness(hwnd) == QtWindows::DpiAwareness::PerMonitor) {
-        result = EnableNonClientDpiScaling(hwnd) != FALSE;
+    if (QWindowsContext::user32dll.enableNonClientDpiScaling && QWindowsContext::windowDpiAwareness(hwnd) == QtWindows::DpiAwareness::PerMonitor) {
+        result = QWindowsContext::user32dll.enableNonClientDpiScaling(hwnd) != FALSE;
         if (!result) {
             const DWORD errorCode = GetLastError();
             qErrnoWarning(int(errorCode), "EnableNonClientDpiScaling() failed for HWND %p (%lu)",
diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp.orig b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp.orig
new file mode 100644
index 0000000000..c363b85cb3
--- /dev/null
+++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp.orig
@@ -0,0 +1,1612 @@
+// Copyright (C) 2013 Samuel Gaist <samuel.gaist@edeltech.ch>
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindowscontext.h"
+#include "qwindowsintegration.h"
+#include "qwindowswindow.h"
+#include "qwindowskeymapper.h"
+#include "qwindowsnativeinterface.h"
+#include "qwindowsmousehandler.h"
+#include "qwindowspointerhandler.h"
+#include "qtwindowsglobal.h"
+#include "qwindowsmenu.h"
+#include "qwindowsmimeregistry.h"
+#include "qwindowsinputcontext.h"
+#if QT_CONFIG(tabletevent)
+#  include "qwindowstabletsupport.h"
+#endif
+#include "qwindowstheme.h"
+#include <private/qguiapplication_p.h>
+#if QT_CONFIG(accessibility)
+#  include "uiautomation/qwindowsuiaaccessibility.h"
+#endif
+#if QT_CONFIG(sessionmanager)
+# include <private/qsessionmanager_p.h>
+# include "qwindowssessionmanager.h"
+#endif
+#include "qwindowsscreen.h"
+#include "qwindowstheme.h"
+
+#include <QtGui/qwindow.h>
+#include <qpa/qwindowsysteminterface.h>
+#include <qpa/qwindowsysteminterface_p.h>
+#include <qpa/qplatformnativeinterface.h>
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qopenglcontext.h>
+#include <QtGui/qpointingdevice.h>
+
+#include <QtCore/qset.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qstringlist.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qsysinfo.h>
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/quuid.h>
+#include <QtCore/private/qwinregistry_p.h>
+#if QT_CONFIG(cpp_winrt)
+#  include <QtCore/private/qfactorycacheregistration_p.h>
+#endif
+#include <QtCore/private/qsystemerror_p.h>
+
+#include <QtGui/private/qwindowsguieventdispatcher_p.h>
+#include <QtGui/private/qwindowsthemecache_p.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <windowsx.h>
+#include <dbt.h>
+#include <wtsapi32.h>
+#include <shellscalingapi.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window")
+Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events")
+Q_LOGGING_CATEGORY(lcQpaGl, "qt.qpa.gl")
+Q_LOGGING_CATEGORY(lcQpaMime, "qt.qpa.mime")
+Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods")
+Q_LOGGING_CATEGORY(lcQpaDialogs, "qt.qpa.dialogs")
+Q_LOGGING_CATEGORY(lcQpaMenus, "qt.qpa.menus")
+Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet")
+Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility")
+Q_LOGGING_CATEGORY(lcQpaUiAutomation, "qt.qpa.uiautomation")
+Q_LOGGING_CATEGORY(lcQpaTrayIcon, "qt.qpa.trayicon")
+Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen")
+
+int QWindowsContext::verbose = 0;
+
+#if !defined(LANG_SYRIAC)
+#    define LANG_SYRIAC 0x5a
+#endif
+
+static inline bool useRTL_Extensions()
+{
+    // Since the IsValidLanguageGroup/IsValidLocale functions always return true on
+    // Vista, check the Keyboard Layouts for enabling RTL.
+    if (const int nLayouts = GetKeyboardLayoutList(0, nullptr)) {
+        QScopedArrayPointer<HKL> lpList(new HKL[nLayouts]);
+        GetKeyboardLayoutList(nLayouts, lpList.data());
+        for (int i = 0; i < nLayouts; ++i) {
+            switch (PRIMARYLANGID((quintptr)lpList[i])) {
+            case LANG_ARABIC:
+            case LANG_HEBREW:
+            case LANG_FARSI:
+            case LANG_SYRIAC:
+                return true;
+            default:
+                break;
+            }
+        }
+    }
+    return false;
+}
+
+#if QT_CONFIG(sessionmanager)
+static inline QWindowsSessionManager *platformSessionManager()
+{
+    auto *guiPrivate = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(qApp));
+    auto *managerPrivate = static_cast<QSessionManagerPrivate*>(QObjectPrivate::get(guiPrivate->session_manager));
+    return static_cast<QWindowsSessionManager *>(managerPrivate->platformSessionManager);
+}
+
+static inline bool sessionManagerInteractionBlocked()
+{
+    return platformSessionManager()->isInteractionBlocked();
+}
+#else // QT_CONFIG(sessionmanager)
+static inline bool sessionManagerInteractionBlocked() { return false; }
+#endif
+
+QWindowsContext *QWindowsContext::m_instance = nullptr;
+
+/*!
+    \class QWindowsContext
+    \brief Singleton container for all relevant information.
+
+    Holds state information formerly stored in \c qapplication_win.cpp.
+
+    \internal
+*/
+
+struct QWindowsContextPrivate {
+    QWindowsContextPrivate();
+
+    unsigned m_systemInfo = 0;
+    QSet<QString> m_registeredWindowClassNames;
+    QWindowsContext::HandleBaseWindowHash m_windows;
+    HDC m_displayContext = nullptr;
+    int m_defaultDPI = 96;
+    QWindowsKeyMapper m_keyMapper;
+    QWindowsMouseHandler m_mouseHandler;
+    QWindowsPointerHandler m_pointerHandler;
+    QWindowsMimeRegistry m_mimeConverter;
+    QWindowsScreenManager m_screenManager;
+    QSharedPointer<QWindowCreationContext> m_creationContext;
+#if QT_CONFIG(tabletevent)
+    QScopedPointer<QWindowsTabletSupport> m_tabletSupport;
+#endif
+    const HRESULT m_oleInitializeResult;
+    QWindow *m_lastActiveWindow = nullptr;
+    bool m_asyncExpose = false;
+    HPOWERNOTIFY m_powerNotification = nullptr;
+    HWND m_powerDummyWindow = nullptr;
+    static bool m_darkMode;
+    static bool m_v2DpiAware;
+};
+
+bool QWindowsContextPrivate::m_darkMode = false;
+bool QWindowsContextPrivate::m_v2DpiAware = false;
+
+QWindowsContextPrivate::QWindowsContextPrivate()
+    : m_oleInitializeResult(OleInitialize(nullptr))
+{
+    if (m_pointerHandler.touchDevice() || m_mouseHandler.touchDevice())
+        m_systemInfo |= QWindowsContext::SI_SupportsTouch;
+    m_displayContext = GetDC(nullptr);
+    m_defaultDPI = GetDeviceCaps(m_displayContext, LOGPIXELSY);
+    if (useRTL_Extensions()) {
+        m_systemInfo |= QWindowsContext::SI_RTL_Extensions;
+        m_keyMapper.setUseRTLExtensions(true);
+    }
+    m_darkMode = QWindowsTheme::queryDarkMode();
+    if (FAILED(m_oleInitializeResult)) {
+       qWarning() << "QWindowsContext: OleInitialize() failed: "
+           << QSystemError::windowsComString(m_oleInitializeResult);
+    }
+}
+
+QWindowsContext::QWindowsContext() :
+    d(new QWindowsContextPrivate)
+{
+#ifdef Q_CC_MSVC
+#    pragma warning( disable : 4996 )
+#endif
+    m_instance = this;
+    // ### FIXME: Remove this once the logging system has other options of configurations.
+    const QByteArray bv = qgetenv("QT_QPA_VERBOSE");
+    if (!bv.isEmpty())
+        QLoggingCategory::setFilterRules(QString::fromLocal8Bit(bv));
+}
+
+QWindowsContext::~QWindowsContext()
+{
+#if QT_CONFIG(tabletevent)
+    d->m_tabletSupport.reset(); // Destroy internal window before unregistering classes.
+#endif
+
+    if (d->m_powerNotification)
+        UnregisterPowerSettingNotification(d->m_powerNotification);
+
+    if (d->m_powerDummyWindow)
+        DestroyWindow(d->m_powerDummyWindow);
+
+    unregisterWindowClasses();
+    if (d->m_oleInitializeResult == S_OK || d->m_oleInitializeResult == S_FALSE) {
+#ifdef QT_USE_FACTORY_CACHE_REGISTRATION
+        detail::QWinRTFactoryCacheRegistration::clearAllCaches();
+#endif
+        OleUninitialize();
+    }
+
+    d->m_screenManager.clearScreens(); // Order: Potentially calls back to the windows.
+    if (d->m_displayContext)
+        ReleaseDC(nullptr, d->m_displayContext);
+    m_instance = nullptr;
+}
+
+bool QWindowsContext::initTouch()
+{
+    return initTouch(QWindowsIntegration::instance()->options());
+}
+
+bool QWindowsContext::initTouch(unsigned integrationOptions)
+{
+    if (d->m_systemInfo & QWindowsContext::SI_SupportsTouch)
+        return true;
+    const bool usePointerHandler = (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) != 0;
+    auto touchDevice = usePointerHandler ? d->m_pointerHandler.touchDevice() : d->m_mouseHandler.touchDevice();
+    if (touchDevice.isNull()) {
+        const bool mouseEmulation =
+            (integrationOptions & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch) == 0;
+        touchDevice = QWindowsPointerHandler::createTouchDevice(mouseEmulation);
+    }
+    if (touchDevice.isNull())
+        return false;
+    d->m_pointerHandler.setTouchDevice(touchDevice);
+    d->m_mouseHandler.setTouchDevice(touchDevice);
+    QWindowSystemInterface::registerInputDevice(touchDevice.data());
+
+    d->m_systemInfo |= QWindowsContext::SI_SupportsTouch;
+
+    // A touch device was plugged while the app is running. Register all windows for touch.
+    registerTouchWindows();
+
+    return true;
+}
+
+void QWindowsContext::registerTouchWindows()
+{
+    if (QGuiApplicationPrivate::is_app_running
+        && (d->m_systemInfo & QWindowsContext::SI_SupportsTouch) != 0) {
+        for (QWindowsWindow *w : std::as_const(d->m_windows))
+            w->registerTouchWindow();
+    }
+}
+
+bool QWindowsContext::initTablet()
+{
+#if QT_CONFIG(tabletevent)
+    d->m_tabletSupport.reset(QWindowsTabletSupport::create());
+    return true;
+#else
+    return false;
+#endif
+}
+
+bool QWindowsContext::disposeTablet()
+{
+#if QT_CONFIG(tabletevent)
+    d->m_tabletSupport.reset();
+    return true;
+#else
+    return false;
+#endif
+}
+
+bool QWindowsContext::initPointer(unsigned integrationOptions)
+{
+    if (integrationOptions & QWindowsIntegration::DontUseWMPointer)
+        return false;
+
+    d->m_systemInfo |= QWindowsContext::SI_SupportsPointer;
+    return true;
+}
+
+extern "C" LRESULT QT_WIN_CALLBACK qWindowsPowerWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+    if (message != WM_POWERBROADCAST || wParam != PBT_POWERSETTINGCHANGE)
+        return DefWindowProc(hwnd, message, wParam, lParam);
+
+    static bool initialized = false; // ignore the initial change
+    if (!initialized) {
+        initialized = true;
+        return DefWindowProc(hwnd, message, wParam, lParam);
+    }
+
+    auto setting = reinterpret_cast<const POWERBROADCAST_SETTING *>(lParam);
+    if (setting) {
+        auto data = reinterpret_cast<const DWORD *>(&setting->Data);
+        if (*data == 1) {
+            // Repaint the windows when returning from sleeping display mode.
+            const auto tlw = QGuiApplication::topLevelWindows();
+            for (auto w : tlw) {
+                if (w->isVisible() && w->windowState() != Qt::WindowMinimized) {
+                    if (auto tw = QWindowsWindow::windowsWindowOf(w)) {
+                        if (HWND hwnd = tw->handle()) {
+                            InvalidateRect(hwnd, nullptr, false);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+bool QWindowsContext::initPowerNotificationHandler()
+{
+    if (d->m_powerNotification)
+        return false;
+
+    d->m_powerDummyWindow = createDummyWindow(QStringLiteral("PowerDummyWindow"), L"QtPowerDummyWindow", qWindowsPowerWindowProc);
+    if (!d->m_powerDummyWindow)
+        return false;
+
+    d->m_powerNotification = RegisterPowerSettingNotification(d->m_powerDummyWindow, &GUID_MONITOR_POWER_ON, DEVICE_NOTIFY_WINDOW_HANDLE);
+    if (!d->m_powerNotification) {
+        DestroyWindow(d->m_powerDummyWindow);
+        d->m_powerDummyWindow = nullptr;
+        return false;
+    }
+    return true;
+}
+
+void QWindowsContext::setTabletAbsoluteRange(int a)
+{
+#if QT_CONFIG(tabletevent)
+    QWindowsTabletSupport::setAbsoluteRange(a);
+#else
+    Q_UNUSED(a);
+#endif
+}
+
+void QWindowsContext::setDetectAltGrModifier(bool a)
+{
+    d->m_keyMapper.setDetectAltGrModifier(a);
+}
+
+[[nodiscard]] static inline QtWindows::DpiAwareness
+    dpiAwarenessContextToQtDpiAwareness(DPI_AWARENESS_CONTEXT context)
+{
+    // IsValidDpiAwarenessContext() will handle the NULL pointer case.
+    if (!IsValidDpiAwarenessContext(context))
+        return QtWindows::DpiAwareness::Invalid;
+    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED))
+        return QtWindows::DpiAwareness::Unaware_GdiScaled;
+    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
+        return QtWindows::DpiAwareness::PerMonitorVersion2;
+    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
+        return QtWindows::DpiAwareness::PerMonitor;
+    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_SYSTEM_AWARE))
+        return QtWindows::DpiAwareness::System;
+    if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE))
+        return QtWindows::DpiAwareness::Unaware;
+    return QtWindows::DpiAwareness::Invalid;
+}
+
+QtWindows::DpiAwareness QWindowsContext::windowDpiAwareness(HWND hwnd)
+{
+    if (!hwnd)
+        return QtWindows::DpiAwareness::Invalid;
+    const auto context = GetWindowDpiAwarenessContext(hwnd);
+    return dpiAwarenessContextToQtDpiAwareness(context);
+}
+
+QtWindows::DpiAwareness QWindowsContext::processDpiAwareness()
+{
+    // Although we have GetDpiAwarenessContextForProcess(), however,
+    // it's only available on Win10 1903+, which is a little higher
+    // than Qt's minimum supported version (1809), so we can't use it.
+    // Luckily, MS docs said GetThreadDpiAwarenessContext() will also
+    // return the default DPI_AWARENESS_CONTEXT for the process if
+    // SetThreadDpiAwarenessContext() was never called. So we can use
+    // it as an equivalent.
+    const auto context = GetThreadDpiAwarenessContext();
+    return dpiAwarenessContextToQtDpiAwareness(context);
+}
+
+[[nodiscard]] static inline DPI_AWARENESS_CONTEXT
+    qtDpiAwarenessToDpiAwarenessContext(QtWindows::DpiAwareness dpiAwareness)
+{
+    switch (dpiAwareness) {
+    case QtWindows::DpiAwareness::Invalid:
+        return nullptr;
+    case QtWindows::DpiAwareness::Unaware:
+        return DPI_AWARENESS_CONTEXT_UNAWARE;
+    case QtWindows::DpiAwareness::System:
+        return DPI_AWARENESS_CONTEXT_SYSTEM_AWARE;
+    case QtWindows::DpiAwareness::PerMonitor:
+        return DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE;
+    case QtWindows::DpiAwareness::PerMonitorVersion2:
+        return DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
+    case QtWindows::DpiAwareness::Unaware_GdiScaled:
+        return DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED;
+    }
+    return nullptr;
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug d, QtWindows::DpiAwareness dpiAwareness)
+{
+    const QDebugStateSaver saver(d);
+    QString message = u"QtWindows::DpiAwareness::"_s;
+    switch (dpiAwareness) {
+    case QtWindows::DpiAwareness::Invalid:
+        message += u"Invalid"_s;
+        break;
+    case QtWindows::DpiAwareness::Unaware:
+        message += u"Unaware"_s;
+        break;
+    case QtWindows::DpiAwareness::System:
+        message += u"System"_s;
+        break;
+    case QtWindows::DpiAwareness::PerMonitor:
+        message += u"PerMonitor"_s;
+        break;
+    case QtWindows::DpiAwareness::PerMonitorVersion2:
+        message += u"PerMonitorVersion2"_s;
+        break;
+    case QtWindows::DpiAwareness::Unaware_GdiScaled:
+        message += u"Unaware_GdiScaled"_s;
+        break;
+    }
+    d.nospace().noquote() << message;
+    return d;
+}
+#endif
+
+bool QWindowsContext::setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwareness)
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << dpiAwareness;
+    if (processDpiAwareness() == dpiAwareness)
+        return true;
+    const auto context = qtDpiAwarenessToDpiAwarenessContext(dpiAwareness);
+    if (!IsValidDpiAwarenessContext(context)) {
+        qCWarning(lcQpaWindow) << dpiAwareness << "is not supported by current system.";
+        return false;
+    }
+    if (!SetProcessDpiAwarenessContext(context)) {
+        qCWarning(lcQpaWindow).noquote().nospace()
+            << "SetProcessDpiAwarenessContext() failed: "
+            << QSystemError::windowsString()
+            << "\nQt's default DPI awareness context is "
+            << "DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2. If you know what you "
+            << "are doing, you can overwrite this default using qt.conf "
+            << "(https://doc.qt.io/qt-6/highdpi.html#configuring-windows).";
+        return false;
+    }
+    QWindowsContextPrivate::m_v2DpiAware
+        = processDpiAwareness() == QtWindows::DpiAwareness::PerMonitorVersion2;
+    return true;
+}
+
+bool QWindowsContext::isDarkMode()
+{
+    return QWindowsContextPrivate::m_darkMode;
+}
+
+QWindowsContext *QWindowsContext::instance()
+{
+    return m_instance;
+}
+
+unsigned QWindowsContext::systemInfo() const
+{
+    return d->m_systemInfo;
+}
+
+bool QWindowsContext::useRTLExtensions() const
+{
+    return d->m_keyMapper.useRTLExtensions();
+}
+
+QPlatformKeyMapper *QWindowsContext::keyMapper() const
+{
+    return &d->m_keyMapper;
+}
+
+QWindowsContext::HandleBaseWindowHash &QWindowsContext::windows()
+{
+    return d->m_windows;
+}
+
+QSharedPointer<QWindowCreationContext> QWindowsContext::setWindowCreationContext(const QSharedPointer<QWindowCreationContext> &ctx)
+{
+    const QSharedPointer<QWindowCreationContext> old = d->m_creationContext;
+    d->m_creationContext = ctx;
+    return old;
+}
+
+QSharedPointer<QWindowCreationContext> QWindowsContext::windowCreationContext() const
+{
+    return d->m_creationContext;
+}
+
+int QWindowsContext::defaultDPI() const
+{
+    return d->m_defaultDPI;
+}
+
+HDC QWindowsContext::displayContext() const
+{
+    return d->m_displayContext;
+}
+
+QWindow *QWindowsContext::keyGrabber() const
+{
+    return d->m_keyMapper.keyGrabber();
+}
+
+void QWindowsContext::setKeyGrabber(QWindow *w)
+{
+    d->m_keyMapper.setKeyGrabber(w);
+}
+
+QString QWindowsContext::classNamePrefix()
+{
+    static QString result;
+    if (result.isEmpty()) {
+        QTextStream str(&result);
+        str << "Qt" << QT_VERSION_MAJOR << QT_VERSION_MINOR << QT_VERSION_PATCH;
+        if (QLibraryInfo::isDebugBuild())
+            str << 'd';
+#ifdef QT_NAMESPACE
+#  define xstr(s) str(s)
+#  define str(s) #s
+        str << xstr(QT_NAMESPACE);
+#  undef str
+#  undef xstr
+#endif
+    }
+    return result;
+}
+
+// Window class registering code (from qapplication_win.cpp)
+
+QString QWindowsContext::registerWindowClass(const QWindow *w)
+{
+    Q_ASSERT(w);
+    const Qt::WindowFlags flags = w->flags();
+    const Qt::WindowFlags type = flags & Qt::WindowType_Mask;
+    // Determine style and icon.
+    uint style = CS_DBLCLKS;
+    bool icon = true;
+    // The following will not set CS_OWNDC for any widget window, even if it contains a
+    // QOpenGLWidget or QQuickWidget later on. That cannot be detected at this stage.
+    if (w->surfaceType() == QSurface::OpenGLSurface || (flags & Qt::MSWindowsOwnDC))
+        style |= CS_OWNDC;
+    if (!(flags & Qt::NoDropShadowWindowHint)
+        && (type == Qt::Popup || w->property("_q_windowsDropShadow").toBool())) {
+        style |= CS_DROPSHADOW;
+    }
+    switch (type) {
+    case Qt::Tool:
+    case Qt::ToolTip:
+    case Qt::Popup:
+        style |= CS_SAVEBITS; // Save/restore background
+        icon = false;
+        break;
+    case Qt::Dialog:
+        if (!(flags & Qt::WindowSystemMenuHint))
+            icon = false; // QTBUG-2027, dialogs without system menu.
+        break;
+    }
+    // Create a unique name for the flag combination
+    QString cname = classNamePrefix();
+    cname += "QWindow"_L1;
+    switch (type) {
+    case Qt::Tool:
+        cname += "Tool"_L1;
+        break;
+    case Qt::ToolTip:
+        cname += "ToolTip"_L1;
+        break;
+    case Qt::Popup:
+        cname += "Popup"_L1;
+        break;
+    default:
+        break;
+    }
+    if (style & CS_DROPSHADOW)
+        cname += "DropShadow"_L1;
+    if (style & CS_SAVEBITS)
+        cname += "SaveBits"_L1;
+    if (style & CS_OWNDC)
+        cname += "OwnDC"_L1;
+    if (icon)
+        cname += "Icon"_L1;
+
+    return registerWindowClass(cname, qWindowsWndProc, style, nullptr, icon);
+}
+
+QString QWindowsContext::registerWindowClass(QString cname,
+                                             WNDPROC proc,
+                                             unsigned style,
+                                             HBRUSH brush,
+                                             bool icon)
+{
+    // since multiple Qt versions can be used in one process
+    // each one has to have window class names with a unique name
+    // The first instance gets the unmodified name; if the class
+    // has already been registered by another instance of Qt then
+    // add a UUID. The check needs to be performed for each name
+    // in case new message windows are added (QTBUG-81347).
+    // Note: GetClassInfo() returns != 0 when a class exists.
+    const auto appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));
+    WNDCLASS wcinfo;
+    const bool classExists = GetClassInfo(appInstance, reinterpret_cast<LPCWSTR>(cname.utf16()), &wcinfo) != FALSE
+        && wcinfo.lpfnWndProc != proc;
+
+    if (classExists)
+        cname += QUuid::createUuid().toString();
+
+    if (d->m_registeredWindowClassNames.contains(cname))        // already registered in our list
+        return cname;
+
+    WNDCLASSEX wc;
+    wc.cbSize       = sizeof(WNDCLASSEX);
+    wc.style        = style;
+    wc.lpfnWndProc  = proc;
+    wc.cbClsExtra   = 0;
+    wc.cbWndExtra   = 0;
+    wc.hInstance    = appInstance;
+    wc.hCursor      = nullptr;
+    wc.hbrBackground = brush;
+    if (icon) {
+        wc.hIcon = static_cast<HICON>(LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE));
+        if (wc.hIcon) {
+            int sw = GetSystemMetrics(SM_CXSMICON);
+            int sh = GetSystemMetrics(SM_CYSMICON);
+            wc.hIconSm = static_cast<HICON>(LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, sw, sh, 0));
+        } else {
+            wc.hIcon = static_cast<HICON>(LoadImage(nullptr, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED));
+            wc.hIconSm = nullptr;
+        }
+    } else {
+        wc.hIcon    = nullptr;
+        wc.hIconSm  = nullptr;
+    }
+
+    wc.lpszMenuName  = nullptr;
+    wc.lpszClassName = reinterpret_cast<LPCWSTR>(cname.utf16());
+    ATOM atom = RegisterClassEx(&wc);
+    if (!atom)
+        qErrnoWarning("QApplication::regClass: Registering window class '%s' failed.",
+                      qPrintable(cname));
+
+    d->m_registeredWindowClassNames.insert(cname);
+    qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << ' ' << cname
+        << " style=0x" << Qt::hex << style << Qt::dec
+        << " brush=" << brush << " icon=" << icon << " atom=" << atom;
+    return cname;
+}
+
+void QWindowsContext::unregisterWindowClasses()
+{
+    const auto appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));
+
+    for (const QString &name : std::as_const(d->m_registeredWindowClassNames)) {
+        if (!UnregisterClass(reinterpret_cast<LPCWSTR>(name.utf16()), appInstance) && QWindowsContext::verbose)
+            qErrnoWarning("UnregisterClass failed for '%s'", qPrintable(name));
+    }
+    d->m_registeredWindowClassNames.clear();
+}
+
+int QWindowsContext::screenDepth() const
+{
+    return GetDeviceCaps(d->m_displayContext, BITSPIXEL);
+}
+
+void QWindowsContext::addWindow(HWND hwnd, QWindowsWindow *w)
+{
+    d->m_windows.insert(hwnd, w);
+}
+
+void QWindowsContext::removeWindow(HWND hwnd)
+{
+    const HandleBaseWindowHash::iterator it = d->m_windows.find(hwnd);
+    if (it != d->m_windows.end()) {
+        if (d->m_keyMapper.keyGrabber() == it.value()->window())
+            d->m_keyMapper.setKeyGrabber(nullptr);
+        d->m_windows.erase(it);
+    }
+}
+
+QWindowsWindow *QWindowsContext::findPlatformWindow(const QWindowsMenuBar *mb) const
+{
+    for (auto it = d->m_windows.cbegin(), end = d->m_windows.cend(); it != end; ++it) {
+        if ((*it)->menuBar() == mb)
+            return *it;
+    }
+    return nullptr;
+}
+
+QWindowsWindow *QWindowsContext::findPlatformWindow(HWND hwnd) const
+{
+    return d->m_windows.value(hwnd);
+}
+
+QWindowsWindow *QWindowsContext::findClosestPlatformWindow(HWND hwnd) const
+{
+    QWindowsWindow *window = d->m_windows.value(hwnd);
+
+    // Requested hwnd may also be a child of a platform window in case of embedded native windows.
+    // Find the closest parent that has a platform window.
+    if (!window) {
+        for (HWND w = hwnd; w; w = GetParent(w)) {
+            window = d->m_windows.value(w);
+            if (window)
+                break;
+        }
+    }
+
+    return window;
+}
+
+QWindow *QWindowsContext::findWindow(HWND hwnd) const
+{
+    if (const QWindowsWindow *bw = findPlatformWindow(hwnd))
+            return bw->window();
+    return nullptr;
+}
+
+QWindow *QWindowsContext::windowUnderMouse() const
+{
+    return (d->m_systemInfo & QWindowsContext::SI_SupportsPointer) ?
+        d->m_pointerHandler.windowUnderMouse() : d->m_mouseHandler.windowUnderMouse();
+}
+
+void QWindowsContext::clearWindowUnderMouse()
+{
+    if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
+        d->m_pointerHandler.clearWindowUnderMouse();
+    else
+        d->m_mouseHandler.clearWindowUnderMouse();
+}
+
+/*!
+    \brief Find a child window at a screen point.
+
+    Deep search for a QWindow at global point, skipping non-owned
+    windows (accessibility?). Implemented using ChildWindowFromPointEx()
+    instead of (historically used) WindowFromPoint() to get a well-defined
+    behaviour for hidden/transparent windows.
+
+    \a cwex_flags are flags of ChildWindowFromPointEx().
+    \a parent is the parent window, pass GetDesktopWindow() for top levels.
+*/
+
+static inline bool findPlatformWindowHelper(const POINT &screenPoint, unsigned cwexFlags,
+                                            const QWindowsContext *context,
+                                            HWND *hwnd, QWindowsWindow **result)
+{
+    POINT point = screenPoint;
+    screenToClient(*hwnd, &point);
+    // Returns parent if inside & none matched.
+    const HWND child = ChildWindowFromPointEx(*hwnd, point, cwexFlags);
+    if (!child || child == *hwnd)
+        return false;
+    if (QWindowsWindow *window = context->findPlatformWindow(child)) {
+        *result = window;
+        *hwnd = child;
+        return true;
+    }
+    // QTBUG-40555: despite CWP_SKIPINVISIBLE, it is possible to hit on invisible
+    // full screen windows of other applications that have WS_EX_TRANSPARENT set
+    // (for example created by  screen sharing applications). In that case, try to
+    // find a Qt window by searching again with CWP_SKIPTRANSPARENT.
+    // Note that Qt 5 uses WS_EX_TRANSPARENT for Qt::WindowTransparentForInput
+    // as well.
+    if (!(cwexFlags & CWP_SKIPTRANSPARENT)
+        && (GetWindowLongPtr(child, GWL_EXSTYLE) & WS_EX_TRANSPARENT)) {
+        const HWND nonTransparentChild = ChildWindowFromPointEx(*hwnd, point, cwexFlags | CWP_SKIPTRANSPARENT);
+        if (!nonTransparentChild || nonTransparentChild == *hwnd)
+            return false;
+        if (QWindowsWindow *nonTransparentWindow = context->findPlatformWindow(nonTransparentChild)) {
+            *result = nonTransparentWindow;
+            *hwnd = nonTransparentChild;
+            return true;
+        }
+    }
+    *hwnd = child;
+    return true;
+}
+
+QWindowsWindow *QWindowsContext::findPlatformWindowAt(HWND parent,
+                                                          const QPoint &screenPointIn,
+                                                          unsigned cwex_flags) const
+{
+    QWindowsWindow *result = nullptr;
+    const POINT screenPoint = { screenPointIn.x(), screenPointIn.y() };
+    while (findPlatformWindowHelper(screenPoint, cwex_flags, this, &parent, &result)) {}
+    // QTBUG-40815: ChildWindowFromPointEx() can hit on special windows from
+    // screen recorder applications like ScreenToGif. Fall back to WindowFromPoint().
+    if (result == nullptr) {
+        if (const HWND window = WindowFromPoint(screenPoint))
+            result = findPlatformWindow(window);
+    }
+    return result;
+}
+
+bool QWindowsContext::isSessionLocked()
+{
+    bool result = false;
+    const DWORD sessionId = WTSGetActiveConsoleSessionId();
+    if (sessionId != 0xFFFFFFFF) {
+        LPTSTR buffer = nullptr;
+        DWORD size = 0;
+#if !defined(Q_CC_MINGW)
+        if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, sessionId,
+                                       WTSSessionInfoEx, &buffer, &size) == TRUE
+            && size > 0) {
+            const WTSINFOEXW *info = reinterpret_cast<WTSINFOEXW *>(buffer);
+            result = info->Level == 1 && info->Data.WTSInfoExLevel1.SessionFlags == WTS_SESSIONSTATE_LOCK;
+            WTSFreeMemory(buffer);
+        }
+#else   // MinGW as of 7.3 does not have WTSINFOEXW in wtsapi32.h
+        // Retrieve the flags which are at offset 16 due to padding for 32/64bit alike.
+        if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, sessionId,
+                                       WTS_INFO_CLASS(25), &buffer, &size) == TRUE
+            && size >= 20) {
+            const DWORD *p = reinterpret_cast<DWORD *>(buffer);
+            const DWORD level = *p;
+            const DWORD sessionFlags = *(p + 4);
+            result = level == 1 && sessionFlags == 1;
+            WTSFreeMemory(buffer);
+        }
+#endif // Q_CC_MINGW
+    }
+    return result;
+}
+
+QWindowsMimeRegistry &QWindowsContext::mimeConverter() const
+{
+    return d->m_mimeConverter;
+}
+
+QWindowsScreenManager &QWindowsContext::screenManager()
+{
+    return d->m_screenManager;
+}
+
+QWindowsTabletSupport *QWindowsContext::tabletSupport() const
+{
+#if QT_CONFIG(tabletevent)
+    return d->m_tabletSupport.data();
+#else
+    return 0;
+#endif
+}
+
+/*!
+    \brief Convenience to create a non-visible, message-only dummy
+    window for example used as clipboard watcher or for GL.
+*/
+
+HWND QWindowsContext::createDummyWindow(const QString &classNameIn,
+                                        const wchar_t *windowName,
+                                        WNDPROC wndProc, DWORD style)
+{
+    if (!wndProc)
+        wndProc = DefWindowProc;
+    QString className = registerWindowClass(classNamePrefix() + classNameIn, wndProc);
+    return CreateWindowEx(0, reinterpret_cast<LPCWSTR>(className.utf16()),
+                          windowName, style,
+                          CW_USEDEFAULT, CW_USEDEFAULT,
+                          CW_USEDEFAULT, CW_USEDEFAULT,
+                          HWND_MESSAGE, nullptr, static_cast<HINSTANCE>(GetModuleHandle(nullptr)), nullptr);
+}
+
+void QWindowsContext::forceNcCalcSize(HWND hwnd)
+{
+    // Force WM_NCCALCSIZE to adjust margin
+    SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
+                 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER);
+}
+
+bool QWindowsContext::systemParametersInfo(unsigned action, unsigned param, void *out,
+                                           unsigned dpi)
+{
+    const BOOL result = dpi != 0
+        ? SystemParametersInfoForDpi(action, param, out, 0, dpi)
+        : SystemParametersInfo(action, param, out, 0);
+    return result == TRUE;
+}
+
+bool QWindowsContext::systemParametersInfoForScreen(unsigned action, unsigned param, void *out,
+                                                    const QPlatformScreen *screen)
+{
+    return systemParametersInfo(action, param, out, screen ? unsigned(screen->logicalDpi().first) : 0u);
+}
+
+bool QWindowsContext::systemParametersInfoForWindow(unsigned action, unsigned param, void *out,
+                                                    const QPlatformWindow *win)
+{
+    return systemParametersInfoForScreen(action, param, out, win ? win->screen() : nullptr);
+}
+
+bool QWindowsContext::nonClientMetrics(NONCLIENTMETRICS *ncm, unsigned dpi)
+{
+    memset(ncm, 0, sizeof(NONCLIENTMETRICS));
+    ncm->cbSize = sizeof(NONCLIENTMETRICS);
+    return systemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm->cbSize, ncm, dpi);
+}
+
+bool QWindowsContext::nonClientMetricsForScreen(NONCLIENTMETRICS *ncm,
+                                                const QPlatformScreen *screen)
+{
+    const int dpi = screen ? qRound(screen->logicalDpi().first) : 0;
+    return nonClientMetrics(ncm, unsigned(dpi));
+}
+
+bool QWindowsContext::nonClientMetricsForWindow(NONCLIENTMETRICS *ncm, const QPlatformWindow *win)
+{
+    return nonClientMetricsForScreen(ncm, win ? win->screen() : nullptr);
+}
+
+static inline QWindowsInputContext *windowsInputContext()
+{
+    return qobject_cast<QWindowsInputContext *>(QWindowsIntegration::instance()->inputContext());
+}
+
+bool QWindowsContext::shouldHaveNonClientDpiScaling(const QWindow *window)
+{
+    // DPI aware V2 processes always have NonClientDpiScaling enabled.
+    if (QWindowsContextPrivate::m_v2DpiAware)
+        return true;
+
+    return window->isTopLevel()
+        && !window->property(QWindowsWindow::embeddedNativeParentHandleProperty).isValid()
+#if QT_CONFIG(opengl) // /QTBUG-62901, EnableNonClientDpiScaling has problems with GL
+        && (window->surfaceType() != QSurface::OpenGLSurface
+            || QOpenGLContext::openGLModuleType() != QOpenGLContext::LibGL)
+#endif
+       ;
+}
+
+static inline bool isInputMessage(UINT m)
+{
+    switch (m) {
+    case WM_IME_STARTCOMPOSITION:
+    case WM_IME_ENDCOMPOSITION:
+    case WM_IME_COMPOSITION:
+    case WM_INPUT:
+    case WM_TOUCH:
+    case WM_MOUSEHOVER:
+    case WM_MOUSELEAVE:
+    case WM_NCMOUSEHOVER:
+    case WM_NCMOUSELEAVE:
+    case WM_SIZING:
+    case WM_MOVING:
+    case WM_SYSCOMMAND:
+    case WM_COMMAND:
+    case WM_DWMNCRENDERINGCHANGED:
+    case WM_PAINT:
+        return true;
+    default:
+        break;
+    }
+    return (m >= WM_MOUSEFIRST && m <= WM_MOUSELAST)
+        || (m >= WM_NCMOUSEMOVE && m <= WM_NCXBUTTONDBLCLK)
+        || (m >= WM_KEYFIRST && m <= WM_KEYLAST);
+}
+
+// Note: This only works within WM_NCCREATE
+static bool enableNonClientDpiScaling(HWND hwnd)
+{
+    bool result = false;
+    if (QWindowsContext::windowDpiAwareness(hwnd) == QtWindows::DpiAwareness::PerMonitor) {
+        result = EnableNonClientDpiScaling(hwnd) != FALSE;
+        if (!result) {
+            const DWORD errorCode = GetLastError();
+            qErrnoWarning(int(errorCode), "EnableNonClientDpiScaling() failed for HWND %p (%lu)",
+                          hwnd, errorCode);
+        }
+    }
+    return result;
+}
+
+/*!
+     \brief Main windows procedure registered for windows.
+
+     \sa QWindowsGuiEventDispatcher
+*/
+
+bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
+                                  QtWindows::WindowsEventType et,
+                                  WPARAM wParam, LPARAM lParam,
+                                  LRESULT *result,
+                                  QWindowsWindow **platformWindowPtr)
+{
+    *result = 0;
+
+    MSG msg;
+    msg.hwnd = hwnd;         // re-create MSG structure
+    msg.message = message;   // time and pt fields ignored
+    msg.wParam = wParam;
+    msg.lParam = lParam;
+    msg.pt.x = msg.pt.y = 0;
+    if (et != QtWindows::CursorEvent && (et & (QtWindows::MouseEventFlag | QtWindows::NonClientEventFlag))) {
+        msg.pt.x = GET_X_LPARAM(lParam);
+        msg.pt.y = GET_Y_LPARAM(lParam);
+        // For non-client-area messages, these are screen coordinates (as expected
+        // in the MSG structure), otherwise they are client coordinates.
+        if (!(et & QtWindows::NonClientEventFlag)) {
+            clientToScreen(msg.hwnd, &msg.pt);
+        }
+    } else {
+        GetCursorPos(&msg.pt);
+    }
+
+    QWindowsWindow *platformWindow = findPlatformWindow(hwnd);
+    *platformWindowPtr = platformWindow;
+
+    // Run the native event filters. QTBUG-67095: Exclude input messages which are sent
+    // by QEventDispatcherWin32::processEvents()
+    if (!isInputMessage(msg.message) && filterNativeEvent(&msg, result))
+        return true;
+
+    if (platformWindow && filterNativeEvent(platformWindow->window(), &msg, result))
+        return true;
+
+    if (et & QtWindows::InputMethodEventFlag) {
+        QWindowsInputContext *windowsInputContext = ::windowsInputContext();
+        // Disable IME assuming this is a special implementation hooking into keyboard input.
+        // "Real" IME implementations should use a native event filter intercepting IME events.
+        if (!windowsInputContext) {
+            QWindowsInputContext::setWindowsImeEnabled(platformWindow, false);
+            return false;
+        }
+        switch (et) {
+        case QtWindows::InputMethodStartCompositionEvent:
+            return windowsInputContext->startComposition(hwnd);
+        case QtWindows::InputMethodCompositionEvent:
+            return windowsInputContext->composition(hwnd, lParam);
+        case QtWindows::InputMethodEndCompositionEvent:
+            return windowsInputContext->endComposition(hwnd);
+        case QtWindows::InputMethodRequest:
+            return windowsInputContext->handleIME_Request(wParam, lParam, result);
+        default:
+            break;
+        }
+    } // InputMethodEventFlag
+
+    switch (et) {
+    case QtWindows::GestureEvent:
+        if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
+            return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateGestureEvent(platformWindow->window(), hwnd, et, msg, result);
+        break;
+    case QtWindows::InputMethodOpenCandidateWindowEvent:
+    case QtWindows::InputMethodCloseCandidateWindowEvent:
+        // TODO: Release/regrab mouse if a popup has mouse grab.
+        return false;
+    case QtWindows::DestroyEvent:
+        if (platformWindow && !platformWindow->testFlag(QWindowsWindow::WithinDestroy)) {
+            qWarning() << "External WM_DESTROY received for " << platformWindow->window()
+                       << ", parent: " << platformWindow->window()->parent()
+                       << ", transient parent: " << platformWindow->window()->transientParent();
+            }
+        return false;
+    case QtWindows::ClipboardEvent:
+        return false;
+    case QtWindows::CursorEvent: // Sent to windows that do not have capture (see QTBUG-58590).
+        if (QWindowsCursor::hasOverrideCursor()) {
+            QWindowsCursor::enforceOverrideCursor();
+            return true;
+        }
+        break;
+    case QtWindows::UnknownEvent:
+        return false;
+    case QtWindows::AccessibleObjectFromWindowRequest:
+#if QT_CONFIG(accessibility)
+        return QWindowsUiaAccessibility::handleWmGetObject(hwnd, wParam, lParam, result);
+#else
+        return false;
+#endif
+    case QtWindows::SettingChangedEvent: {
+        QWindowsWindow::settingsChanged();
+        // Only refresh the window theme if the user changes the personalize settings.
+        if ((wParam == 0) && (lParam != 0) // lParam sometimes may be NULL.
+            && (wcscmp(reinterpret_cast<LPCWSTR>(lParam), L"ImmersiveColorSet") == 0)) {
+            const bool darkMode = QWindowsTheme::queryDarkMode();
+            const bool darkModeChanged = darkMode != QWindowsContextPrivate::m_darkMode;
+            QWindowsContextPrivate::m_darkMode = darkMode;
+            auto integration = QWindowsIntegration::instance();
+            integration->updateApplicationBadge();
+            if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle)) {
+                QWindowsTheme::instance()->refresh();
+                QWindowSystemInterface::handleThemeChange();
+            }
+            if (darkModeChanged) {
+                if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames)) {
+                    for (QWindowsWindow *w : d->m_windows)
+                        w->setDarkBorder(QWindowsContextPrivate::m_darkMode);
+                }
+            }
+        }
+        return d->m_screenManager.handleScreenChanges();
+    }
+    default:
+        break;
+    }
+
+    // Before CreateWindowEx() returns, some events are sent,
+    // for example WM_GETMINMAXINFO asking for size constraints for top levels.
+    // Pass on to current creation context
+    if (!platformWindow && !d->m_creationContext.isNull()) {
+        switch (et) {
+        case QtWindows::QuerySizeHints:
+            d->m_creationContext->applyToMinMaxInfo(reinterpret_cast<MINMAXINFO *>(lParam));
+            return true;
+        case QtWindows::ResizeEvent:
+            d->m_creationContext->obtainedSize = QSize(LOWORD(lParam), HIWORD(lParam));
+            return true;
+        case QtWindows::MoveEvent:
+            d->m_creationContext->obtainedPos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+            return true;
+        case QtWindows::NonClientCreate:
+            if (shouldHaveNonClientDpiScaling(d->m_creationContext->window) &&
+                // DPI aware V2 processes always have NonClientDpiScaling enabled
+                // and there is no need to make an API call to manually enable.
+                !QWindowsContextPrivate::m_v2DpiAware) {
+                    enableNonClientDpiScaling(msg.hwnd);
+                }
+            return false;
+        case QtWindows::CalculateSize:
+            return QWindowsGeometryHint::handleCalculateSize(d->m_creationContext->customMargins, msg, result);
+        case QtWindows::GeometryChangingEvent:
+            return QWindowsWindow::handleGeometryChangingMessage(&msg, d->m_creationContext->window,
+                                                                 d->m_creationContext->margins + d->m_creationContext->customMargins);
+        default:
+            break;
+        }
+    }
+    if (platformWindow) {
+        // Suppress events sent during DestroyWindow() for native children.
+        if (platformWindow->testFlag(QWindowsWindow::WithinDestroy))
+            return false;
+        if (QWindowsContext::verbose > 1)
+            qCDebug(lcQpaEvents) << "Event window: " << platformWindow->window();
+    } else {
+        qWarning("%s: No Qt Window found for event 0x%x (%s), hwnd=0x%p.",
+                 __FUNCTION__, message,
+                 QWindowsGuiEventDispatcher::windowsMessageName(message), hwnd);
+        return false;
+    }
+
+    switch (et) {
+    case QtWindows::DeviceChangeEvent:
+        if (d->m_systemInfo & QWindowsContext::SI_SupportsTouch)
+            break;
+        // See if there are any touch devices added
+        if (wParam == DBT_DEVNODES_CHANGED)
+            initTouch();
+        break;
+    case QtWindows::InputLanguageChangeEvent:
+        if (QWindowsInputContext *wic = windowsInputContext())
+            wic->handleInputLanguageChanged(wParam, lParam);
+        Q_FALLTHROUGH();
+    case QtWindows::KeyDownEvent:
+    case QtWindows::KeyEvent:
+    case QtWindows::InputMethodKeyEvent:
+    case QtWindows::InputMethodKeyDownEvent:
+    case QtWindows::AppCommandEvent:
+        return sessionManagerInteractionBlocked() ||  d->m_keyMapper.translateKeyEvent(platformWindow->window(), hwnd, msg, result);
+    case QtWindows::MenuAboutToShowEvent:
+        if (sessionManagerInteractionBlocked())
+            return true;
+        if (QWindowsPopupMenu::notifyAboutToShow(reinterpret_cast<HMENU>(wParam)))
+            return true;
+        if (platformWindow == nullptr || platformWindow->menuBar() == nullptr)
+            return false;
+        return platformWindow->menuBar()->notifyAboutToShow(reinterpret_cast<HMENU>(wParam));
+    case QtWindows::MenuCommandEvent:
+        if (sessionManagerInteractionBlocked())
+            return true;
+        if (QWindowsPopupMenu::notifyTriggered(LOWORD(wParam)))
+            return true;
+        if (platformWindow == nullptr || platformWindow->menuBar() == nullptr)
+            return false;
+        return platformWindow->menuBar()->notifyTriggered(LOWORD(wParam));
+    case QtWindows::MoveEvent:
+        platformWindow->handleMoved();
+        return true;
+    case QtWindows::ResizeEvent:
+        platformWindow->handleResized(static_cast<int>(wParam), lParam);
+        return true;
+    case QtWindows::QuerySizeHints:
+        platformWindow->getSizeHints(reinterpret_cast<MINMAXINFO *>(lParam));
+        return true;// maybe available on some SDKs revisit WM_NCCALCSIZE
+    case QtWindows::CalculateSize:
+        return QWindowsGeometryHint::handleCalculateSize(platformWindow->customMargins(), msg, result);
+    case QtWindows::NonClientHitTest:
+        return platformWindow->handleNonClientHitTest(QPoint(msg.pt.x, msg.pt.y), result);
+    case QtWindows::GeometryChangingEvent:
+        return platformWindow->handleGeometryChanging(&msg);
+    case QtWindows::ExposeEvent:
+        return platformWindow->handleWmPaint(hwnd, message, wParam, lParam, result);
+    case QtWindows::NonClientMouseEvent:
+        if (!platformWindow->frameStrutEventsEnabled())
+            break;
+        if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
+            return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);
+        else
+            return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);
+    case QtWindows::NonClientPointerEvent:
+        if (!platformWindow->frameStrutEventsEnabled())
+            break;
+        if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
+            return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result);
+        break;
+    case QtWindows::EnterSizeMoveEvent:
+        platformWindow->setFlag(QWindowsWindow::ResizeMoveActive);
+        if (!IsZoomed(hwnd))
+            platformWindow->updateRestoreGeometry();
+        return true;
+    case QtWindows::ExitSizeMoveEvent:
+        platformWindow->clearFlag(QWindowsWindow::ResizeMoveActive);
+        platformWindow->checkForScreenChanged();
+        handleExitSizeMove(platformWindow->window());
+        if (!IsZoomed(hwnd))
+            platformWindow->updateRestoreGeometry();
+        return true;
+    case QtWindows::ScrollEvent:
+        if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
+            return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateScrollEvent(platformWindow->window(), hwnd, msg, result);
+        break;
+    case QtWindows::MouseWheelEvent:
+    case QtWindows::MouseEvent:
+    case QtWindows::LeaveEvent:
+        {
+            QWindow *window = platformWindow->window();
+            while (window && (window->flags() & Qt::WindowTransparentForInput))
+                window = window->parent();
+            if (!window)
+                return false;
+            if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
+                return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(window, hwnd, et, msg, result);
+            else
+                return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result);
+        }
+        break;
+    case QtWindows::TouchEvent:
+        if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
+            return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result);
+        break;
+    case QtWindows::PointerEvent:
+        if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
+            return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result);
+        break;
+    case QtWindows::FocusInEvent: // see QWindowsWindow::requestActivateWindow().
+    case QtWindows::FocusOutEvent:
+        handleFocusEvent(et, platformWindow);
+        return true;
+    case QtWindows::ShowEventOnParentRestoring: // QTBUG-40696, prevent Windows from re-showing hidden transient children (dialogs).
+        if (!platformWindow->window()->isVisible()) {
+            *result = 0;
+            return true;
+        }
+        break;
+    case QtWindows::HideEvent:
+        platformWindow->handleHidden();
+        return false;// Indicate transient children should be hidden by windows (SW_PARENTCLOSING)
+    case QtWindows::CloseEvent:
+        QWindowSystemInterface::handleCloseEvent(platformWindow->window());
+        return true;
+    case QtWindows::ThemeChanged: {
+        QWindowsThemeCache::clearThemeCache(platformWindow->handle());
+        // Switch from Aero to Classic changes margins.
+        if (QWindowsTheme *theme = QWindowsTheme::instance())
+            theme->windowsThemeChanged(platformWindow->window());
+        return true;
+    }
+    case QtWindows::CompositionSettingsChanged:
+        platformWindow->handleCompositionSettingsChanged();
+        return true;
+    case QtWindows::ActivateWindowEvent:
+        if (platformWindow->window()->flags() & Qt::WindowDoesNotAcceptFocus) {
+            *result = LRESULT(MA_NOACTIVATE);
+            return true;
+        }
+#if QT_CONFIG(tabletevent)
+        if (!d->m_tabletSupport.isNull())
+            d->m_tabletSupport->notifyActivate();
+#endif // QT_CONFIG(tabletevent)
+        if (platformWindow->testFlag(QWindowsWindow::BlockedByModal))
+            if (const QWindow *modalWindow = QGuiApplication::modalWindow()) {
+                QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(modalWindow);
+                Q_ASSERT(platformWindow);
+                platformWindow->alertWindow();
+            }
+        break;
+    case QtWindows::MouseActivateWindowEvent:
+    case QtWindows::PointerActivateWindowEvent:
+        if (platformWindow->window()->flags() & Qt::WindowDoesNotAcceptFocus) {
+            *result = LRESULT(MA_NOACTIVATE);
+            return true;
+        }
+        break;
+#ifndef QT_NO_CONTEXTMENU
+    case QtWindows::ContextMenu:
+        return handleContextMenuEvent(platformWindow->window(), msg);
+#endif
+    case QtWindows::WhatsThisEvent: {
+#ifndef QT_NO_WHATSTHIS
+        QWindowSystemInterface::handleEnterWhatsThisEvent();
+        return true;
+#endif
+    }   break;
+    case QtWindows::DpiScaledSizeEvent:
+        platformWindow->handleDpiScaledSize(wParam, lParam, result);
+        return true;
+    case QtWindows::DpiChangedEvent:
+        platformWindow->handleDpiChanged(hwnd, wParam, lParam);
+        return true;
+    case QtWindows::DpiChangedAfterParentEvent:
+        platformWindow->handleDpiChangedAfterParent(hwnd);
+        return true;
+#if QT_CONFIG(sessionmanager)
+    case QtWindows::QueryEndSessionApplicationEvent: {
+        QWindowsSessionManager *sessionManager = platformSessionManager();
+        if (sessionManager->isActive()) { // bogus message from windows
+            *result = sessionManager->wasCanceled() ? 0 : 1;
+            return true;
+        }
+
+        sessionManager->setActive(true);
+        sessionManager->blocksInteraction();
+        sessionManager->clearCancellation();
+
+        auto *qGuiAppPriv = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(qApp));
+        qGuiAppPriv->commitData();
+
+        if (lParam & ENDSESSION_LOGOFF)
+            fflush(nullptr);
+
+        *result = sessionManager->wasCanceled() ? 0 : 1;
+        return true;
+    }
+    case QtWindows::EndSessionApplicationEvent: {
+        QWindowsSessionManager *sessionManager = platformSessionManager();
+
+        sessionManager->setActive(false);
+        sessionManager->allowsInteraction();
+        const bool endsession = wParam != 0;
+
+        // we receive the message for each toplevel window included internal hidden ones,
+        // but the aboutToQuit signal should be emitted only once.
+        auto *qGuiAppPriv = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(qApp));
+        if (endsession && !qGuiAppPriv->aboutToQuitEmitted) {
+            qGuiAppPriv->aboutToQuitEmitted = true;
+            int index = QGuiApplication::staticMetaObject.indexOfSignal("aboutToQuit()");
+            qApp->qt_metacall(QMetaObject::InvokeMetaMethod, index, nullptr);
+            // since the process will be killed immediately quit() has no real effect
+            QGuiApplication::quit();
+        }
+        return true;
+    }
+#endif // !defined(QT_NO_SESSIONMANAGER)
+    case QtWindows::TaskbarButtonCreated:
+        // Apply application badge if this is the first time we have a taskbar
+        // button, or after Explorer restart.
+        QWindowsIntegration::instance()->updateApplicationBadge();
+        break;
+    default:
+        break;
+    }
+    return false;
+}
+
+/* Compress activation events. If the next focus window is already known
+ * at the time the current one receives focus-out, pass that to
+ * QWindowSystemInterface instead of sending 0 and ignore its consecutive
+ * focus-in event.
+ * This helps applications that do handling in focus-out events. */
+void QWindowsContext::handleFocusEvent(QtWindows::WindowsEventType et,
+                                       QWindowsWindow *platformWindow)
+{
+    QWindow *nextActiveWindow = nullptr;
+    if (et == QtWindows::FocusInEvent) {
+        QWindow *topWindow = QWindowsWindow::topLevelOf(platformWindow->window());
+        QWindow *modalWindow = nullptr;
+        if (QGuiApplicationPrivate::instance()->isWindowBlocked(topWindow, &modalWindow) && topWindow != modalWindow) {
+            modalWindow->requestActivate();
+            return;
+        }
+        // QTBUG-32867: Invoking WinAPI SetParent() can cause focus-in for the
+        // window which is not desired for native child widgets.
+        if (platformWindow->testFlag(QWindowsWindow::WithinSetParent)) {
+            QWindow *currentFocusWindow = QGuiApplication::focusWindow();
+            if (currentFocusWindow && currentFocusWindow != platformWindow->window()) {
+                currentFocusWindow->requestActivate();
+                return;
+            }
+        }
+        nextActiveWindow = platformWindow->window();
+    } else {
+        // Focus out: Is the next window known and different
+        // from the receiving the focus out.
+        if (const HWND nextActiveHwnd = GetFocus())
+            if (QWindowsWindow *nextActivePlatformWindow = findClosestPlatformWindow(nextActiveHwnd))
+                if (nextActivePlatformWindow != platformWindow)
+                    nextActiveWindow = nextActivePlatformWindow->window();
+    }
+    if (nextActiveWindow != d->m_lastActiveWindow) {
+         d->m_lastActiveWindow = nextActiveWindow;
+         QWindowSystemInterface::handleFocusWindowChanged(nextActiveWindow, Qt::ActiveWindowFocusReason);
+    }
+}
+
+#ifndef QT_NO_CONTEXTMENU
+bool QWindowsContext::handleContextMenuEvent(QWindow *window, const MSG &msg)
+{
+    bool mouseTriggered = false;
+    QPoint globalPos;
+    QPoint pos;
+    if (msg.lParam != int(0xffffffff)) {
+        mouseTriggered = true;
+        globalPos.setX(msg.pt.x);
+        globalPos.setY(msg.pt.y);
+        pos = QWindowsGeometryHint::mapFromGlobal(msg.hwnd, globalPos);
+
+        RECT clientRect;
+        if (GetClientRect(msg.hwnd, &clientRect)) {
+            if (pos.x() < clientRect.left || pos.x() >= clientRect.right ||
+                pos.y() < clientRect.top || pos.y() >= clientRect.bottom)
+            {
+                // This is the case that user has right clicked in the window's caption,
+                // We should call DefWindowProc() to display a default shortcut menu
+                // instead of sending a Qt window system event.
+                return false;
+            }
+        }
+    }
+
+    QWindowSystemInterface::handleContextMenuEvent(window, mouseTriggered, pos, globalPos,
+                                                   keyMapper()->queryKeyboardModifiers());
+    return true;
+}
+#endif
+
+void QWindowsContext::handleExitSizeMove(QWindow *window)
+{
+    // Windows can be moved/resized by:
+    // 1) User moving a window by dragging the title bar: Causes a sequence
+    //    of WM_NCLBUTTONDOWN, WM_NCMOUSEMOVE but no WM_NCLBUTTONUP,
+    //    leaving the left mouse button 'pressed'
+    // 2) User choosing Resize/Move from System menu and using mouse/cursor keys:
+    //    No mouse events are received
+    // 3) Programmatically via QSizeGrip calling QPlatformWindow::startSystemResize/Move():
+    //    Mouse is left in pressed state after press on size grip (inside window),
+    //    no further mouse events are received
+    // For cases 1,3, intercept WM_EXITSIZEMOVE to sync the buttons.
+    const Qt::MouseButtons currentButtons = QWindowsMouseHandler::queryMouseButtons();
+    const Qt::MouseButtons appButtons = QGuiApplication::mouseButtons();
+    if (currentButtons == appButtons)
+        return;
+    const Qt::KeyboardModifiers keyboardModifiers = keyMapper()->queryKeyboardModifiers();
+    const QPoint globalPos = QWindowsCursor::mousePosition();
+    const QPlatformWindow *platWin = window->handle();
+    const QPoint localPos = platWin->mapFromGlobal(globalPos);
+    const QEvent::Type type = platWin->geometry().contains(globalPos)
+        ? QEvent::MouseButtonRelease : QEvent::NonClientAreaMouseButtonRelease;
+    for (Qt::MouseButton button : {Qt::LeftButton, Qt::RightButton, Qt::MiddleButton}) {
+        if (appButtons.testFlag(button) && !currentButtons.testFlag(button)) {
+            QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
+                currentButtons, button, type, keyboardModifiers);
+        }
+    }
+    if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
+        d->m_pointerHandler.clearEvents();
+    else
+        d->m_mouseHandler.clearEvents();
+}
+
+bool QWindowsContext::asyncExpose() const
+{
+    return d->m_asyncExpose;
+}
+
+void QWindowsContext::setAsyncExpose(bool value)
+{
+    d->m_asyncExpose = value;
+}
+
+DWORD QWindowsContext::readAdvancedExplorerSettings(const wchar_t *subKey, DWORD defaultValue)
+{
+    const auto value =
+        QWinRegistryKey(HKEY_CURRENT_USER,
+                        LR"(Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced)")
+                       .dwordValue(subKey);
+    return value.second ? value.first : defaultValue;
+}
+
+static inline bool isEmptyRect(const RECT &rect)
+{
+    return rect.right - rect.left == 0 && rect.bottom - rect.top == 0;
+}
+
+static inline QMargins marginsFromRects(const RECT &frame, const RECT &client)
+{
+    return QMargins(client.left - frame.left, client.top - frame.top,
+                    frame.right - client.right, frame.bottom - client.bottom);
+}
+
+static RECT rectFromNcCalcSize(UINT message, WPARAM wParam, LPARAM lParam, int n)
+{
+    RECT result = {0, 0, 0, 0};
+    if (message == WM_NCCALCSIZE && wParam)
+        result = reinterpret_cast<const NCCALCSIZE_PARAMS *>(lParam)->rgrc[n];
+    return result;
+}
+
+static inline bool isMinimized(HWND hwnd)
+{
+    WINDOWPLACEMENT windowPlacement;
+    windowPlacement.length = sizeof(WINDOWPLACEMENT);
+    return GetWindowPlacement(hwnd, &windowPlacement) && windowPlacement.showCmd == SW_SHOWMINIMIZED;
+}
+
+static inline bool isTopLevel(HWND hwnd)
+{
+    return (GetWindowLongPtr(hwnd, GWL_STYLE) & WS_CHILD) == 0;
+}
+
+/*!
+    \brief Windows functions for actual windows.
+
+    There is another one for timers, sockets, etc in
+    QEventDispatcherWin32.
+
+*/
+
+extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+    LRESULT result;
+    const QtWindows::WindowsEventType et = windowsEventType(message, wParam, lParam);
+    QWindowsWindow *platformWindow = nullptr;
+    const RECT ncCalcSizeFrame = rectFromNcCalcSize(message, wParam, lParam, 0);
+    const bool handled = QWindowsContext::instance()->windowsProc(hwnd, message, et, wParam, lParam, &result, &platformWindow);
+    if (QWindowsContext::verbose > 1 && lcQpaEvents().isDebugEnabled()) {
+        if (const char *eventName = QWindowsGuiEventDispatcher::windowsMessageName(message)) {
+            qCDebug(lcQpaEvents).nospace() << "EVENT: hwd=" << hwnd << ' ' << eventName
+                << " msg=0x" << Qt::hex << message << " et=0x" << et << Qt::dec << " wp="
+                << int(wParam) << " at " << GET_X_LPARAM(lParam) << ','
+                << GET_Y_LPARAM(lParam) << " handled=" << handled;
+        }
+    }
+    if (!handled)
+        result = DefWindowProc(hwnd, message, wParam, lParam);
+
+    // Capture WM_NCCALCSIZE on top level windows and obtain the window margins by
+    // subtracting the rectangles before and after processing. This will correctly
+    // capture client code overriding the message and allow for per-monitor margins
+    // for High DPI (QTBUG-53255, QTBUG-40578).
+    if (message == WM_NCCALCSIZE && !isEmptyRect(ncCalcSizeFrame) && isTopLevel(hwnd) && !isMinimized(hwnd)) {
+        const QMargins margins =
+            marginsFromRects(ncCalcSizeFrame, rectFromNcCalcSize(message, wParam, lParam, 0));
+        if (margins.left() >= 0) {
+            if (platformWindow) {
+                qCDebug(lcQpaWindow) << __FUNCTION__ << "WM_NCCALCSIZE for" << hwnd << margins;
+                platformWindow->setFullFrameMargins(margins);
+            } else {
+                const QSharedPointer<QWindowCreationContext> ctx = QWindowsContext::instance()->windowCreationContext();
+                if (!ctx.isNull())
+                    ctx->margins = margins;
+            }
+        }
+    }
+    return result;
+}
+
+
+static inline QByteArray nativeEventType() { return QByteArrayLiteral("windows_generic_MSG"); }
+
+// Send to QAbstractEventDispatcher
+bool QWindowsContext::filterNativeEvent(MSG *msg, LRESULT *result)
+{
+    QAbstractEventDispatcher *dispatcher = QAbstractEventDispatcher::instance();
+    qintptr filterResult = 0;
+    if (dispatcher && dispatcher->filterNativeEvent(nativeEventType(), msg, &filterResult)) {
+        *result = LRESULT(filterResult);
+        return true;
+    }
+    return false;
+}
+
+// Send to QWindowSystemInterface
+bool QWindowsContext::filterNativeEvent(QWindow *window, MSG *msg, LRESULT *result)
+{
+    qintptr filterResult = 0;
+    if (QWindowSystemInterface::handleNativeEvent(window, nativeEventType(), msg, &filterResult)) {
+        *result = LRESULT(filterResult);
+        return true;
+    }
+    return false;
+}
+
+QT_END_NAMESPACE
diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.h b/qtbase/src/plugins/platforms/windows/qwindowscontext.h
index 1089224433..557bd1f63c 100644
--- a/qtbase/src/plugins/platforms/windows/qwindowscontext.h
+++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.h
@@ -14,6 +14,7 @@
 #define STRICT_TYPED_ITEMIDS
 #include <shlobj.h>
 #include <shlwapi.h>
+#include <shellscalingapi.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -44,6 +45,95 @@ struct QWindowsContextPrivate;
 class QPoint;
 class QKeyEvent;
 class QPointingDevice;
+
+struct QWindowsUser32DLL
+{
+    inline void init();
+    inline bool supportsPointerApi();
+
+    typedef BOOL (WINAPI *EnableMouseInPointer)(BOOL);
+    typedef BOOL (WINAPI *GetPointerType)(UINT32, PVOID);
+    typedef BOOL (WINAPI *GetPointerInfo)(UINT32, PVOID);
+    typedef BOOL (WINAPI *GetPointerDeviceRects)(HANDLE, RECT *, RECT *);
+    typedef BOOL (WINAPI *GetPointerTouchInfo)(UINT32, PVOID);
+    typedef BOOL (WINAPI *GetPointerFrameTouchInfo)(UINT32, UINT32 *, PVOID);
+    typedef BOOL (WINAPI *GetPointerFrameTouchInfoHistory)(UINT32, UINT32 *, UINT32 *, PVOID);
+    typedef BOOL (WINAPI *GetPointerPenInfo)(UINT32, PVOID);
+    typedef BOOL (WINAPI *GetPointerPenInfoHistory)(UINT32, UINT32 *, PVOID);
+    typedef BOOL (WINAPI *SkipPointerFrameMessages)(UINT32);
+    typedef BOOL (WINAPI *SetProcessDPIAware)();
+    typedef BOOL (WINAPI *SetProcessDpiAwarenessContext)(HANDLE);
+    typedef BOOL (WINAPI *AddClipboardFormatListener)(HWND);
+    typedef BOOL (WINAPI *RemoveClipboardFormatListener)(HWND);
+    typedef BOOL (WINAPI *GetDisplayAutoRotationPreferences)(DWORD *);
+    typedef BOOL (WINAPI *SetDisplayAutoRotationPreferences)(DWORD);
+    typedef BOOL (WINAPI *AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UINT);
+    typedef BOOL (WINAPI *EnableNonClientDpiScaling)(HWND);
+    typedef DPI_AWARENESS_CONTEXT (WINAPI *GetWindowDpiAwarenessContext)(HWND);
+    typedef DPI_AWARENESS (WINAPI *GetAwarenessFromDpiAwarenessContext)(int);
+    typedef BOOL (WINAPI *SystemParametersInfoForDpi)(UINT, UINT, PVOID, UINT, UINT);
+    typedef int  (WINAPI *GetDpiForWindow)(HWND);
+    typedef BOOL (WINAPI *GetSystemMetricsForDpi)(INT, UINT);
+    typedef BOOL (WINAPI *AreDpiAwarenessContextsEqual)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT);
+    typedef DPI_AWARENESS_CONTEXT (WINAPI *GetThreadDpiAwarenessContext)();
+    typedef BOOL (WINAPI *IsValidDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
+
+    // Windows pointer functions (Windows 8 or later).
+    EnableMouseInPointer enableMouseInPointer = nullptr;
+    GetPointerType getPointerType = nullptr;
+    GetPointerInfo getPointerInfo = nullptr;
+    GetPointerDeviceRects getPointerDeviceRects = nullptr;
+    GetPointerTouchInfo getPointerTouchInfo = nullptr;
+    GetPointerFrameTouchInfo getPointerFrameTouchInfo = nullptr;
+    GetPointerFrameTouchInfoHistory getPointerFrameTouchInfoHistory = nullptr;
+    GetPointerPenInfo getPointerPenInfo = nullptr;
+    GetPointerPenInfoHistory getPointerPenInfoHistory = nullptr;
+    SkipPointerFrameMessages skipPointerFrameMessages = nullptr;
+
+    // Windows Vista onwards
+    SetProcessDPIAware setProcessDPIAware = nullptr;
+
+    // Windows 10 version 1607 onwards
+    GetDpiForWindow getDpiForWindow = nullptr;
+    GetThreadDpiAwarenessContext getThreadDpiAwarenessContext = nullptr;
+    IsValidDpiAwarenessContext isValidDpiAwarenessContext = nullptr;
+
+    // Windows 10 version 1703 onwards
+    SetProcessDpiAwarenessContext setProcessDpiAwarenessContext = nullptr;
+    AreDpiAwarenessContextsEqual areDpiAwarenessContextsEqual = nullptr;
+
+    // Clipboard listeners are present on Windows Vista onwards
+    // but missing in MinGW 4.9 stub libs. Can be removed in MinGW 5.
+    AddClipboardFormatListener addClipboardFormatListener = nullptr;
+    RemoveClipboardFormatListener removeClipboardFormatListener = nullptr;
+
+    // Rotation API
+    GetDisplayAutoRotationPreferences getDisplayAutoRotationPreferences = nullptr;
+    SetDisplayAutoRotationPreferences setDisplayAutoRotationPreferences = nullptr;
+
+    AdjustWindowRectExForDpi adjustWindowRectExForDpi = nullptr;
+    EnableNonClientDpiScaling enableNonClientDpiScaling = nullptr;
+    GetWindowDpiAwarenessContext getWindowDpiAwarenessContext = nullptr;
+    GetAwarenessFromDpiAwarenessContext getAwarenessFromDpiAwarenessContext = nullptr;
+    SystemParametersInfoForDpi systemParametersInfoForDpi = nullptr;
+    GetSystemMetricsForDpi getSystemMetricsForDpi = nullptr;
+};
+
+// Shell scaling library (Windows 8.1 onwards)
+struct QWindowsShcoreDLL
+{
+    void init();
+    inline bool isValid() const { return getProcessDpiAwareness && setProcessDpiAwareness && getDpiForMonitor; }
+
+    typedef HRESULT (WINAPI *GetProcessDpiAwareness)(HANDLE,PROCESS_DPI_AWARENESS *);
+    typedef HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS);
+    typedef HRESULT (WINAPI *GetDpiForMonitor)(HMONITOR,int,UINT *,UINT *);
+
+    GetProcessDpiAwareness getProcessDpiAwareness = nullptr;
+    SetProcessDpiAwareness setProcessDpiAwareness = nullptr;
+    GetDpiForMonitor getDpiForMonitor = nullptr;
+};
+
 class QWindowsContext
 {
     Q_DISABLE_COPY_MOVE(QWindowsContext)
@@ -138,6 +228,9 @@ public:
     QWindowsScreenManager &screenManager();
     QWindowsTabletSupport *tabletSupport() const;
 
+    static QWindowsUser32DLL user32dll;
+    static QWindowsShcoreDLL shcoredll;
+
     bool asyncExpose() const;
     void setAsyncExpose(bool value);
 
diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.h.orig b/qtbase/src/plugins/platforms/windows/qwindowscontext.h.orig
new file mode 100644
index 0000000000..1089224433
--- /dev/null
+++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.h.orig
@@ -0,0 +1,178 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWINDOWSCONTEXT_H
+#define QWINDOWSCONTEXT_H
+
+#include "qtwindowsglobal.h"
+#include <QtCore/qt_windows.h>
+
+#include <QtCore/qscopedpointer.h>
+#include <QtCore/qsharedpointer.h>
+#include <QtCore/qloggingcategory.h>
+
+#define STRICT_TYPED_ITEMIDS
+#include <shlobj.h>
+#include <shlwapi.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaEvents)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaGl)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaMime)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaInputMethods)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaDialogs)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaMenus)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaTablet)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaAccessibility)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaUiAutomation)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaTrayIcon)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen)
+
+class QWindow;
+class QPlatformScreen;
+class QPlatformWindow;
+class QPlatformKeyMapper;
+class QWindowsMenuBar;
+class QWindowsScreenManager;
+class QWindowsTabletSupport;
+class QWindowsWindow;
+class QWindowsMimeRegistry;
+struct QWindowCreationContext;
+struct QWindowsContextPrivate;
+class QPoint;
+class QKeyEvent;
+class QPointingDevice;
+class QWindowsContext
+{
+    Q_DISABLE_COPY_MOVE(QWindowsContext)
+public:
+    using HandleBaseWindowHash = QHash<HWND, QWindowsWindow *>;
+
+    enum SystemInfoFlags
+    {
+        SI_RTL_Extensions = 0x1,
+        SI_SupportsTouch = 0x2,
+        SI_SupportsPointer = 0x4,
+    };
+
+    // Verbose flag set by environment variable QT_QPA_VERBOSE
+    static int verbose;
+
+    explicit QWindowsContext();
+    ~QWindowsContext();
+
+    bool initTouch();
+    bool initTouch(unsigned integrationOptions); // For calls from QWindowsIntegration::QWindowsIntegration() only.
+    void registerTouchWindows();
+    bool initTablet();
+    bool initPointer(unsigned integrationOptions);
+    bool disposeTablet();
+
+    bool initPowerNotificationHandler();
+
+    int defaultDPI() const;
+
+    static QString classNamePrefix();
+    QString registerWindowClass(const QWindow *w);
+    QString registerWindowClass(QString cname, WNDPROC proc,
+                                unsigned style = 0, HBRUSH brush = nullptr,
+                                bool icon = false);
+    HWND createDummyWindow(const QString &classNameIn,
+                           const wchar_t *windowName,
+                           WNDPROC wndProc = nullptr, DWORD style = WS_OVERLAPPED);
+
+    HDC displayContext() const;
+    int screenDepth() const;
+
+    static QWindowsContext *instance();
+
+    void addWindow(HWND, QWindowsWindow *w);
+    void removeWindow(HWND);
+
+    QWindowsWindow *findClosestPlatformWindow(HWND) const;
+    QWindowsWindow *findPlatformWindow(HWND) const;
+    QWindowsWindow *findPlatformWindow(const QWindowsMenuBar *mb) const;
+    QWindow *findWindow(HWND) const;
+    QWindowsWindow *findPlatformWindowAt(HWND parent, const QPoint &screenPoint,
+                                             unsigned cwex_flags) const;
+
+    static bool shouldHaveNonClientDpiScaling(const QWindow *window);
+
+    QWindow *windowUnderMouse() const;
+    void clearWindowUnderMouse();
+
+    inline bool windowsProc(HWND hwnd, UINT message,
+                            QtWindows::WindowsEventType et,
+                            WPARAM wParam, LPARAM lParam, LRESULT *result,
+                            QWindowsWindow **platformWindowPtr);
+
+    QWindow *keyGrabber() const;
+    void setKeyGrabber(QWindow *hwnd);
+
+    QSharedPointer<QWindowCreationContext> setWindowCreationContext(const QSharedPointer<QWindowCreationContext> &ctx);
+    QSharedPointer<QWindowCreationContext> windowCreationContext() const;
+
+    static void setTabletAbsoluteRange(int a);
+
+    static bool setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwareness);
+    static QtWindows::DpiAwareness processDpiAwareness();
+    static QtWindows::DpiAwareness windowDpiAwareness(HWND hwnd);
+
+    static bool isDarkMode();
+
+    void setDetectAltGrModifier(bool a);
+
+    // Returns a combination of SystemInfoFlags
+    unsigned systemInfo() const;
+
+    bool useRTLExtensions() const;
+    QPlatformKeyMapper *keyMapper() const;
+
+    HandleBaseWindowHash &windows();
+
+    static bool isSessionLocked();
+
+    QWindowsMimeRegistry &mimeConverter() const;
+    QWindowsScreenManager &screenManager();
+    QWindowsTabletSupport *tabletSupport() const;
+
+    bool asyncExpose() const;
+    void setAsyncExpose(bool value);
+
+    static void forceNcCalcSize(HWND hwnd);
+
+    static bool systemParametersInfo(unsigned action, unsigned param, void *out, unsigned dpi = 0);
+    static bool systemParametersInfoForScreen(unsigned action, unsigned param, void *out,
+                                              const QPlatformScreen *screen = nullptr);
+    static bool systemParametersInfoForWindow(unsigned action, unsigned param, void *out,
+                                              const QPlatformWindow *win = nullptr);
+    static bool nonClientMetrics(NONCLIENTMETRICS *ncm, unsigned dpi = 0);
+    static bool nonClientMetricsForScreen(NONCLIENTMETRICS *ncm,
+                                          const QPlatformScreen *screen = nullptr);
+    static bool nonClientMetricsForWindow(NONCLIENTMETRICS *ncm,
+                                          const QPlatformWindow *win = nullptr);
+
+    static DWORD readAdvancedExplorerSettings(const wchar_t *subKey, DWORD defaultValue);
+
+    static bool filterNativeEvent(MSG *msg, LRESULT *result);
+    static bool filterNativeEvent(QWindow *window, MSG *msg, LRESULT *result);
+
+private:
+    void handleFocusEvent(QtWindows::WindowsEventType et, QWindowsWindow *w);
+#ifndef QT_NO_CONTEXTMENU
+    bool handleContextMenuEvent(QWindow *window, const MSG &msg);
+#endif
+    void handleExitSizeMove(QWindow *window);
+    void unregisterWindowClasses();
+
+    QScopedPointer<QWindowsContextPrivate> d;
+    static QWindowsContext *m_instance;
+};
+
+extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND, UINT, WPARAM, LPARAM);
+
+QT_END_NAMESPACE
+
+#endif // QWINDOWSCONTEXT_H
diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.h.rej b/qtbase/src/plugins/platforms/windows/qwindowscontext.h.rej
new file mode 100644
index 0000000000..ac1b3ee2b9
--- /dev/null
+++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.h.rej
@@ -0,0 +1,97 @@
+--- qtbase/src/plugins/platforms/windows/qwindowscontext.h
++++ qtbase/src/plugins/platforms/windows/qwindowscontext.h
+@@ -45,6 +46,94 @@ class QPoint;
+ class QKeyEvent;
+ class QPointingDevice;
+ 
++struct QWindowsUser32DLL
++{
++    inline void init();
++    inline bool supportsPointerApi();
++
++    typedef BOOL (WINAPI *EnableMouseInPointer)(BOOL);
++    typedef BOOL (WINAPI *GetPointerType)(UINT32, PVOID);
++    typedef BOOL (WINAPI *GetPointerInfo)(UINT32, PVOID);
++    typedef BOOL (WINAPI *GetPointerDeviceRects)(HANDLE, RECT *, RECT *);
++    typedef BOOL (WINAPI *GetPointerTouchInfo)(UINT32, PVOID);
++    typedef BOOL (WINAPI *GetPointerFrameTouchInfo)(UINT32, UINT32 *, PVOID);
++    typedef BOOL (WINAPI *GetPointerFrameTouchInfoHistory)(UINT32, UINT32 *, UINT32 *, PVOID);
++    typedef BOOL (WINAPI *GetPointerPenInfo)(UINT32, PVOID);
++    typedef BOOL (WINAPI *GetPointerPenInfoHistory)(UINT32, UINT32 *, PVOID);
++    typedef BOOL (WINAPI *SkipPointerFrameMessages)(UINT32);
++    typedef BOOL (WINAPI *SetProcessDPIAware)();
++    typedef BOOL (WINAPI *SetProcessDpiAwarenessContext)(HANDLE);
++    typedef BOOL (WINAPI *AddClipboardFormatListener)(HWND);
++    typedef BOOL (WINAPI *RemoveClipboardFormatListener)(HWND);
++    typedef BOOL (WINAPI *GetDisplayAutoRotationPreferences)(DWORD *);
++    typedef BOOL (WINAPI *SetDisplayAutoRotationPreferences)(DWORD);
++    typedef BOOL (WINAPI *AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UINT);
++    typedef BOOL (WINAPI *EnableNonClientDpiScaling)(HWND);
++    typedef DPI_AWARENESS_CONTEXT (WINAPI *GetWindowDpiAwarenessContext)(HWND);
++    typedef DPI_AWARENESS (WINAPI *GetAwarenessFromDpiAwarenessContext)(int);
++    typedef BOOL (WINAPI *SystemParametersInfoForDpi)(UINT, UINT, PVOID, UINT, UINT);
++    typedef int  (WINAPI *GetDpiForWindow)(HWND);
++    typedef BOOL (WINAPI *GetSystemMetricsForDpi)(INT, UINT);
++    typedef BOOL (WINAPI *AreDpiAwarenessContextsEqual)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT);
++    typedef DPI_AWARENESS_CONTEXT (WINAPI *GetThreadDpiAwarenessContext)();
++    typedef BOOL (WINAPI *IsValidDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
++    
++    // Windows pointer functions (Windows 8 or later).
++    EnableMouseInPointer enableMouseInPointer = nullptr;
++    GetPointerType getPointerType = nullptr;
++    GetPointerInfo getPointerInfo = nullptr;
++    GetPointerDeviceRects getPointerDeviceRects = nullptr;
++    GetPointerTouchInfo getPointerTouchInfo = nullptr;
++    GetPointerFrameTouchInfo getPointerFrameTouchInfo = nullptr;
++    GetPointerFrameTouchInfoHistory getPointerFrameTouchInfoHistory = nullptr;
++    GetPointerPenInfo getPointerPenInfo = nullptr;
++    GetPointerPenInfoHistory getPointerPenInfoHistory = nullptr;
++    SkipPointerFrameMessages skipPointerFrameMessages = nullptr;
++
++    // Windows Vista onwards
++    SetProcessDPIAware setProcessDPIAware = nullptr;
++
++    // Windows 10 version 1607 onwards
++    GetDpiForWindow getDpiForWindow = nullptr;
++    GetThreadDpiAwarenessContext getThreadDpiAwarenessContext = nullptr;
++    IsValidDpiAwarenessContext isValidDpiAwarenessContext = nullptr;
++
++    // Windows 10 version 1703 onwards
++    SetProcessDpiAwarenessContext setProcessDpiAwarenessContext = nullptr;
++    AreDpiAwarenessContextsEqual areDpiAwarenessContextsEqual = nullptr;
++
++    // Clipboard listeners are present on Windows Vista onwards
++    // but missing in MinGW 4.9 stub libs. Can be removed in MinGW 5.
++    AddClipboardFormatListener addClipboardFormatListener = nullptr;
++    RemoveClipboardFormatListener removeClipboardFormatListener = nullptr;
++
++    // Rotation API
++    GetDisplayAutoRotationPreferences getDisplayAutoRotationPreferences = nullptr;
++    SetDisplayAutoRotationPreferences setDisplayAutoRotationPreferences = nullptr;
++
++    AdjustWindowRectExForDpi adjustWindowRectExForDpi = nullptr;
++    EnableNonClientDpiScaling enableNonClientDpiScaling = nullptr;
++    GetWindowDpiAwarenessContext getWindowDpiAwarenessContext = nullptr;
++    GetAwarenessFromDpiAwarenessContext getAwarenessFromDpiAwarenessContext = nullptr;
++    SystemParametersInfoForDpi systemParametersInfoForDpi = nullptr;
++    GetSystemMetricsForDpi getSystemMetricsForDpi = nullptr;
++};
++
++// Shell scaling library (Windows 8.1 onwards)
++struct QWindowsShcoreDLL
++{
++    void init();
++    inline bool isValid() const { return getProcessDpiAwareness && setProcessDpiAwareness && getDpiForMonitor; }
++
++    typedef HRESULT (WINAPI *GetProcessDpiAwareness)(HANDLE,PROCESS_DPI_AWARENESS *);
++    typedef HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS);
++    typedef HRESULT (WINAPI *GetDpiForMonitor)(HMONITOR,int,UINT *,UINT *);
++
++    GetProcessDpiAwareness getProcessDpiAwareness = nullptr;
++    SetProcessDpiAwareness setProcessDpiAwareness = nullptr;
++    GetDpiForMonitor getDpiForMonitor = nullptr;
++};
++
+ class QWindowsContext
+ {
+     Q_DISABLE_COPY_MOVE(QWindowsContext)
diff --git a/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp b/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp
index c6f55c3509..d97efc6851 100644
--- a/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp
+++ b/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp
@@ -673,7 +673,7 @@ static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource,
                 const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
 
                 POINTER_INFO pointerInfo{};
-                if (!GetPointerInfo(pointerId, &pointerInfo))
+                if (!QWindowsContext::user32dll.getPointerInfo || !QWindowsContext::user32dll.getPointerInfo(pointerId, &pointerInfo))
                     return E_FAIL;
 
                 if (pointerInfo.pointerFlags & POINTER_FLAG_PRIMARY) {
diff --git a/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp.orig b/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp.orig
new file mode 100644
index 0000000000..c6f55c3509
--- /dev/null
+++ b/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp.orig
@@ -0,0 +1,784 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtCore/qt_windows.h>
+#include "qwindowsdrag.h"
+#include "qwindowscontext.h"
+#include "qwindowsscreen.h"
+#if QT_CONFIG(clipboard)
+#  include "qwindowsclipboard.h"
+#endif
+#include "qwindowsintegration.h"
+#include "qwindowsdropdataobject.h"
+#include "qwindowswindow.h"
+#include "qwindowsmousehandler.h"
+#include "qwindowscursor.h"
+#include "qwindowskeymapper.h"
+
+#include <QtGui/qevent.h>
+#include <QtGui/qpixmap.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qrasterwindow.h>
+#include <QtGui/qguiapplication.h>
+#include <qpa/qwindowsysteminterface_p.h>
+#include <QtGui/private/qdnd_p.h>
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/private/qhighdpiscaling_p.h>
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qbuffer.h>
+#include <QtCore/qpoint.h>
+#include <QtCore/qpointer.h>
+#include <QtCore/private/qcomobject_p.h>
+
+#include <shlobj.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+    \class QWindowsDragCursorWindow
+    \brief A toplevel window showing the drag icon in case of touch drag.
+
+    \sa QWindowsOleDropSource
+    \internal
+*/
+
+class QWindowsDragCursorWindow : public QRasterWindow
+{
+public:
+    explicit QWindowsDragCursorWindow(QWindow *parent = nullptr);
+
+    void setPixmap(const QPixmap &p);
+
+protected:
+    void paintEvent(QPaintEvent *) override
+    {
+        QPainter painter(this);
+        painter.drawPixmap(0, 0, m_pixmap);
+    }
+
+private:
+    QPixmap m_pixmap;
+};
+
+QWindowsDragCursorWindow::QWindowsDragCursorWindow(QWindow *parent)
+    : QRasterWindow(parent)
+{
+    QSurfaceFormat windowFormat = format();
+    windowFormat.setAlphaBufferSize(8);
+    setFormat(windowFormat);
+    setObjectName(QStringLiteral("QWindowsDragCursorWindow"));
+    setFlags(Qt::Popup | Qt::NoDropShadowWindowHint
+             | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint
+             | Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput);
+}
+
+void QWindowsDragCursorWindow::setPixmap(const QPixmap &p)
+{
+    if (p.cacheKey() == m_pixmap.cacheKey())
+        return;
+    const QSize oldSize = m_pixmap.size();
+    QSize newSize = p.size();
+    qCDebug(lcQpaMime) << __FUNCTION__ << p.cacheKey() << newSize;
+    m_pixmap = p;
+    if (oldSize != newSize) {
+        const qreal pixDevicePixelRatio = p.devicePixelRatio();
+        if (pixDevicePixelRatio > 1.0 && qFuzzyCompare(pixDevicePixelRatio, devicePixelRatio()))
+            newSize /= qRound(pixDevicePixelRatio);
+        resize(newSize);
+    }
+    if (isVisible())
+        update();
+}
+
+/*!
+    \class QWindowsDropMimeData
+    \brief Special mime data class for data retrieval from Drag operations.
+
+    Implementation of QWindowsInternalMimeDataBase which retrieves the
+    current drop data object from QWindowsDrag.
+
+    \sa QWindowsDrag
+    \internal
+*/
+
+IDataObject *QWindowsDropMimeData::retrieveDataObject() const
+{
+    return QWindowsDrag::instance()->dropDataObject();
+}
+
+static inline Qt::DropActions translateToQDragDropActions(DWORD pdwEffects)
+{
+    Qt::DropActions actions = Qt::IgnoreAction;
+    if (pdwEffects & DROPEFFECT_LINK)
+        actions |= Qt::LinkAction;
+    if (pdwEffects & DROPEFFECT_COPY)
+        actions |= Qt::CopyAction;
+    if (pdwEffects & DROPEFFECT_MOVE)
+        actions |= Qt::MoveAction;
+    return actions;
+}
+
+static inline Qt::DropAction translateToQDragDropAction(DWORD pdwEffect)
+{
+    if (pdwEffect & DROPEFFECT_LINK)
+        return Qt::LinkAction;
+    if (pdwEffect & DROPEFFECT_COPY)
+        return Qt::CopyAction;
+    if (pdwEffect & DROPEFFECT_MOVE)
+        return Qt::MoveAction;
+    return Qt::IgnoreAction;
+}
+
+static inline DWORD translateToWinDragEffects(Qt::DropActions action)
+{
+    DWORD effect = DROPEFFECT_NONE;
+    if (action & Qt::LinkAction)
+        effect |= DROPEFFECT_LINK;
+    if (action & Qt::CopyAction)
+        effect |= DROPEFFECT_COPY;
+    if (action & Qt::MoveAction)
+        effect |= DROPEFFECT_MOVE;
+    return effect;
+}
+
+static inline Qt::KeyboardModifiers toQtKeyboardModifiers(DWORD keyState)
+{
+    Qt::KeyboardModifiers modifiers = Qt::NoModifier;
+
+    if (keyState & MK_SHIFT)
+        modifiers |= Qt::ShiftModifier;
+    if (keyState & MK_CONTROL)
+        modifiers |= Qt::ControlModifier;
+    if (keyState & MK_ALT)
+        modifiers |= Qt::AltModifier;
+
+    return modifiers;
+}
+
+static Qt::KeyboardModifiers lastModifiers = Qt::NoModifier;
+static Qt::MouseButtons lastButtons = Qt::NoButton;
+
+/*!
+    \class QWindowsOleDropSource
+    \brief Implementation of IDropSource
+
+    Used for drag operations.
+
+    \sa QWindowsDrag
+    \internal
+*/
+
+class QWindowsOleDropSource : public QComObject<IDropSource>
+{
+public:
+    enum Mode {
+        MouseDrag,
+        TouchDrag // Mouse cursor suppressed, use window as cursor.
+    };
+
+    explicit QWindowsOleDropSource(QWindowsDrag *drag);
+    ~QWindowsOleDropSource() override;
+
+    void createCursors();
+
+    // IDropSource methods
+    STDMETHOD(QueryContinueDrag)(BOOL fEscapePressed, DWORD grfKeyState) override;
+    STDMETHOD(GiveFeedback)(DWORD dwEffect) override;
+
+private:
+    struct CursorEntry {
+        CursorEntry() : cacheKey(0) {}
+        CursorEntry(const QPixmap &p, qint64 cK, const CursorHandlePtr &c, const QPoint &h) :
+            pixmap(p), cacheKey(cK), cursor(c), hotSpot(h) {}
+
+        QPixmap pixmap;
+        qint64 cacheKey; // Cache key of cursor
+        CursorHandlePtr cursor;
+        QPoint hotSpot;
+    };
+
+    typedef QMap<Qt::DropAction, CursorEntry> ActionCursorMap;
+
+    Mode m_mode;
+    QWindowsDrag *m_drag;
+    QPointer<QWindow> m_windowUnderMouse;
+    Qt::MouseButtons m_currentButtons;
+    ActionCursorMap m_cursors;
+    QWindowsDragCursorWindow *m_touchDragWindow;
+
+#ifndef QT_NO_DEBUG_STREAM
+    friend QDebug operator<<(QDebug, const QWindowsOleDropSource::CursorEntry &);
+#endif
+};
+
+QWindowsOleDropSource::QWindowsOleDropSource(QWindowsDrag *drag)
+    : m_mode(QWindowsCursor::cursorState() != QWindowsCursor::State::Suppressed ? MouseDrag : TouchDrag)
+    , m_drag(drag)
+    , m_windowUnderMouse(QWindowsContext::instance()->windowUnderMouse())
+    , m_currentButtons(Qt::NoButton)
+    , m_touchDragWindow(nullptr)
+{
+    qCDebug(lcQpaMime) << __FUNCTION__ << m_mode;
+}
+
+QWindowsOleDropSource::~QWindowsOleDropSource()
+{
+    m_cursors.clear();
+    delete m_touchDragWindow;
+    qCDebug(lcQpaMime) << __FUNCTION__;
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug d, const QWindowsOleDropSource::CursorEntry &e)
+{
+    d << "CursorEntry:" << e.pixmap.size() << '#' << e.cacheKey
+      << "HCURSOR" << e.cursor->handle() << "hotspot:" << e.hotSpot;
+    return d;
+}
+#endif // !QT_NO_DEBUG_STREAM
+
+/*!
+    \brief Blend custom pixmap with cursors.
+*/
+
+void QWindowsOleDropSource::createCursors()
+{
+    const QDrag *drag = m_drag->currentDrag();
+    const QPixmap pixmap = drag->pixmap();
+    const bool hasPixmap = !pixmap.isNull();
+
+    // Find screen for drag. Could be obtained from QDrag::source(), but that might be a QWidget.
+    const QPlatformScreen *platformScreen = QWindowsContext::instance()->screenManager().screenAtDp(QWindowsCursor::mousePosition());
+    if (!platformScreen) {
+        if (const QScreen *primaryScreen = QGuiApplication::primaryScreen())
+            platformScreen = primaryScreen->handle();
+    }
+    Q_ASSERT(platformScreen);
+    QPlatformCursor *platformCursor = platformScreen->cursor();
+
+    if (GetSystemMetrics (SM_REMOTESESSION) != 0) {
+        /* Workaround for RDP issues with large cursors.
+         * Touch drag window seems to work just fine...
+         * 96 pixel is a 'large' mouse cursor, according to RDP spec */
+        const int rdpLargeCursor = qRound(qreal(96) / QHighDpiScaling::factor(platformScreen));
+        if (pixmap.width() > rdpLargeCursor || pixmap.height() > rdpLargeCursor)
+            m_mode = TouchDrag;
+    }
+
+    qreal pixmapScaleFactor = 1;
+    qreal hotSpotScaleFactor = 1;
+    if (m_mode != TouchDrag) { // Touch drag: pixmap is shown in a separate QWindow, which will be scaled.)
+        hotSpotScaleFactor = QHighDpiScaling::factor(platformScreen);
+        pixmapScaleFactor = hotSpotScaleFactor / pixmap.devicePixelRatio();
+    }
+    QPixmap scaledPixmap = qFuzzyCompare(pixmapScaleFactor, 1.0)
+        ? pixmap
+        :  pixmap.scaled((QSizeF(pixmap.size()) * pixmapScaleFactor).toSize(),
+                         Qt::KeepAspectRatio, Qt::SmoothTransformation);
+    scaledPixmap.setDevicePixelRatio(1);
+
+    Qt::DropAction actions[] = { Qt::MoveAction, Qt::CopyAction, Qt::LinkAction, Qt::IgnoreAction };
+    int actionCount = int(sizeof(actions) / sizeof(actions[0]));
+    if (!hasPixmap)
+        --actionCount; // No Qt::IgnoreAction unless pixmap
+    const QPoint hotSpot = qFuzzyCompare(hotSpotScaleFactor, 1.0)
+        ?  drag->hotSpot()
+        : (QPointF(drag->hotSpot()) * hotSpotScaleFactor).toPoint();
+    for (int cnum = 0; cnum < actionCount; ++cnum) {
+        const Qt::DropAction action = actions[cnum];
+        QPixmap cursorPixmap = drag->dragCursor(action);
+        if (cursorPixmap.isNull() && platformCursor)
+            cursorPixmap = static_cast<QWindowsCursor *>(platformCursor)->dragDefaultCursor(action);
+        const qint64 cacheKey = cursorPixmap.cacheKey();
+        const auto it = m_cursors.find(action);
+        if (it != m_cursors.end() && it.value().cacheKey == cacheKey)
+            continue;
+        if (cursorPixmap.isNull()) {
+            qWarning("%s: Unable to obtain drag cursor for %d.", __FUNCTION__, action);
+            continue;
+        }
+
+        QPoint newHotSpot(0, 0);
+        QPixmap newPixmap = cursorPixmap;
+
+        if (hasPixmap) {
+            const int x1 = qMin(-hotSpot.x(), 0);
+            const int x2 = qMax(scaledPixmap.width() - hotSpot.x(), cursorPixmap.width());
+            const int y1 = qMin(-hotSpot.y(), 0);
+            const int y2 = qMax(scaledPixmap.height() - hotSpot.y(), cursorPixmap.height());
+            QPixmap newCursor(x2 - x1 + 1, y2 - y1 + 1);
+            newCursor.fill(Qt::transparent);
+            QPainter p(&newCursor);
+            const QPoint pmDest = QPoint(qMax(0, -hotSpot.x()), qMax(0, -hotSpot.y()));
+            p.drawPixmap(pmDest, scaledPixmap);
+            p.drawPixmap(qMax(0, hotSpot.x()),qMax(0, hotSpot.y()), cursorPixmap);
+            newPixmap = newCursor;
+            newHotSpot = QPoint(qMax(0, hotSpot.x()), qMax(0, hotSpot.y()));
+        }
+
+        if (const HCURSOR sysCursor = QWindowsCursor::createPixmapCursor(newPixmap, newHotSpot)) {
+            const CursorEntry entry(newPixmap, cacheKey, CursorHandlePtr(new CursorHandle(sysCursor)), newHotSpot);
+            if (it == m_cursors.end())
+                m_cursors.insert(action, entry);
+            else
+                it.value() = entry;
+        }
+    }
+#ifndef QT_NO_DEBUG_OUTPUT
+    if (lcQpaMime().isDebugEnabled())
+        qCDebug(lcQpaMime) << __FUNCTION__ << "pixmap" << pixmap.size() << m_cursors.size() << "cursors:\n" << m_cursors;
+#endif // !QT_NO_DEBUG_OUTPUT
+}
+
+/*!
+    \brief Check for cancel.
+*/
+
+QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
+QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
+{
+    // In some rare cases, when a mouse button is released but the mouse is static,
+    // grfKeyState will not be updated with these released buttons until the mouse
+    // is moved. So we use the async key state given by queryMouseButtons() instead.
+    Qt::MouseButtons buttons = QWindowsMouseHandler::queryMouseButtons();
+
+    SCODE result = S_OK;
+    if (fEscapePressed || QWindowsDrag::isCanceled()) {
+        result = DRAGDROP_S_CANCEL;
+        buttons = Qt::NoButton;
+    } else {
+        if (buttons && !m_currentButtons) {
+            m_currentButtons = buttons;
+        } else if (m_currentButtons != buttons) { // Button changed: Complete Drop operation.
+            result = DRAGDROP_S_DROP;
+        }
+    }
+
+    switch (result) {
+        case DRAGDROP_S_DROP:
+        case DRAGDROP_S_CANCEL:
+            if (!m_windowUnderMouse.isNull() && m_mode != TouchDrag && fEscapePressed == FALSE
+                && buttons != lastButtons) {
+                // QTBUG 66447: Synthesize a mouse release to the window under mouse at
+                // start of the DnD operation as Windows does not send any.
+                const QPoint globalPos = QWindowsCursor::mousePosition();
+                const QPoint localPos = m_windowUnderMouse->handle()->mapFromGlobal(globalPos);
+                QWindowSystemInterface::handleMouseEvent(m_windowUnderMouse.data(),
+                                                         QPointF(localPos), QPointF(globalPos),
+                                                         QWindowsMouseHandler::queryMouseButtons(),
+                                                         Qt::LeftButton, QEvent::MouseButtonRelease);
+            }
+            m_currentButtons = Qt::NoButton;
+            break;
+
+        default:
+            QGuiApplication::processEvents();
+            break;
+    }
+
+    if (QWindowsContext::verbose > 1 || result != S_OK) {
+        qCDebug(lcQpaMime) << __FUNCTION__ << "fEscapePressed=" << fEscapePressed
+            << "grfKeyState=" << grfKeyState << "buttons" << m_currentButtons
+            << "returns 0x" << Qt::hex << int(result) << Qt::dec;
+    }
+    return ResultFromScode(result);
+}
+
+/*!
+    \brief Give feedback: Change cursor according to action.
+*/
+
+QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
+QWindowsOleDropSource::GiveFeedback(DWORD dwEffect)
+{
+    const Qt::DropAction action = translateToQDragDropAction(dwEffect);
+    m_drag->updateAction(action);
+
+    const qint64 currentCacheKey = m_drag->currentDrag()->dragCursor(action).cacheKey();
+    auto it = m_cursors.constFind(action);
+    // If a custom drag cursor is set, check its cache key to detect changes.
+    if (it == m_cursors.constEnd() || (currentCacheKey && currentCacheKey != it.value().cacheKey)) {
+        createCursors();
+        it = m_cursors.constFind(action);
+    }
+
+    if (it != m_cursors.constEnd()) {
+        const CursorEntry &e = it.value();
+        switch (m_mode) {
+        case MouseDrag:
+            SetCursor(e.cursor->handle());
+            break;
+        case TouchDrag:
+            // "Touch drag" with an unsuppressed cursor may happen with RDP (see createCursors())
+            if (QWindowsCursor::cursorState() != QWindowsCursor::State::Suppressed)
+                SetCursor(nullptr);
+            if (!m_touchDragWindow)
+                m_touchDragWindow = new QWindowsDragCursorWindow;
+            m_touchDragWindow->setPixmap(e.pixmap);
+            m_touchDragWindow->setFramePosition(QCursor::pos() - e.hotSpot);
+            if (!m_touchDragWindow->isVisible())
+                m_touchDragWindow->show();
+            break;
+        }
+        return ResultFromScode(S_OK);
+    }
+
+    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
+}
+
+/*!
+    \class QWindowsOleDropTarget
+    \brief Implementation of IDropTarget
+
+    To be registered for each window. Currently, drop sites
+    are enabled for top levels. The child window handling
+    (sending DragEnter/Leave, etc) is handled in here.
+
+    \sa QWindowsDrag
+    \internal
+*/
+
+QWindowsOleDropTarget::QWindowsOleDropTarget(QWindow *w) : m_window(w)
+{
+    qCDebug(lcQpaMime) << __FUNCTION__ << this << w;
+}
+
+QWindowsOleDropTarget::~QWindowsOleDropTarget()
+{
+    qCDebug(lcQpaMime) << __FUNCTION__ <<  this;
+}
+
+void QWindowsOleDropTarget::handleDrag(QWindow *window, DWORD grfKeyState,
+                                       const QPoint &point, LPDWORD pdwEffect)
+{
+    Q_ASSERT(window);
+    m_lastPoint = point;
+    m_lastKeyState = grfKeyState;
+
+    QWindowsDrag *windowsDrag = QWindowsDrag::instance();
+    const Qt::DropActions actions = translateToQDragDropActions(*pdwEffect);
+
+    lastModifiers = toQtKeyboardModifiers(grfKeyState);
+    lastButtons = QWindowsMouseHandler::queryMouseButtons();
+
+    const QPlatformDragQtResponse response =
+          QWindowSystemInterface::handleDrag(window, windowsDrag->dropData(),
+                                             m_lastPoint, actions,
+                                             lastButtons, lastModifiers);
+
+    m_answerRect = response.answerRect();
+    const Qt::DropAction action = response.acceptedAction();
+    if (response.isAccepted()) {
+        m_chosenEffect = translateToWinDragEffects(action);
+    } else {
+        m_chosenEffect = DROPEFFECT_NONE;
+    }
+    *pdwEffect = m_chosenEffect;
+    qCDebug(lcQpaMime) << __FUNCTION__ << m_window
+        << windowsDrag->dropData() << " supported actions=" << actions
+        << " mods=" << lastModifiers << " mouse=" << lastButtons
+        << " accepted: " << response.isAccepted() << action
+        << m_answerRect << " effect" << *pdwEffect;
+}
+
+QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
+QWindowsOleDropTarget::DragEnter(LPDATAOBJECT pDataObj, DWORD grfKeyState,
+                                 POINTL pt, LPDWORD pdwEffect)
+{
+    if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
+        dh->DragEnter(reinterpret_cast<HWND>(m_window->winId()), pDataObj, reinterpret_cast<POINT*>(&pt), *pdwEffect);
+
+    qCDebug(lcQpaMime) << __FUNCTION__ << "widget=" << m_window << " key=" << grfKeyState
+        << "pt=" << pt.x << pt.y;
+
+    QWindowsDrag::instance()->setDropDataObject(pDataObj);
+    pDataObj->AddRef();
+    const QPoint point = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y));
+    handleDrag(m_window, grfKeyState, point, pdwEffect);
+    return NOERROR;
+}
+
+QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
+QWindowsOleDropTarget::DragOver(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
+{
+    if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
+        dh->DragOver(reinterpret_cast<POINT*>(&pt), *pdwEffect);
+
+    qCDebug(lcQpaMime) << __FUNCTION__ << "m_window" << m_window << "key=" << grfKeyState
+        << "pt=" << pt.x << pt.y;
+    const QPoint tmpPoint = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y));
+    // see if we should compress this event
+    if ((tmpPoint == m_lastPoint || m_answerRect.contains(tmpPoint))
+        && m_lastKeyState == grfKeyState) {
+        *pdwEffect = m_chosenEffect;
+        qCDebug(lcQpaMime) << __FUNCTION__ << "compressed event";
+        return NOERROR;
+    }
+
+    handleDrag(m_window, grfKeyState, tmpPoint, pdwEffect);
+    return NOERROR;
+}
+
+QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
+QWindowsOleDropTarget::DragLeave()
+{
+    if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
+        dh->DragLeave();
+
+    qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window;
+
+    const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+    lastModifiers = keyMapper->queryKeyboardModifiers();
+    lastButtons = QWindowsMouseHandler::queryMouseButtons();
+
+    QWindowSystemInterface::handleDrag(m_window, nullptr, QPoint(), Qt::IgnoreAction,
+                                       Qt::NoButton, Qt::NoModifier);
+
+    if (!QDragManager::self()->source())
+        m_lastKeyState = 0;
+    QWindowsDrag::instance()->releaseDropDataObject();
+
+    return NOERROR;
+}
+
+#define KEY_STATE_BUTTON_MASK (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)
+
+QT_ENSURE_STACK_ALIGNED_FOR_SSE STDMETHODIMP
+QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState,
+                            POINTL pt, LPDWORD pdwEffect)
+{
+    if (IDropTargetHelper* dh = QWindowsDrag::instance()->dropHelper())
+        dh->Drop(pDataObj, reinterpret_cast<POINT*>(&pt), *pdwEffect);
+
+    qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window
+        << "keys=" << grfKeyState << "pt=" << pt.x << ',' << pt.y;
+
+    m_lastPoint = QWindowsGeometryHint::mapFromGlobal(m_window, QPoint(pt.x,pt.y));
+
+    QWindowsDrag *windowsDrag = QWindowsDrag::instance();
+
+    lastModifiers = toQtKeyboardModifiers(grfKeyState);
+    lastButtons = QWindowsMouseHandler::queryMouseButtons();
+
+    const QPlatformDropQtResponse response =
+        QWindowSystemInterface::handleDrop(m_window, windowsDrag->dropData(),
+                                           m_lastPoint,
+                                           translateToQDragDropActions(*pdwEffect),
+                                           lastButtons,
+                                           lastModifiers);
+
+    m_lastKeyState = grfKeyState;
+
+    if (response.isAccepted()) {
+        const Qt::DropAction action = response.acceptedAction();
+        if (action == Qt::MoveAction || action == Qt::TargetMoveAction) {
+            if (action == Qt::MoveAction)
+                m_chosenEffect = DROPEFFECT_MOVE;
+            else
+                m_chosenEffect = DROPEFFECT_COPY;
+            HGLOBAL hData = GlobalAlloc(0, sizeof(DWORD));
+            if (hData) {
+                auto *moveEffect = reinterpret_cast<DWORD *>(GlobalLock(hData));
+                *moveEffect = DROPEFFECT_MOVE;
+                GlobalUnlock(hData);
+                STGMEDIUM medium;
+                memset(&medium, 0, sizeof(STGMEDIUM));
+                medium.tymed = TYMED_HGLOBAL;
+                medium.hGlobal = hData;
+                FORMATETC format;
+                format.cfFormat = CLIPFORMAT(RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT));
+                format.tymed = TYMED_HGLOBAL;
+                format.ptd = nullptr;
+                format.dwAspect = 1;
+                format.lindex = -1;
+                windowsDrag->dropDataObject()->SetData(&format, &medium, true);
+            }
+        } else {
+            m_chosenEffect = translateToWinDragEffects(action);
+        }
+    } else {
+        m_chosenEffect = DROPEFFECT_NONE;
+    }
+    *pdwEffect = m_chosenEffect;
+
+    windowsDrag->releaseDropDataObject();
+    return NOERROR;
+}
+
+
+/*!
+    \class QWindowsDrag
+    \brief Windows drag implementation.
+    \internal
+*/
+
+bool QWindowsDrag::m_canceled = false;
+
+QWindowsDrag::QWindowsDrag() = default;
+
+QWindowsDrag::~QWindowsDrag()
+{
+    if (m_cachedDropTargetHelper)
+        m_cachedDropTargetHelper->Release();
+}
+
+/*!
+    \brief Return data for a drop in process. If it stems from a current drag, use a shortcut.
+*/
+
+QMimeData *QWindowsDrag::dropData()
+{
+    if (const QDrag *drag = currentDrag())
+        return drag->mimeData();
+    return &m_dropData;
+}
+
+/*!
+    \brief May be used to handle extended cursors functionality for drags from outside the app.
+*/
+IDropTargetHelper* QWindowsDrag::dropHelper() {
+    if (!m_cachedDropTargetHelper) {
+        CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER,
+                         IID_IDropTargetHelper,
+                         reinterpret_cast<void**>(&m_cachedDropTargetHelper));
+    }
+    return m_cachedDropTargetHelper;
+}
+
+// Workaround for DoDragDrop() not working with touch/pen input, causing DnD to hang until the mouse is moved.
+// We process pointer messages for touch/pen and generate mouse input through SendInput() to trigger DoDragDrop()
+static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect)
+{
+    QWindow *underMouse = QWindowsContext::instance()->windowUnderMouse();
+    const bool hasMouseCapture = underMouse && static_cast<QWindowsWindow *>(underMouse->handle())->hasMouseCapture();
+    const HWND hwnd = hasMouseCapture ? reinterpret_cast<HWND>(underMouse->winId()) : ::GetFocus();
+    bool starting = false;
+
+    for (;;) {
+        MSG msg{};
+        if (::GetMessage(&msg, hwnd, 0, 0) > 0) {
+
+            if (msg.message == WM_MOUSEMOVE) {
+
+                // Only consider the first simulated event, or actual mouse messages.
+                if (!starting && (msg.wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON | MK_XBUTTON1 | MK_XBUTTON2)) == 0)
+                    return E_FAIL;
+
+                return ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect);
+            }
+
+            if (msg.message == WM_POINTERUPDATE) {
+
+                const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
+
+                POINTER_INFO pointerInfo{};
+                if (!GetPointerInfo(pointerId, &pointerInfo))
+                    return E_FAIL;
+
+                if (pointerInfo.pointerFlags & POINTER_FLAG_PRIMARY) {
+
+                    DWORD flags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK | MOUSEEVENTF_MOVE;
+                    if (IS_POINTER_FIRSTBUTTON_WPARAM(msg.wParam))
+                        flags |= MOUSEEVENTF_LEFTDOWN;
+                    if (IS_POINTER_SECONDBUTTON_WPARAM(msg.wParam))
+                        flags |= MOUSEEVENTF_RIGHTDOWN;
+                    if (IS_POINTER_THIRDBUTTON_WPARAM(msg.wParam))
+                        flags |= MOUSEEVENTF_MIDDLEDOWN;
+
+                    if (!starting) {
+                        POINT pt{};
+                        if (::GetCursorPos(&pt)) {
+
+                            // Send mouse input that can generate a WM_MOUSEMOVE message.
+                            if ((flags & MOUSEEVENTF_LEFTDOWN || flags & MOUSEEVENTF_RIGHTDOWN || flags & MOUSEEVENTF_MIDDLEDOWN)
+                                && (pt.x != pointerInfo.ptPixelLocation.x || pt.y != pointerInfo.ptPixelLocation.y)) {
+
+                                const int origin_x = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
+                                const int origin_y = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
+                                const int virt_w = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
+                                const int virt_h = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
+                                const int virt_x = pointerInfo.ptPixelLocation.x - origin_x;
+                                const int virt_y = pointerInfo.ptPixelLocation.y - origin_y;
+
+                                INPUT input{};
+                                input.type = INPUT_MOUSE;
+                                input.mi.dx = static_cast<DWORD>(virt_x * (65535.0 / virt_w));
+                                input.mi.dy = static_cast<DWORD>(virt_y * (65535.0 / virt_h));
+                                input.mi.dwFlags = flags;
+
+                                ::SendInput(1, &input, sizeof(input));
+                                starting = true;
+                            }
+                        }
+                    }
+                }
+            } else {
+                // Handle other messages.
+                qWindowsWndProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
+
+                if (msg.message == WM_POINTERLEAVE)
+                    return E_FAIL;
+            }
+        } else {
+            return E_FAIL;
+        }
+    }
+}
+
+Qt::DropAction QWindowsDrag::drag(QDrag *drag)
+{
+    // TODO: Accessibility handling?
+    QMimeData *dropData = drag->mimeData();
+    Qt::DropAction dragResult = Qt::IgnoreAction;
+
+    DWORD resultEffect;
+    QWindowsDrag::m_canceled = false;
+    auto *windowDropSource = new QWindowsOleDropSource(this);
+    windowDropSource->createCursors();
+    auto *dropDataObject = new QWindowsDropDataObject(dropData);
+    const Qt::DropActions possibleActions = drag->supportedActions();
+    const DWORD allowedEffects = translateToWinDragEffects(possibleActions);
+    qCDebug(lcQpaMime) << '>' << __FUNCTION__ << "possible Actions=0x"
+        << Qt::hex << int(possibleActions) << "effects=0x" << allowedEffects << Qt::dec;
+    const HRESULT r = startDoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect);
+    const DWORD  reportedPerformedEffect = dropDataObject->reportedPerformedEffect();
+    if (r == DRAGDROP_S_DROP) {
+        if (reportedPerformedEffect == DROPEFFECT_MOVE && resultEffect != DROPEFFECT_MOVE) {
+            dragResult = Qt::TargetMoveAction;
+            resultEffect = DROPEFFECT_MOVE;
+        } else {
+            dragResult = translateToQDragDropAction(resultEffect);
+        }
+        // Force it to be a copy if an unsupported operation occurred.
+        // This indicates a bug in the drop target.
+        if (resultEffect != DROPEFFECT_NONE && !(resultEffect & allowedEffects)) {
+            qWarning("%s: Forcing Qt::CopyAction", __FUNCTION__);
+            dragResult = Qt::CopyAction;
+        }
+    }
+    // clean up
+    dropDataObject->releaseQt();
+    dropDataObject->Release();        // Will delete obj if refcount becomes 0
+    windowDropSource->Release();        // Will delete src if refcount becomes 0
+    qCDebug(lcQpaMime) << '<' << __FUNCTION__ << Qt::hex << "allowedEffects=0x" << allowedEffects
+        << "reportedPerformedEffect=0x" << reportedPerformedEffect
+        <<  " resultEffect=0x" << resultEffect << "hr=0x" << int(r) << Qt::dec << "dropAction=" << dragResult;
+    return dragResult;
+}
+
+QWindowsDrag *QWindowsDrag::instance()
+{
+    return static_cast<QWindowsDrag *>(QWindowsIntegration::instance()->drag());
+}
+
+void QWindowsDrag::releaseDropDataObject()
+{
+    qCDebug(lcQpaMime) << __FUNCTION__ << m_dropDataObject;
+    if (m_dropDataObject) {
+        m_dropDataObject->Release();
+        m_dropDataObject = nullptr;
+    }
+}
+
+QT_END_NAMESPACE
diff --git a/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp b/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp
index af1230d240..c7e9ecbfec 100644
--- a/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp
+++ b/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp
@@ -732,15 +732,28 @@ static inline QString messageKeyText(const MSG &msg)
 
 [[nodiscard]] static inline int getTitleBarHeight(const HWND hwnd)
 {
-    const UINT dpi = GetDpiForWindow(hwnd);
-    const int captionHeight = GetSystemMetricsForDpi(SM_CYCAPTION, dpi);
-    if (IsZoomed(hwnd))
-        return captionHeight;
-    // The frame height should also be taken into account if the window
-    // is not maximized.
-    const int frameHeight = GetSystemMetricsForDpi(SM_CYSIZEFRAME, dpi)
-                            + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
-    return captionHeight + frameHeight;
+    if (QWindowsContext::user32dll.getDpiForWindow && QWindowsContext::user32dll.getSystemMetricsForDpi)
+    {
+        const UINT dpi = QWindowsContext::user32dll.getDpiForWindow(hwnd);
+        const int captionHeight = QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CYCAPTION, dpi);
+        if (IsZoomed(hwnd))
+            return captionHeight;
+        // The frame height should also be taken into account if the window
+        // is not maximized.
+        const int frameHeight = QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CYSIZEFRAME, dpi)
+                                + QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
+        return captionHeight + frameHeight;    
+    }
+    else
+    {
+        const int captionHeight = GetSystemMetrics(SM_CYCAPTION);
+        if (IsZoomed(hwnd))
+            return captionHeight;
+        // The frame height should also be taken into account if the window
+        // is not maximized.
+        const int frameHeight = GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
+        return captionHeight + frameHeight;    
+    }
 }
 
 [[nodiscard]] static inline bool isSystemMenuOffsetNeeded(const Qt::WindowFlags flags)
diff --git a/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp.orig b/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp.orig
new file mode 100644
index 0000000000..af1230d240
--- /dev/null
+++ b/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp.orig
@@ -0,0 +1,1384 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindowskeymapper.h"
+#include "qwindowscontext.h"
+#include "qwindowsintegration.h"
+#include "qwindowswindow.h"
+#include "qwindowsinputcontext.h"
+
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qwindow.h>
+#include <qpa/qwindowsysteminterface.h>
+#include <private/qguiapplication_p.h>
+#include <private/qhighdpiscaling_p.h>
+#include <QtGui/qevent.h>
+#include <QtGui/private/qwindowsguieventdispatcher_p.h>
+#include <QtCore/private/qdebug_p.h>
+#include <QtCore/private/qtools_p.h>
+
+#if defined(WM_APPCOMMAND)
+#  ifndef FAPPCOMMAND_MOUSE
+#    define FAPPCOMMAND_MOUSE 0x8000
+#  endif
+#  ifndef FAPPCOMMAND_KEY
+#    define FAPPCOMMAND_KEY   0
+#  endif
+#  ifndef FAPPCOMMAND_OEM
+#    define FAPPCOMMAND_OEM   0x1000
+#  endif
+#  ifndef FAPPCOMMAND_MASK
+#    define FAPPCOMMAND_MASK  0xF000
+#  endif
+#  ifndef GET_APPCOMMAND_LPARAM
+#    define GET_APPCOMMAND_LPARAM(lParam) ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK))
+#  endif
+#  ifndef GET_DEVICE_LPARAM
+#    define GET_DEVICE_LPARAM(lParam)     ((WORD)(HIWORD(lParam) & FAPPCOMMAND_MASK))
+#  endif
+#  ifndef GET_MOUSEORKEY_LPARAM
+#    define GET_MOUSEORKEY_LPARAM         GET_DEVICE_LPARAM
+#  endif
+#  ifndef GET_FLAGS_LPARAM
+#    define GET_FLAGS_LPARAM(lParam)      (LOWORD(lParam))
+#  endif
+#  ifndef GET_KEYSTATE_LPARAM
+#    define GET_KEYSTATE_LPARAM(lParam)   GET_FLAGS_LPARAM(lParam)
+#  endif
+#endif
+
+QT_BEGIN_NAMESPACE
+
+/*!
+    \class QWindowsKeyMapper
+    \brief Translates Windows keys to QWindowSystemInterface events.
+    \internal
+
+    In addition, handles some special keys to display system menus, etc.
+    The code originates from \c qkeymapper_win.cpp.
+*/
+
+static void clearKeyRecorderOnApplicationInActive(Qt::ApplicationState state);
+
+QWindowsKeyMapper::QWindowsKeyMapper()
+    : m_useRTLExtensions(false), m_keyGrabber(nullptr)
+{
+    memset(keyLayout, 0, sizeof(keyLayout));
+    auto *app = static_cast<QGuiApplication *>(QGuiApplication::instance());
+    QObject::connect(app, &QGuiApplication::applicationStateChanged,
+                     app, clearKeyRecorderOnApplicationInActive);
+    changeKeyboard();
+}
+
+QWindowsKeyMapper::~QWindowsKeyMapper()= default;
+
+#ifndef LANG_PASHTO
+#define LANG_PASHTO 0x63
+#endif
+#ifndef LANG_SYRIAC
+#define LANG_SYRIAC 0x5a
+#endif
+#ifndef LANG_DIVEHI
+#define LANG_DIVEHI 0x65
+#endif
+#ifndef VK_OEM_PLUS
+#define VK_OEM_PLUS 0xBB
+#endif
+#ifndef VK_OEM_3
+#define VK_OEM_3 0xC0
+#endif
+
+// Get scancode from the given message
+static constexpr quint32 getScancode(const MSG &msg)
+{
+    const auto keyFlags = HIWORD(msg.lParam);
+    quint32 scancode = LOBYTE(keyFlags);
+    // if extended-key flag is on, the scan code consists of a sequence of two bytes,
+    // where the first byte has a value of 0xe0.
+    if ((keyFlags & KF_EXTENDED) != 0)
+        scancode |= 0xE000;
+    return scancode;
+}
+
+// Key recorder ------------------------------------------------------------------------[ start ] --
+struct KeyRecord {
+    KeyRecord(int c, int a, int s, const QString &t) : code(c), ascii(a), state(s), text(t) {}
+    KeyRecord() {}
+
+    int code;
+    int ascii;
+    int state;
+    QString text;
+};
+
+// We need to record the pressed keys in order to decide, whether the key event is an autorepeat
+// event. As soon as its state changes, the chain of autorepeat events will be broken.
+static const int QT_MAX_KEY_RECORDINGS = 64; // User has LOTS of fingers...
+struct KeyRecorder
+{
+    inline KeyRecord *findKey(int code, bool remove);
+    inline void storeKey(int code, int ascii, int state, const QString& text);
+    inline void clearKeys();
+
+    int nrecs = 0;
+    KeyRecord deleted_record; // A copy of last entry removed from records[]
+    KeyRecord records[QT_MAX_KEY_RECORDINGS];
+};
+static KeyRecorder key_recorder;
+
+static void clearKeyRecorderOnApplicationInActive(Qt::ApplicationState state)
+{
+    if (state == Qt::ApplicationInactive)
+        key_recorder.clearKeys();
+}
+
+KeyRecord *KeyRecorder::findKey(int code, bool remove)
+{
+    KeyRecord *result = nullptr;
+    for (int i = 0; i < nrecs; ++i) {
+        if (records[i].code == code) {
+            if (remove) {
+                deleted_record = records[i];
+                // Move rest down, and decrease count
+                while (i + 1 < nrecs) {
+                    records[i] = records[i + 1];
+                    ++i;
+                }
+                --nrecs;
+                result = &deleted_record;
+            } else {
+                result = &records[i];
+            }
+            break;
+        }
+    }
+    return result;
+}
+
+void KeyRecorder::storeKey(int code, int ascii, int state, const QString& text)
+{
+    Q_ASSERT_X(nrecs != QT_MAX_KEY_RECORDINGS,
+               "Internal KeyRecorder",
+               "Keyboard recorder buffer overflow, consider increasing QT_MAX_KEY_RECORDINGS");
+
+    if (nrecs == QT_MAX_KEY_RECORDINGS) {
+        qWarning("Qt: Internal keyboard buffer overflow");
+        return;
+    }
+    records[nrecs++] = KeyRecord(code,ascii,state,text);
+}
+
+void KeyRecorder::clearKeys()
+{
+    nrecs = 0;
+}
+// Key recorder --------------------------------------------------------------------------[ end ] --
+
+
+// Key translation ---------------------------------------------------------------------[ start ] --
+// Meaning of values:
+//             0 = Character output key, needs keyboard driver mapping
+//   Key_unknown = Unknown Virtual Key, no translation possible, ignore
+static const uint KeyTbl[] = { // Keyboard mapping table
+                        // Dec |  Hex | Windows Virtual key
+    Qt::Key_unknown,    //   0   0x00
+    Qt::Key_unknown,    //   1   0x01   VK_LBUTTON          | Left mouse button
+    Qt::Key_unknown,    //   2   0x02   VK_RBUTTON          | Right mouse button
+    Qt::Key_Cancel,     //   3   0x03   VK_CANCEL           | Control-Break processing
+    Qt::Key_unknown,    //   4   0x04   VK_MBUTTON          | Middle mouse button
+    Qt::Key_unknown,    //   5   0x05   VK_XBUTTON1         | X1 mouse button
+    Qt::Key_unknown,    //   6   0x06   VK_XBUTTON2         | X2 mouse button
+    Qt::Key_unknown,    //   7   0x07   -- unassigned --
+    Qt::Key_Backspace,  //   8   0x08   VK_BACK             | BackSpace key
+    Qt::Key_Tab,        //   9   0x09   VK_TAB              | Tab key
+    Qt::Key_unknown,    //  10   0x0A   -- reserved --
+    Qt::Key_unknown,    //  11   0x0B   -- reserved --
+    Qt::Key_Clear,      //  12   0x0C   VK_CLEAR            | Clear key
+    Qt::Key_Return,     //  13   0x0D   VK_RETURN           | Enter key
+    Qt::Key_unknown,    //  14   0x0E   -- unassigned --
+    Qt::Key_unknown,    //  15   0x0F   -- unassigned --
+    Qt::Key_Shift,      //  16   0x10   VK_SHIFT            | Shift key
+    Qt::Key_Control,    //  17   0x11   VK_CONTROL          | Ctrl key
+    Qt::Key_Alt,        //  18   0x12   VK_MENU             | Alt key
+    Qt::Key_Pause,      //  19   0x13   VK_PAUSE            | Pause key
+    Qt::Key_CapsLock,   //  20   0x14   VK_CAPITAL          | Caps-Lock
+    Qt::Key_unknown,    //  21   0x15   VK_KANA / VK_HANGUL | IME Kana or Hangul mode
+    Qt::Key_unknown,    //  22   0x16   -- unassigned --
+    Qt::Key_unknown,    //  23   0x17   VK_JUNJA            | IME Junja mode
+    Qt::Key_unknown,    //  24   0x18   VK_FINAL            | IME final mode
+    Qt::Key_unknown,    //  25   0x19   VK_HANJA / VK_KANJI | IME Hanja or Kanji mode
+    Qt::Key_unknown,    //  26   0x1A   -- unassigned --
+    Qt::Key_Escape,     //  27   0x1B   VK_ESCAPE           | Esc key
+    Qt::Key_unknown,    //  28   0x1C   VK_CONVERT          | IME convert
+    Qt::Key_unknown,    //  29   0x1D   VK_NONCONVERT       | IME non-convert
+    Qt::Key_unknown,    //  30   0x1E   VK_ACCEPT           | IME accept
+    Qt::Key_Mode_switch,//  31   0x1F   VK_MODECHANGE       | IME mode change request
+    Qt::Key_Space,      //  32   0x20   VK_SPACE            | Spacebar
+    Qt::Key_PageUp,     //  33   0x21   VK_PRIOR            | Page Up key
+    Qt::Key_PageDown,   //  34   0x22   VK_NEXT             | Page Down key
+    Qt::Key_End,        //  35   0x23   VK_END              | End key
+    Qt::Key_Home,       //  36   0x24   VK_HOME             | Home key
+    Qt::Key_Left,       //  37   0x25   VK_LEFT             | Left arrow key
+    Qt::Key_Up,         //  38   0x26   VK_UP               | Up arrow key
+    Qt::Key_Right,      //  39   0x27   VK_RIGHT            | Right arrow key
+    Qt::Key_Down,       //  40   0x28   VK_DOWN             | Down arrow key
+    Qt::Key_Select,     //  41   0x29   VK_SELECT           | Select key
+    Qt::Key_Printer,    //  42   0x2A   VK_PRINT            | Print key
+    Qt::Key_Execute,    //  43   0x2B   VK_EXECUTE          | Execute key
+    Qt::Key_Print,      //  44   0x2C   VK_SNAPSHOT         | Print Screen key
+    Qt::Key_Insert,     //  45   0x2D   VK_INSERT           | Ins key
+    Qt::Key_Delete,     //  46   0x2E   VK_DELETE           | Del key
+    Qt::Key_Help,       //  47   0x2F   VK_HELP             | Help key
+    0,                  //  48   0x30   (VK_0)              | 0 key
+    0,                  //  49   0x31   (VK_1)              | 1 key
+    0,                  //  50   0x32   (VK_2)              | 2 key
+    0,                  //  51   0x33   (VK_3)              | 3 key
+    0,                  //  52   0x34   (VK_4)              | 4 key
+    0,                  //  53   0x35   (VK_5)              | 5 key
+    0,                  //  54   0x36   (VK_6)              | 6 key
+    0,                  //  55   0x37   (VK_7)              | 7 key
+    0,                  //  56   0x38   (VK_8)              | 8 key
+    0,                  //  57   0x39   (VK_9)              | 9 key
+    Qt::Key_unknown,    //  58   0x3A   -- unassigned --
+    Qt::Key_unknown,    //  59   0x3B   -- unassigned --
+    Qt::Key_unknown,    //  60   0x3C   -- unassigned --
+    Qt::Key_unknown,    //  61   0x3D   -- unassigned --
+    Qt::Key_unknown,    //  62   0x3E   -- unassigned --
+    Qt::Key_unknown,    //  63   0x3F   -- unassigned --
+    Qt::Key_unknown,    //  64   0x40   -- unassigned --
+    0,                  //  65   0x41   (VK_A)              | A key
+    0,                  //  66   0x42   (VK_B)              | B key
+    0,                  //  67   0x43   (VK_C)              | C key
+    0,                  //  68   0x44   (VK_D)              | D key
+    0,                  //  69   0x45   (VK_E)              | E key
+    0,                  //  70   0x46   (VK_F)              | F key
+    0,                  //  71   0x47   (VK_G)              | G key
+    0,                  //  72   0x48   (VK_H)              | H key
+    0,                  //  73   0x49   (VK_I)              | I key
+    0,                  //  74   0x4A   (VK_J)              | J key
+    0,                  //  75   0x4B   (VK_K)              | K key
+    0,                  //  76   0x4C   (VK_L)              | L key
+    0,                  //  77   0x4D   (VK_M)              | M key
+    0,                  //  78   0x4E   (VK_N)              | N key
+    0,                  //  79   0x4F   (VK_O)              | O key
+    0,                  //  80   0x50   (VK_P)              | P key
+    0,                  //  81   0x51   (VK_Q)              | Q key
+    0,                  //  82   0x52   (VK_R)              | R key
+    0,                  //  83   0x53   (VK_S)              | S key
+    0,                  //  84   0x54   (VK_T)              | T key
+    0,                  //  85   0x55   (VK_U)              | U key
+    0,                  //  86   0x56   (VK_V)              | V key
+    0,                  //  87   0x57   (VK_W)              | W key
+    0,                  //  88   0x58   (VK_X)              | X key
+    0,                  //  89   0x59   (VK_Y)              | Y key
+    0,                  //  90   0x5A   (VK_Z)              | Z key
+    Qt::Key_Meta,       //  91   0x5B   VK_LWIN             | Left Windows  - MS Natural kbd
+    Qt::Key_Meta,       //  92   0x5C   VK_RWIN             | Right Windows - MS Natural kbd
+    Qt::Key_Menu,       //  93   0x5D   VK_APPS             | Application key-MS Natural kbd
+    Qt::Key_unknown,    //  94   0x5E   -- reserved --
+    Qt::Key_Sleep,      //  95   0x5F   VK_SLEEP
+    Qt::Key_0,          //  96   0x60   VK_NUMPAD0          | Numeric keypad 0 key
+    Qt::Key_1,          //  97   0x61   VK_NUMPAD1          | Numeric keypad 1 key
+    Qt::Key_2,          //  98   0x62   VK_NUMPAD2          | Numeric keypad 2 key
+    Qt::Key_3,          //  99   0x63   VK_NUMPAD3          | Numeric keypad 3 key
+    Qt::Key_4,          // 100   0x64   VK_NUMPAD4          | Numeric keypad 4 key
+    Qt::Key_5,          // 101   0x65   VK_NUMPAD5          | Numeric keypad 5 key
+    Qt::Key_6,          // 102   0x66   VK_NUMPAD6          | Numeric keypad 6 key
+    Qt::Key_7,          // 103   0x67   VK_NUMPAD7          | Numeric keypad 7 key
+    Qt::Key_8,          // 104   0x68   VK_NUMPAD8          | Numeric keypad 8 key
+    Qt::Key_9,          // 105   0x69   VK_NUMPAD9          | Numeric keypad 9 key
+    Qt::Key_Asterisk,   // 106   0x6A   VK_MULTIPLY         | Multiply key
+    Qt::Key_Plus,       // 107   0x6B   VK_ADD              | Add key
+    Qt::Key_unknown,    // 108   0x6C   VK_SEPARATOR        | Separator key (locale-dependent)
+    Qt::Key_Minus,      // 109   0x6D   VK_SUBTRACT         | Subtract key
+    Qt::Key_unknown,    // 110   0x6E   VK_DECIMAL          | Decimal key (locale-dependent)
+    Qt::Key_Slash,      // 111   0x6F   VK_DIVIDE           | Divide key
+    Qt::Key_F1,         // 112   0x70   VK_F1               | F1 key
+    Qt::Key_F2,         // 113   0x71   VK_F2               | F2 key
+    Qt::Key_F3,         // 114   0x72   VK_F3               | F3 key
+    Qt::Key_F4,         // 115   0x73   VK_F4               | F4 key
+    Qt::Key_F5,         // 116   0x74   VK_F5               | F5 key
+    Qt::Key_F6,         // 117   0x75   VK_F6               | F6 key
+    Qt::Key_F7,         // 118   0x76   VK_F7               | F7 key
+    Qt::Key_F8,         // 119   0x77   VK_F8               | F8 key
+    Qt::Key_F9,         // 120   0x78   VK_F9               | F9 key
+    Qt::Key_F10,        // 121   0x79   VK_F10              | F10 key
+    Qt::Key_F11,        // 122   0x7A   VK_F11              | F11 key
+    Qt::Key_F12,        // 123   0x7B   VK_F12              | F12 key
+    Qt::Key_F13,        // 124   0x7C   VK_F13              | F13 key
+    Qt::Key_F14,        // 125   0x7D   VK_F14              | F14 key
+    Qt::Key_F15,        // 126   0x7E   VK_F15              | F15 key
+    Qt::Key_F16,        // 127   0x7F   VK_F16              | F16 key
+    Qt::Key_F17,        // 128   0x80   VK_F17              | F17 key
+    Qt::Key_F18,        // 129   0x81   VK_F18              | F18 key
+    Qt::Key_F19,        // 130   0x82   VK_F19              | F19 key
+    Qt::Key_F20,        // 131   0x83   VK_F20              | F20 key
+    Qt::Key_F21,        // 132   0x84   VK_F21              | F21 key
+    Qt::Key_F22,        // 133   0x85   VK_F22              | F22 key
+    Qt::Key_F23,        // 134   0x86   VK_F23              | F23 key
+    Qt::Key_F24,        // 135   0x87   VK_F24              | F24 key
+    Qt::Key_unknown,    // 136   0x88   -- unassigned --
+    Qt::Key_unknown,    // 137   0x89   -- unassigned --
+    Qt::Key_unknown,    // 138   0x8A   -- unassigned --
+    Qt::Key_unknown,    // 139   0x8B   -- unassigned --
+    Qt::Key_unknown,    // 140   0x8C   -- unassigned --
+    Qt::Key_unknown,    // 141   0x8D   -- unassigned --
+    Qt::Key_unknown,    // 142   0x8E   -- unassigned --
+    Qt::Key_unknown,    // 143   0x8F   -- unassigned --
+    Qt::Key_NumLock,    // 144   0x90   VK_NUMLOCK          | Num Lock key
+    Qt::Key_ScrollLock, // 145   0x91   VK_SCROLL           | Scroll Lock key
+                        // Fujitsu/OASYS kbd --------------------
+    0, //Qt::Key_Jisho, // 146   0x92   VK_OEM_FJ_JISHO     | 'Dictionary' key /
+                        //              VK_OEM_NEC_EQUAL  = key on numpad on NEC PC-9800 kbd
+    Qt::Key_Massyo,     // 147   0x93   VK_OEM_FJ_MASSHOU   | 'Unregister word' key
+    Qt::Key_Touroku,    // 148   0x94   VK_OEM_FJ_TOUROKU   | 'Register word' key
+    0, //Qt::Key_Oyayubi_Left,//149   0x95  VK_OEM_FJ_LOYA  | 'Left OYAYUBI' key
+    0, //Qt::Key_Oyayubi_Right,//150  0x96  VK_OEM_FJ_ROYA  | 'Right OYAYUBI' key
+    Qt::Key_unknown,    // 151   0x97   -- unassigned --
+    Qt::Key_unknown,    // 152   0x98   -- unassigned --
+    Qt::Key_unknown,    // 153   0x99   -- unassigned --
+    Qt::Key_unknown,    // 154   0x9A   -- unassigned --
+    Qt::Key_unknown,    // 155   0x9B   -- unassigned --
+    Qt::Key_unknown,    // 156   0x9C   -- unassigned --
+    Qt::Key_unknown,    // 157   0x9D   -- unassigned --
+    Qt::Key_unknown,    // 158   0x9E   -- unassigned --
+    Qt::Key_unknown,    // 159   0x9F   -- unassigned --
+    Qt::Key_Shift,      // 160   0xA0   VK_LSHIFT           | Left Shift key
+    Qt::Key_Shift,      // 161   0xA1   VK_RSHIFT           | Right Shift key
+    Qt::Key_Control,    // 162   0xA2   VK_LCONTROL         | Left Ctrl key
+    Qt::Key_Control,    // 163   0xA3   VK_RCONTROL         | Right Ctrl key
+    Qt::Key_Alt,        // 164   0xA4   VK_LMENU            | Left Menu key
+    Qt::Key_Alt,        // 165   0xA5   VK_RMENU            | Right Menu key
+    Qt::Key_Back,       // 166   0xA6   VK_BROWSER_BACK     | Browser Back key
+    Qt::Key_Forward,    // 167   0xA7   VK_BROWSER_FORWARD  | Browser Forward key
+    Qt::Key_Refresh,    // 168   0xA8   VK_BROWSER_REFRESH  | Browser Refresh key
+    Qt::Key_Stop,       // 169   0xA9   VK_BROWSER_STOP     | Browser Stop key
+    Qt::Key_Search,     // 170   0xAA   VK_BROWSER_SEARCH   | Browser Search key
+    Qt::Key_Favorites,  // 171   0xAB   VK_BROWSER_FAVORITES| Browser Favorites key
+    Qt::Key_HomePage,   // 172   0xAC   VK_BROWSER_HOME     | Browser Start and Home key
+    Qt::Key_VolumeMute, // 173   0xAD   VK_VOLUME_MUTE      | Volume Mute key
+    Qt::Key_VolumeDown, // 174   0xAE   VK_VOLUME_DOWN      | Volume Down key
+    Qt::Key_VolumeUp,   // 175   0xAF   VK_VOLUME_UP        | Volume Up key
+    Qt::Key_MediaNext,  // 176   0xB0   VK_MEDIA_NEXT_TRACK | Next Track key
+    Qt::Key_MediaPrevious, //177 0xB1   VK_MEDIA_PREV_TRACK | Previous Track key
+    Qt::Key_MediaStop,  // 178   0xB2   VK_MEDIA_STOP       | Stop Media key
+    Qt::Key_MediaTogglePlayPause,
+                        // 179   0xB3   VK_MEDIA_PLAY_PAUSE | Play/Pause Media key
+    Qt::Key_LaunchMail, // 180   0xB4   VK_LAUNCH_MAIL      | Start Mail key
+    Qt::Key_LaunchMedia,// 181   0xB5   VK_LAUNCH_MEDIA_SELECT Select Media key
+    Qt::Key_Launch0,    // 182   0xB6   VK_LAUNCH_APP1      | Start Application 1 key
+    Qt::Key_Launch1,    // 183   0xB7   VK_LAUNCH_APP2      | Start Application 2 key
+    Qt::Key_unknown,    // 184   0xB8   -- reserved --
+    Qt::Key_unknown,    // 185   0xB9   -- reserved --
+    0,                  // 186   0xBA   VK_OEM_1            | ';:' for US
+    0,                  // 187   0xBB   VK_OEM_PLUS         | '+' any country
+    0,                  // 188   0xBC   VK_OEM_COMMA        | ',' any country
+    0,                  // 189   0xBD   VK_OEM_MINUS        | '-' any country
+    0,                  // 190   0xBE   VK_OEM_PERIOD       | '.' any country
+    0,                  // 191   0xBF   VK_OEM_2            | '/?' for US
+    0,                  // 192   0xC0   VK_OEM_3            | '`~' for US
+    Qt::Key_unknown,    // 193   0xC1   -- reserved --
+    Qt::Key_unknown,    // 194   0xC2   -- reserved --
+    Qt::Key_unknown,    // 195   0xC3   -- reserved --
+    Qt::Key_unknown,    // 196   0xC4   -- reserved --
+    Qt::Key_unknown,    // 197   0xC5   -- reserved --
+    Qt::Key_unknown,    // 198   0xC6   -- reserved --
+    Qt::Key_unknown,    // 199   0xC7   -- reserved --
+    Qt::Key_unknown,    // 200   0xC8   -- reserved --
+    Qt::Key_unknown,    // 201   0xC9   -- reserved --
+    Qt::Key_unknown,    // 202   0xCA   -- reserved --
+    Qt::Key_unknown,    // 203   0xCB   -- reserved --
+    Qt::Key_unknown,    // 204   0xCC   -- reserved --
+    Qt::Key_unknown,    // 205   0xCD   -- reserved --
+    Qt::Key_unknown,    // 206   0xCE   -- reserved --
+    Qt::Key_unknown,    // 207   0xCF   -- reserved --
+    Qt::Key_unknown,    // 208   0xD0   -- reserved --
+    Qt::Key_unknown,    // 209   0xD1   -- reserved --
+    Qt::Key_unknown,    // 210   0xD2   -- reserved --
+    Qt::Key_unknown,    // 211   0xD3   -- reserved --
+    Qt::Key_unknown,    // 212   0xD4   -- reserved --
+    Qt::Key_unknown,    // 213   0xD5   -- reserved --
+    Qt::Key_unknown,    // 214   0xD6   -- reserved --
+    Qt::Key_unknown,    // 215   0xD7   -- reserved --
+    Qt::Key_unknown,    // 216   0xD8   -- unassigned --
+    Qt::Key_unknown,    // 217   0xD9   -- unassigned --
+    Qt::Key_unknown,    // 218   0xDA   -- unassigned --
+    0,                  // 219   0xDB   VK_OEM_4            | '[{' for US
+    0,                  // 220   0xDC   VK_OEM_5            | '\|' for US
+    0,                  // 221   0xDD   VK_OEM_6            | ']}' for US
+    0,                  // 222   0xDE   VK_OEM_7            | ''"' for US
+    0,                  // 223   0xDF   VK_OEM_8
+    Qt::Key_unknown,    // 224   0xE0   -- reserved --
+    Qt::Key_unknown,    // 225   0xE1   VK_OEM_AX           | 'AX' key on Japanese AX kbd
+    Qt::Key_unknown,    // 226   0xE2   VK_OEM_102          | "<>" or "\|" on RT 102-key kbd
+    Qt::Key_unknown,    // 227   0xE3   VK_ICO_HELP         | Help key on ICO
+    Qt::Key_unknown,    // 228   0xE4   VK_ICO_00           | 00 key on ICO
+    Qt::Key_unknown,    // 229   0xE5   VK_PROCESSKEY       | IME Process key
+    Qt::Key_unknown,    // 230   0xE6   VK_ICO_CLEAR        |
+    Qt::Key_unknown,    // 231   0xE7   VK_PACKET           | Unicode char as keystrokes
+    Qt::Key_unknown,    // 232   0xE8   -- unassigned --
+                        // Nokia/Ericsson definitions ---------------
+    Qt::Key_unknown,    // 233   0xE9   VK_OEM_RESET
+    Qt::Key_unknown,    // 234   0xEA   VK_OEM_JUMP
+    Qt::Key_unknown,    // 235   0xEB   VK_OEM_PA1
+    Qt::Key_unknown,    // 236   0xEC   VK_OEM_PA2
+    Qt::Key_unknown,    // 237   0xED   VK_OEM_PA3
+    Qt::Key_unknown,    // 238   0xEE   VK_OEM_WSCTRL
+    Qt::Key_unknown,    // 239   0xEF   VK_OEM_CUSEL
+    Qt::Key_unknown,    // 240   0xF0   VK_OEM_ATTN
+    Qt::Key_unknown,    // 241   0xF1   VK_OEM_FINISH
+    Qt::Key_unknown,    // 242   0xF2   VK_OEM_COPY
+    Qt::Key_unknown,    // 243   0xF3   VK_OEM_AUTO
+    Qt::Key_unknown,    // 244   0xF4   VK_OEM_ENLW
+    Qt::Key_unknown,    // 245   0xF5   VK_OEM_BACKTAB
+    Qt::Key_unknown,    // 246   0xF6   VK_ATTN             | Attn key
+    Qt::Key_unknown,    // 247   0xF7   VK_CRSEL            | CrSel key
+    Qt::Key_unknown,    // 248   0xF8   VK_EXSEL            | ExSel key
+    Qt::Key_unknown,    // 249   0xF9   VK_EREOF            | Erase EOF key
+    Qt::Key_Play,       // 250   0xFA   VK_PLAY             | Play key
+    Qt::Key_Zoom,       // 251   0xFB   VK_ZOOM             | Zoom key
+    Qt::Key_unknown,    // 252   0xFC   VK_NONAME           | Reserved
+    Qt::Key_unknown,    // 253   0xFD   VK_PA1              | PA1 key
+    Qt::Key_Clear,      // 254   0xFE   VK_OEM_CLEAR        | Clear key
+    0
+};
+
+static const uint CmdTbl[] = { // Multimedia keys mapping table
+                            // Dec |  Hex | AppCommand
+    Qt::Key_unknown,        //   0   0x00
+    Qt::Key_Back,           //   1   0x01   APPCOMMAND_BROWSER_BACKWARD
+    Qt::Key_Forward,        //   2   0x02   APPCOMMAND_BROWSER_FORWARD
+    Qt::Key_Refresh,        //   3   0x03   APPCOMMAND_BROWSER_REFRESH
+    Qt::Key_Stop,           //   4   0x04   APPCOMMAND_BROWSER_STOP
+    Qt::Key_Search,         //   5   0x05   APPCOMMAND_BROWSER_SEARCH
+    Qt::Key_Favorites,      //   6   0x06   APPCOMMAND_BROWSER_FAVORITES
+    Qt::Key_Home,           //   7   0x07   APPCOMMAND_BROWSER_HOME
+    Qt::Key_VolumeMute,     //   8   0x08   APPCOMMAND_VOLUME_MUTE
+    Qt::Key_VolumeDown,     //   9   0x09   APPCOMMAND_VOLUME_DOWN
+    Qt::Key_VolumeUp,       //  10   0x0a   APPCOMMAND_VOLUME_UP
+    Qt::Key_MediaNext,      //  11   0x0b   APPCOMMAND_MEDIA_NEXTTRACK
+    Qt::Key_MediaPrevious,  //  12   0x0c   APPCOMMAND_MEDIA_PREVIOUSTRACK
+    Qt::Key_MediaStop,      //  13   0x0d   APPCOMMAND_MEDIA_STOP
+    Qt::Key_MediaTogglePlayPause,   //  14   0x0e   APPCOMMAND_MEDIA_PLAYPAUSE
+    Qt::Key_LaunchMail,     //  15   0x0f   APPCOMMAND_LAUNCH_MAIL
+    Qt::Key_LaunchMedia,    //  16   0x10   APPCOMMAND_LAUNCH_MEDIA_SELECT
+    Qt::Key_Launch0,        //  17   0x11   APPCOMMAND_LAUNCH_APP1
+    Qt::Key_Launch1,        //  18   0x12   APPCOMMAND_LAUNCH_APP2
+    Qt::Key_BassDown,       //  19   0x13   APPCOMMAND_BASS_DOWN
+    Qt::Key_BassBoost,      //  20   0x14   APPCOMMAND_BASS_BOOST
+    Qt::Key_BassUp,         //  21   0x15   APPCOMMAND_BASS_UP
+    Qt::Key_TrebleDown,     //  22   0x16   APPCOMMAND_TREBLE_DOWN
+    Qt::Key_TrebleUp,       //  23   0x17   APPCOMMAND_TREBLE_UP
+    Qt::Key_MicMute,        //  24   0x18   APPCOMMAND_MICROPHONE_VOLUME_MUTE
+    Qt::Key_MicVolumeDown,  //  25   0x19   APPCOMMAND_MICROPHONE_VOLUME_DOWN
+    Qt::Key_MicVolumeUp,    //  26   0x1a   APPCOMMAND_MICROPHONE_VOLUME_UP
+    Qt::Key_Help,           //  27   0x1b   APPCOMMAND_HELP
+    Qt::Key_Find,           //  28   0x1c   APPCOMMAND_FIND
+    Qt::Key_New,            //  29   0x1d   APPCOMMAND_NEW
+    Qt::Key_Open,           //  30   0x1e   APPCOMMAND_OPEN
+    Qt::Key_Close,          //  31   0x1f   APPCOMMAND_CLOSE
+    Qt::Key_Save,           //  32   0x20   APPCOMMAND_SAVE
+    Qt::Key_Printer,        //  33   0x21   APPCOMMAND_PRINT
+    Qt::Key_Undo,           //  34   0x22   APPCOMMAND_UNDO
+    Qt::Key_Redo,           //  35   0x23   APPCOMMAND_REDO
+    Qt::Key_Copy,           //  36   0x24   APPCOMMAND_COPY
+    Qt::Key_Cut,            //  37   0x25   APPCOMMAND_CUT
+    Qt::Key_Paste,          //  38   0x26   APPCOMMAND_PASTE
+    Qt::Key_Reply,          //  39   0x27   APPCOMMAND_REPLY_TO_MAIL
+    Qt::Key_MailForward,    //  40   0x28   APPCOMMAND_FORWARD_MAIL
+    Qt::Key_Send,           //  41   0x29   APPCOMMAND_SEND_MAIL
+    Qt::Key_Spell,          //  42   0x2a   APPCOMMAND_SPELL_CHECK
+    Qt::Key_unknown,        //  43   0x2b   APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE
+    Qt::Key_unknown,        //  44   0x2c   APPCOMMAND_MIC_ON_OFF_TOGGLE
+    Qt::Key_unknown,        //  45   0x2d   APPCOMMAND_CORRECTION_LIST
+    Qt::Key_MediaPlay,      //  46   0x2e   APPCOMMAND_MEDIA_PLAY
+    Qt::Key_MediaPause,     //  47   0x2f   APPCOMMAND_MEDIA_PAUSE
+    Qt::Key_MediaRecord,    //  48   0x30   APPCOMMAND_MEDIA_RECORD
+    Qt::Key_AudioForward,   //  49   0x31   APPCOMMAND_MEDIA_FAST_FORWARD
+    Qt::Key_AudioRewind,    //  50   0x32   APPCOMMAND_MEDIA_REWIND
+    Qt::Key_ChannelDown,    //  51   0x33   APPCOMMAND_MEDIA_CHANNEL_DOWN
+    Qt::Key_ChannelUp       //  52   0x34   APPCOMMAND_MEDIA_CHANNEL_UP
+};
+
+// Possible modifier states.
+// NOTE: The order of these states match the order in QWindowsKeyMapper::updatePossibleKeyCodes()!
+static const Qt::KeyboardModifiers ModsTbl[] = {
+    Qt::NoModifier,                                             // 0
+    Qt::ShiftModifier,                                          // 1
+    Qt::ControlModifier,                                        // 2
+    Qt::ControlModifier | Qt::ShiftModifier,                    // 3
+    Qt::AltModifier,                                            // 4
+    Qt::AltModifier | Qt::ShiftModifier,                        // 5
+    Qt::AltModifier | Qt::ControlModifier,                      // 6
+    Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier,  // 7
+    Qt::NoModifier,                                             // Fall-back to raw Key_*
+};
+static const size_t NumMods = sizeof ModsTbl / sizeof *ModsTbl;
+static_assert((NumMods == KeyboardLayoutItem::NumQtKeys));
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug d, const KeyboardLayoutItem &k)
+{
+    QDebugStateSaver saver(d);
+    d.nospace();
+    d << "KeyboardLayoutItem(";
+    if (k.exists) {
+        for (size_t i = 0; i < NumMods; ++i) {
+            if (const quint32 qtKey = k.qtKey[i]) {
+                d << '[' << i << ' ';
+                QtDebugUtils::formatQFlags(d, ModsTbl[i]);
+                d << ' ' << Qt::hex << Qt::showbase << qtKey << Qt::dec << Qt::noshowbase << ' ';
+                QtDebugUtils::formatQEnum(d, Qt::Key(qtKey));
+                if (qtKey >= 32 && qtKey < 128)
+                    d << " '" << char(qtKey)  << '\'';
+                if (k.deadkeys & (1<<i))
+                    d << "  deadkey";
+                d << "] ";
+            }
+        }
+    }
+    d << ')';
+    return d;
+}
+#endif // QT_NO_DEBUG_STREAM
+
+/**
+  Remap return or action key to select key for windows mobile.
+*/
+inline quint32 winceKeyBend(quint32 keyCode)
+{
+    return KeyTbl[keyCode];
+}
+
+// Translate a VK into a Qt key code, or unicode character
+static inline quint32 toKeyOrUnicode(quint32 vk, quint32 scancode, unsigned char *kbdBuffer, bool *isDeadkey = nullptr)
+{
+    Q_ASSERT(vk > 0 && vk < 256);
+    quint32 code = 0;
+    QChar unicodeBuffer[5];
+    int res = ToUnicode(vk, scancode, kbdBuffer, reinterpret_cast<LPWSTR>(unicodeBuffer), 5, 0);
+    // When Ctrl modifier is used ToUnicode does not return correct values. In order to assign the
+    // right key the control modifier is removed for just that function if the previous call failed.
+    if (res == 0 && kbdBuffer[VK_CONTROL]) {
+        const unsigned char controlState = kbdBuffer[VK_CONTROL];
+        kbdBuffer[VK_CONTROL] = 0;
+        res = ToUnicode(vk, scancode, kbdBuffer, reinterpret_cast<LPWSTR>(unicodeBuffer), 5, 0);
+        kbdBuffer[VK_CONTROL] = controlState;
+    }
+    if (res)
+        code = unicodeBuffer[0].toUpper().unicode();
+
+    // Qt::Key_*'s are not encoded below 0x20, so try again, and DEL keys (0x7f) is encoded with a
+    // proper Qt::Key_ code
+    if (code < 0x20 || code == 0x7f) // Handles res==0 too
+        code = winceKeyBend(vk);
+
+    if (isDeadkey)
+        *isDeadkey = (res == -1);
+
+    return code == Qt::Key_unknown ? 0 : code;
+}
+
+static inline int asciiToKeycode(char a, int state)
+{
+    a = QtMiscUtils::toAsciiUpper(a);
+    if ((state & Qt::ControlModifier) != 0) {
+        if (a >= 0 && a <= 31)              // Ctrl+@..Ctrl+A..CTRL+Z..Ctrl+_
+            a += '@';                       // to @..A..Z.._
+    }
+    return a & 0xff;
+}
+
+// Key translation -----------------------------------------------------------------------[ end ]---
+
+
+// Keyboard map private ----------------------------------------------------------------[ start ]---
+
+void QWindowsKeyMapper::deleteLayouts()
+{
+    for (KeyboardLayoutItem &k : keyLayout)
+        k.exists = false;
+}
+
+void QWindowsKeyMapper::changeKeyboard()
+{
+    deleteLayouts();
+
+    /* MAKELCID()'s first argument is a WORD, and GetKeyboardLayout()
+     * returns a DWORD. */
+
+    LCID newLCID = MAKELCID(quintptr(GetKeyboardLayout(0)), SORT_DEFAULT);
+//    keyboardInputLocale = qt_localeFromLCID(newLCID);
+
+    bool bidi = false;
+    wchar_t LCIDFontSig[16];
+    if (GetLocaleInfo(newLCID, LOCALE_FONTSIGNATURE, LCIDFontSig, sizeof(LCIDFontSig) / sizeof(wchar_t))
+        && (LCIDFontSig[7] & wchar_t(0x0800)))
+        bidi = true;
+
+    keyboardInputDirection = bidi ? Qt::RightToLeft : Qt::LeftToRight;
+    m_seenAltGr = false;
+}
+
+// Helper function that is used when obtaining the list of characters that can be produced by one key and
+// every possible combination of modifiers
+inline void setKbdState(unsigned char *kbd, bool shift, bool ctrl, bool alt)
+{
+    kbd[VK_LSHIFT  ] = (shift ? 0x80 : 0);
+    kbd[VK_SHIFT   ] = (shift ? 0x80 : 0);
+    kbd[VK_LCONTROL] = (ctrl ? 0x80 : 0);
+    kbd[VK_CONTROL ] = (ctrl ? 0x80 : 0);
+    kbd[VK_RMENU   ] = (alt ? 0x80 : 0);
+    kbd[VK_MENU    ] = (alt ? 0x80 : 0);
+}
+
+// Adds the msg's key to keyLayout if it is not yet present there
+void QWindowsKeyMapper::updateKeyMap(const MSG &msg)
+{
+    unsigned char kbdBuffer[256]; // Will hold the complete keyboard state
+    GetKeyboardState(kbdBuffer);
+    const quint32 scancode = getScancode(msg);
+    updatePossibleKeyCodes(kbdBuffer, scancode, quint32(msg.wParam));
+}
+
+// Fills keyLayout for that vk_key. Values are all characters one can type using that key
+// (in connection with every combination of modifiers) and whether these "characters" are
+// dead keys.
+void QWindowsKeyMapper::updatePossibleKeyCodes(unsigned char *kbdBuffer, quint32 scancode,
+                                               quint32 vk_key)
+{
+    if (!vk_key || (keyLayout[vk_key].exists && !keyLayout[vk_key].dirty))
+        return;
+
+    // Copy keyboard state, so we can modify and query output for each possible permutation
+    unsigned char buffer[256];
+    memcpy(buffer, kbdBuffer, sizeof(buffer));
+    // Always 0, as Windows doesn't treat these as modifiers;
+    buffer[VK_LWIN    ] = 0;
+    buffer[VK_RWIN    ] = 0;
+    buffer[VK_CAPITAL ] = 0;
+    buffer[VK_NUMLOCK ] = 0;
+    buffer[VK_SCROLL  ] = 0;
+    // Always 0, since we'll only change the other versions
+    buffer[VK_RSHIFT  ] = 0;
+    buffer[VK_RCONTROL] = 0;
+    buffer[VK_LMENU   ] = 0; // Use right Alt, since left Ctrl + right Alt is considered AltGraph
+
+    // keyLayout contains the actual characters which can be written using the vk_key together with the
+    // different modifiers. '2' together with shift will for example cause the character
+    // to be @ for a US key layout (thus keyLayout[vk_key].qtKey[1] will be @). In addition to that
+    // it stores whether the resulting key is a dead key as these keys have to be handled later.
+    bool isDeadKey = false;
+    keyLayout[vk_key].deadkeys = 0;
+    keyLayout[vk_key].dirty = false;
+    keyLayout[vk_key].exists = true;
+    setKbdState(buffer, false, false, false);
+    keyLayout[vk_key].qtKey[0] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey);
+    keyLayout[vk_key].deadkeys |= isDeadKey ? 0x01 : 0;
+    setKbdState(buffer, true, false, false);
+    keyLayout[vk_key].qtKey[1] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey);
+    keyLayout[vk_key].deadkeys |= isDeadKey ? 0x02 : 0;
+    setKbdState(buffer, false, true, false);
+    keyLayout[vk_key].qtKey[2] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey);
+    keyLayout[vk_key].deadkeys |= isDeadKey ? 0x04 : 0;
+    setKbdState(buffer, true, true, false);
+    keyLayout[vk_key].qtKey[3] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey);
+    keyLayout[vk_key].deadkeys |= isDeadKey ? 0x08 : 0;
+    setKbdState(buffer, false, false, true);
+    keyLayout[vk_key].qtKey[4] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey);
+    keyLayout[vk_key].deadkeys |= isDeadKey ? 0x10 : 0;
+    setKbdState(buffer, true, false, true);
+    keyLayout[vk_key].qtKey[5] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey);
+    keyLayout[vk_key].deadkeys |= isDeadKey ? 0x20 : 0;
+    setKbdState(buffer, false, true, true);
+    keyLayout[vk_key].qtKey[6] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey);
+    keyLayout[vk_key].deadkeys |= isDeadKey ? 0x40 : 0;
+    setKbdState(buffer, true, true, true);
+    keyLayout[vk_key].qtKey[7] = toKeyOrUnicode(vk_key, scancode, buffer, &isDeadKey);
+    keyLayout[vk_key].deadkeys |= isDeadKey ? 0x80 : 0;
+    // Add a fall back key for layouts which don't do composition and show non-latin1 characters
+    quint32 fallbackKey = winceKeyBend(vk_key);
+    if (!fallbackKey || fallbackKey == Qt::Key_unknown) {
+        fallbackKey = 0;
+        if (vk_key != keyLayout[vk_key].qtKey[0] && vk_key != keyLayout[vk_key].qtKey[1]
+            && vk_key < 0x5B && vk_key > 0x2F)
+            fallbackKey = vk_key;
+    }
+    keyLayout[vk_key].qtKey[8] = fallbackKey;
+
+    // If one of the values inserted into the keyLayout above, can be considered a dead key, we have
+    // to run the workaround below.
+    if (keyLayout[vk_key].deadkeys) {
+        // Push a Space, then the original key through the low-level ToAscii functions.
+        // We do this because these functions (ToAscii / ToUnicode) will alter the internal state of
+        // the keyboard driver By doing the following, we set the keyboard driver state back to what
+        // it was before we wrecked it with the code above.
+        // We need to push the space with an empty keystate map, since the driver checks the map for
+        // transitions in modifiers, so this helps us capture all possible deadkeys.
+        unsigned char emptyBuffer[256];
+        memset(emptyBuffer, 0, sizeof(emptyBuffer));
+        ::ToAscii(VK_SPACE, 0, emptyBuffer, reinterpret_cast<LPWORD>(&buffer), 0);
+        ::ToAscii(vk_key, scancode, kbdBuffer, reinterpret_cast<LPWORD>(&buffer), 0);
+    }
+    qCDebug(lcQpaEvents) << __FUNCTION__ << "for virtual key="
+        << Qt::hex << Qt::showbase << vk_key << Qt::dec << Qt::noshowbase << keyLayout[vk_key];
+}
+
+static inline QString messageKeyText(const MSG &msg)
+{
+    const QChar ch = QChar(ushort(msg.wParam));
+    return ch.isNull() ? QString() : QString(ch);
+}
+
+[[nodiscard]] static inline int getTitleBarHeight(const HWND hwnd)
+{
+    const UINT dpi = GetDpiForWindow(hwnd);
+    const int captionHeight = GetSystemMetricsForDpi(SM_CYCAPTION, dpi);
+    if (IsZoomed(hwnd))
+        return captionHeight;
+    // The frame height should also be taken into account if the window
+    // is not maximized.
+    const int frameHeight = GetSystemMetricsForDpi(SM_CYSIZEFRAME, dpi)
+                            + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
+    return captionHeight + frameHeight;
+}
+
+[[nodiscard]] static inline bool isSystemMenuOffsetNeeded(const Qt::WindowFlags flags)
+{
+    static constexpr const Qt::WindowFlags titleBarHints =
+        Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint | Qt::WindowContextHelpButtonHint;
+    return (flags & Qt::WindowSystemMenuHint) && (flags & Qt::WindowTitleHint) && !(flags & titleBarHints)
+           && (flags & (Qt::FramelessWindowHint | Qt::CustomizeWindowHint));
+}
+
+static void showSystemMenu(QWindow* w)
+{
+    QWindow *topLevel = QWindowsWindow::topLevelOf(w);
+    HWND topLevelHwnd = QWindowsWindow::handleOf(topLevel);
+    HMENU menu = GetSystemMenu(topLevelHwnd, FALSE);
+    if (!menu)
+        return; // no menu for this window
+
+#define enabled (MF_BYCOMMAND | MFS_ENABLED)
+#define disabled (MF_BYCOMMAND | MFS_GRAYED)
+
+    EnableMenuItem(menu, SC_MINIMIZE, (topLevel->flags() & Qt::WindowMinimizeButtonHint) ? enabled : disabled);
+    const bool maximized = IsZoomed(topLevelHwnd);
+
+    EnableMenuItem(menu, SC_MAXIMIZE, !(topLevel->flags() & Qt::WindowMaximizeButtonHint) || maximized ? disabled : enabled);
+
+    // We should _not_ check with the setFixedSize(x,y) case here, since Windows is not able to check
+    // this and our menu here would be out-of-sync with the menu produced by mouse-click on the
+    // System Menu, or right-click on the title bar.
+    EnableMenuItem(menu, SC_SIZE, (topLevel->flags() & Qt::MSWindowsFixedSizeDialogHint) || maximized ? disabled : enabled);
+    EnableMenuItem(menu, SC_MOVE, maximized ? disabled : enabled);
+    EnableMenuItem(menu, SC_CLOSE, enabled);
+    EnableMenuItem(menu, SC_RESTORE, maximized ? enabled : disabled);
+
+    // Highlight the first entry in the menu, this is what native Win32 applications usually do.
+    HiliteMenuItem(topLevelHwnd, menu, SC_RESTORE, MF_BYCOMMAND | MFS_HILITE);
+
+    // Set bold on close menu item
+    SetMenuDefaultItem(menu, SC_CLOSE, FALSE);
+
+#undef enabled
+#undef disabled
+
+    const QPoint pos = QHighDpi::toNativePixels(topLevel->geometry().topLeft(), topLevel);
+    const int titleBarOffset = isSystemMenuOffsetNeeded(topLevel->flags()) ? getTitleBarHeight(topLevelHwnd) : 0;
+    const int ret = TrackPopupMenuEx(menu,
+                               TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
+                               pos.x(), pos.y() + titleBarOffset,
+                               topLevelHwnd,
+                               nullptr);
+
+    // Remove the highlight of the restore menu item, otherwise when the user right-clicks
+    // on the title bar, the popuped system menu will also highlight the restore item, which
+    // is not appropriate, it should only be highlighted if the menu is brought up by keyboard.
+    HiliteMenuItem(topLevelHwnd, menu, SC_RESTORE, MF_BYCOMMAND | MFS_UNHILITE);
+
+    if (ret)
+        qWindowsWndProc(topLevelHwnd, WM_SYSCOMMAND, WPARAM(ret), 0);
+}
+
+static inline void sendExtendedPressRelease(QWindow *w, int k,
+                                            Qt::KeyboardModifiers mods,
+                                            quint32 nativeScanCode,
+                                            quint32 nativeVirtualKey,
+                                            quint32 nativeModifiers,
+                                            const QString & text = QString(),
+                                            bool autorep = false,
+                                            ushort count = 1)
+{
+    QWindowSystemInterface::handleExtendedKeyEvent(w, QEvent::KeyPress, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count);
+    QWindowSystemInterface::handleExtendedKeyEvent(w, QEvent::KeyRelease, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count);
+}
+
+/*!
+    \brief To be called from the window procedure.
+*/
+
+bool QWindowsKeyMapper::translateKeyEvent(QWindow *widget, HWND hwnd,
+                                          const MSG &msg, LRESULT *result)
+{
+    *result = 0;
+
+    // Reset layout map when system keyboard layout is changed
+    if (msg.message == WM_INPUTLANGCHANGE) {
+        changeKeyboard();
+        return true;
+    }
+
+#if defined(WM_APPCOMMAND)
+    if (msg.message == WM_APPCOMMAND)
+        return translateMultimediaKeyEventInternal(widget, msg);
+#endif
+
+    // WM_(IME_)CHAR messages already contain the character in question so there is
+    // no need to fiddle with our key map. In any other case add this key to the
+    // keymap if it is not present yet.
+    if (msg.message != WM_CHAR && msg.message != WM_IME_CHAR)
+        updateKeyMap(msg);
+
+    MSG peekedMsg;
+    // consume dead chars?(for example, typing '`','a' resulting in a-accent).
+    if (PeekMessage(&peekedMsg, hwnd, 0, 0, PM_NOREMOVE) && peekedMsg.message == WM_DEADCHAR)
+        return true;
+
+    return translateKeyEventInternal(widget, msg, false, result);
+}
+
+bool QWindowsKeyMapper::translateMultimediaKeyEventInternal(QWindow *window, const MSG &msg)
+{
+#if defined(WM_APPCOMMAND)
+    const int cmd = GET_APPCOMMAND_LPARAM(msg.lParam);
+    // QTBUG-57198, do not send mouse-synthesized commands as key events in addition
+    bool skipPressRelease = false;
+    switch (GET_DEVICE_LPARAM(msg.lParam)) {
+    case FAPPCOMMAND_MOUSE:
+        return false;
+    case FAPPCOMMAND_KEY:
+        // QTBUG-62838, use WM_KEYDOWN/WM_KEYUP for commands that are reflected
+        // in VK(s) like VK_MEDIA_NEXT_TRACK, to get correct codes and autorepeat.
+        // Don't do that for APPCOMMAND_BROWSER_HOME as that one does not trigger two events.
+        if (cmd != APPCOMMAND_BROWSER_HOME)
+            skipPressRelease = true;
+        break;
+    }
+
+    const int dwKeys = GET_KEYSTATE_LPARAM(msg.lParam);
+    int state = 0;
+    state |= (dwKeys & MK_SHIFT ? int(Qt::ShiftModifier) : 0);
+    state |= (dwKeys & MK_CONTROL ? int(Qt::ControlModifier) : 0);
+
+    QWindow *receiver = m_keyGrabber ? m_keyGrabber : window;
+
+    if (cmd < 0 || cmd > 52)
+        return false;
+
+    const int qtKey = int(CmdTbl[cmd]);
+    if (!skipPressRelease)
+        sendExtendedPressRelease(receiver, qtKey, Qt::KeyboardModifier(state), 0, 0, 0);
+    // QTBUG-43343: Make sure to return false if Qt does not handle the key, otherwise,
+    // the keys are not passed to the active media player.
+# if QT_CONFIG(shortcut)
+    const QKeySequence sequence(Qt::Modifier(state) | Qt::Key(qtKey));
+    return QGuiApplicationPrivate::instance()->shortcutMap.hasShortcutForKeySequence(sequence);
+# else
+    return false;
+# endif
+#else
+    Q_UNREACHABLE();
+    return false;
+#endif
+}
+
+// QTBUG-69317: Check for AltGr found on some keyboards
+// which is a sequence of left Ctrl (SYSKEY) + right Menu (Alt).
+static bool isAltGr(MSG *msg)
+{
+    enum : LONG_PTR { RightFlag = 0x1000000 };
+    if (msg->wParam != VK_CONTROL || (msg->lParam & RightFlag) != 0
+        || (msg->message != WM_KEYDOWN && msg->message != WM_SYSKEYUP)) {
+        return false;
+    }
+    const UINT expectedMessage = msg->message == WM_SYSKEYUP
+        ? WM_KEYUP : msg->message;
+    MSG peekedMsg;
+    if (PeekMessage(&peekedMsg, msg->hwnd, 0, 0, PM_NOREMOVE) == FALSE
+        || peekedMsg.message != expectedMessage || peekedMsg.wParam != VK_MENU
+        || (peekedMsg.lParam & RightFlag) == 0) {
+        return false;
+    }
+    *msg = peekedMsg;
+    PeekMessage(&peekedMsg, msg->hwnd, 0, 0, PM_REMOVE);
+    return true;
+}
+
+bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
+                                                  bool /* grab */, LRESULT *lResult)
+{
+    const bool altGr = m_detectAltGrModifier && isAltGr(&msg);
+    if (altGr)
+        m_seenAltGr = true;
+    const UINT msgType = msg.message;
+
+    const quint32 scancode = getScancode(msg);
+    auto vk_key = quint32(msg.wParam);
+    quint32 nModifiers = 0;
+
+    QWindow *receiver = m_keyGrabber ? m_keyGrabber : window;
+
+    // Map native modifiers to some bit representation
+    nModifiers |= (GetKeyState(VK_LSHIFT  ) & 0x80 ? ShiftLeft : 0);
+    nModifiers |= (GetKeyState(VK_RSHIFT  ) & 0x80 ? ShiftRight : 0);
+    nModifiers |= (GetKeyState(VK_LCONTROL) & 0x80 ? ControlLeft : 0);
+    nModifiers |= (GetKeyState(VK_RCONTROL) & 0x80 ? ControlRight : 0);
+    nModifiers |= (GetKeyState(VK_LMENU   ) & 0x80 ? AltLeft : 0);
+    nModifiers |= (GetKeyState(VK_RMENU   ) & 0x80 ? AltRight : 0);
+    nModifiers |= (GetKeyState(VK_LWIN    ) & 0x80 ? MetaLeft : 0);
+    nModifiers |= (GetKeyState(VK_RWIN    ) & 0x80 ? MetaRight : 0);
+    // Add Lock keys to the same bits
+    nModifiers |= (GetKeyState(VK_CAPITAL ) & 0x01 ? CapsLock : 0);
+    nModifiers |= (GetKeyState(VK_NUMLOCK ) & 0x01 ? NumLock : 0);
+    nModifiers |= (GetKeyState(VK_SCROLL  ) & 0x01 ? ScrollLock : 0);
+
+    if (msg.lParam & ExtendedKey)
+        nModifiers |= msg.lParam & ExtendedKey;
+
+    // Get the modifier states (may be altered later, depending on key code)
+    int state = 0;
+    state |= (nModifiers & ShiftAny ? int(Qt::ShiftModifier) : 0);
+    state |= (nModifiers & AltLeft ? int(Qt::AltModifier) : 0);
+    if ((nModifiers & AltRight) != 0)
+        state |= m_seenAltGr ? Qt::GroupSwitchModifier : Qt::AltModifier;
+    if ((nModifiers & ControlAny) != 0 && (state & Qt::GroupSwitchModifier) == 0)
+        state |= Qt::ControlModifier;
+    state |= (nModifiers & MetaAny ? int(Qt::MetaModifier) : 0);
+    // A multi-character key or a Input method character
+    // not found by our look-ahead
+    if (msgType == WM_CHAR || msgType == WM_IME_CHAR) {
+        sendExtendedPressRelease(receiver, 0, Qt::KeyboardModifier(state), scancode, 0, nModifiers, messageKeyText(msg), false);
+        return true;
+    }
+
+    // Enable Alt accelerators ("&File") on menus
+    if (msgType == WM_SYSKEYDOWN && (nModifiers & AltAny) != 0 && GetMenu(msg.hwnd) != nullptr)
+        return false;
+    if (msgType == WM_SYSKEYUP && nModifiers == 0 && GetMenu(msg.hwnd) != nullptr)
+        return false;
+
+    bool result = false;
+    // handle Directionality changes (BiDi) with RTL extensions
+    if (m_useRTLExtensions) {
+        static int dirStatus = 0;
+        if (!dirStatus && state == Qt::ControlModifier
+                && msg.wParam == VK_CONTROL
+                && msgType == WM_KEYDOWN) {
+            if (GetKeyState(VK_LCONTROL) < 0)
+                dirStatus = VK_LCONTROL;
+            else if (GetKeyState(VK_RCONTROL) < 0)
+                dirStatus = VK_RCONTROL;
+        } else if (dirStatus) {
+            if (msgType == WM_KEYDOWN) {
+                if (msg.wParam == VK_SHIFT) {
+                    if (dirStatus == VK_LCONTROL && GetKeyState(VK_LSHIFT) < 0)
+                        dirStatus = VK_LSHIFT;
+                    else if (dirStatus == VK_RCONTROL && GetKeyState(VK_RSHIFT) < 0)
+                        dirStatus = VK_RSHIFT;
+                } else {
+                    dirStatus = 0;
+                }
+            } else if (msgType == WM_KEYUP) {
+                if (dirStatus == VK_LSHIFT
+                        && ((msg.wParam == VK_SHIFT && GetKeyState(VK_LCONTROL))
+                            || (msg.wParam == VK_CONTROL && GetKeyState(VK_LSHIFT)))) {
+                    sendExtendedPressRelease(receiver, Qt::Key_Direction_L, {},
+                                             scancode, vk_key, nModifiers, QString(), false);
+                    result = true;
+                    dirStatus = 0;
+                } else if (dirStatus == VK_RSHIFT
+                           && ( (msg.wParam == VK_SHIFT && GetKeyState(VK_RCONTROL))
+                                || (msg.wParam == VK_CONTROL && GetKeyState(VK_RSHIFT)))) {
+                    sendExtendedPressRelease(receiver, Qt::Key_Direction_R, {},
+                                             scancode, vk_key, nModifiers, QString(), false);
+                    result = true;
+                    dirStatus = 0;
+                } else {
+                    dirStatus = 0;
+                }
+            } else {
+                dirStatus = 0;
+            }
+        }
+    } // RTL
+
+    // IME will process these keys, so simply return
+    if (msg.wParam == VK_PROCESSKEY)
+        return true;
+
+    // Ignore invalid virtual keycodes (see bugs 127424, QTBUG-3630)
+    if (msg.wParam == 0 || msg.wParam == 0xFF)
+        return true;
+
+    // Translate VK_* (native) -> Key_* (Qt) keys
+    int modifiersIndex = 0;
+    modifiersIndex |= (nModifiers & ShiftAny ? 0x1 : 0);
+    modifiersIndex |= (nModifiers & ControlAny ? 0x2 : 0);
+    modifiersIndex |= (nModifiers & AltAny ? 0x4 : 0);
+
+    // Note: For the resulting key, AltGr is equivalent to Alt + Ctrl (as
+    // opposed to Linux); hence no entry in KeyboardLayoutItem is required
+    int code = keyLayout[vk_key].qtKey[modifiersIndex];
+
+    // If the bit 24 of lParm is set you received a enter,
+    // otherwise a Return. (This is the extended key bit)
+    if ((code == Qt::Key_Return) && (msg.lParam & 0x1000000))
+        code = Qt::Key_Enter;
+    else if (altGr)
+        code = Qt::Key_AltGr;
+
+    // Invert state logic:
+    // If the key actually pressed is a modifier key, then we remove its modifier key from the
+    // state, since a modifier-key can't have itself as a modifier
+    if (code == Qt::Key_Control)
+        state = state ^ Qt::ControlModifier;
+    else if (code == Qt::Key_Shift)
+        state = state ^ Qt::ShiftModifier;
+    else if (code == Qt::Key_Alt)
+        state = state ^ Qt::AltModifier;
+    else if (code == Qt::Key_AltGr)
+        state = state ^ Qt::GroupSwitchModifier;
+
+    // All cursor keys without extended bit
+    if (!(msg.lParam & 0x1000000)) {
+        switch (code) {
+        case Qt::Key_Left:
+        case Qt::Key_Right:
+        case Qt::Key_Up:
+        case Qt::Key_Down:
+        case Qt::Key_PageUp:
+        case Qt::Key_PageDown:
+        case Qt::Key_Home:
+        case Qt::Key_End:
+        case Qt::Key_Insert:
+        case Qt::Key_Delete:
+        case Qt::Key_Asterisk:
+        case Qt::Key_Plus:
+        case Qt::Key_Minus:
+        case Qt::Key_Period:
+        case Qt::Key_Comma:
+        case Qt::Key_0:
+        case Qt::Key_1:
+        case Qt::Key_2:
+        case Qt::Key_3:
+        case Qt::Key_4:
+        case Qt::Key_5:
+        case Qt::Key_6:
+        case Qt::Key_7:
+        case Qt::Key_8:
+        case Qt::Key_9:
+            state |= ((msg.wParam >= '0' && msg.wParam <= '9')
+                      || (msg.wParam >= VK_OEM_PLUS && msg.wParam <= VK_OEM_3))
+                    ? 0 : int(Qt::KeypadModifier);
+            Q_FALLTHROUGH();
+        default:
+            if (uint(msg.lParam) == 0x004c0001 || uint(msg.lParam) == 0xc04c0001)
+                state |= Qt::KeypadModifier;
+            break;
+        }
+    }
+    // Other keys with with extended bit
+    else {
+        switch (code) {
+        case Qt::Key_Enter:
+        case Qt::Key_Slash:
+        case Qt::Key_NumLock:
+            state |= Qt::KeypadModifier;
+            break;
+        default:
+            break;
+        }
+    }
+
+    // KEYDOWN ---------------------------------------------------------------------------------
+    if (msgType == WM_KEYDOWN || msgType == WM_IME_KEYDOWN || msgType == WM_SYSKEYDOWN) {
+        // Get the last record of this key press, so we can validate the current state
+        // The record is not removed from the list
+        KeyRecord *rec = key_recorder.findKey(int(msg.wParam), false);
+
+        // If rec's state doesn't match the current state, something has changed behind our back
+        // (Consumed by modal widget is one possibility) So, remove the record from the list
+        // This will stop the auto-repeat of the key, should a modifier change, for example
+        if (rec && rec->state != state) {
+            key_recorder.findKey(int(msg.wParam), true);
+            rec = nullptr;
+        }
+
+        // Find unicode character from Windows Message Queue
+        MSG wm_char;
+        UINT charType = (msgType == WM_KEYDOWN
+                         ? WM_CHAR
+                         : msgType == WM_IME_KEYDOWN ? WM_IME_CHAR : WM_SYSCHAR);
+
+        QChar uch;
+        if (PeekMessage(&wm_char, nullptr, charType, charType, PM_REMOVE)) {
+            if (QWindowsContext::filterNativeEvent(&wm_char, lResult))
+                return true;
+            if (receiver && QWindowsContext::filterNativeEvent(receiver, &wm_char, lResult))
+                return true;
+            // Found a ?_CHAR
+            uch = QChar(ushort(wm_char.wParam));
+            if (uch.isHighSurrogate()) {
+                m_lastHighSurrogate = uch;
+                return true;
+            }
+            if (uch.isLowSurrogate() && !m_lastHighSurrogate.isNull()) {
+                if (QObject *focusObject = QGuiApplication::focusObject()) {
+                    const QChar chars[2] = {m_lastHighSurrogate, uch};
+                    QInputMethodEvent event;
+                    event.setCommitString(QString(chars, 2));
+                    QCoreApplication::sendEvent(focusObject, &event);
+                }
+                m_lastHighSurrogate = QChar();
+                return true;
+            } else {
+                m_lastHighSurrogate = QChar();
+            }
+            if (msgType == WM_SYSKEYDOWN && uch.isLetter() && (msg.lParam & KF_ALTDOWN))
+                uch = uch.toLower(); // (See doc of WM_SYSCHAR) Alt-letter
+            if (!code && !uch.row())
+                code = asciiToKeycode(char(uch.cell()), state);
+        }
+
+        // Special handling for the WM_IME_KEYDOWN message. Microsoft IME (Korean) will not
+        // generate a WM_IME_CHAR message corresponding to this message. We might get wrong
+        // results, if we map this virtual key-code directly (for eg '?' US layouts). So try
+        // to find the correct key using the current message parameters & keyboard state.
+        if (uch.isNull() && msgType == WM_IME_KEYDOWN) {
+            const auto *windowsInputContext =
+                qobject_cast<const QWindowsInputContext *>(QWindowsIntegration::instance()->inputContext());
+            if (!(windowsInputContext && windowsInputContext->isComposing()))
+                vk_key = ImmGetVirtualKey(reinterpret_cast<HWND>(window->winId()));
+            BYTE keyState[256];
+            wchar_t newKey[3] = {0};
+            GetKeyboardState(keyState);
+            int val = ToUnicode(vk_key, scancode, keyState, newKey, 2,  0);
+            if (val == 1) {
+                uch = QChar(newKey[0]);
+            } else {
+                // If we are still not able to find a unicode key, pass the WM_IME_KEYDOWN
+                // message to DefWindowProc() for generating a proper WM_KEYDOWN.
+                return false;
+            }
+        }
+
+        // If no ?_CHAR was found in the queue; deduct character from the ?_KEYDOWN parameters
+        if (uch.isNull()) {
+            if (msg.wParam == VK_DELETE) {
+                uch = QChar(QLatin1Char(0x7f)); // Windows doesn't know this one.
+            } else {
+                if (msgType != WM_SYSKEYDOWN || !code) {
+                    UINT map = MapVirtualKey(UINT(msg.wParam), 2);
+                    // If the high bit of the return value is set, it's a deadkey
+                    if (!(map & 0x80000000))
+                        uch = QChar(ushort(map));
+                }
+            }
+            if (!code && !uch.row())
+                code = asciiToKeycode(char(uch.cell()), state);
+        }
+
+        // Special handling of global Windows hotkeys
+        if (state == Qt::AltModifier) {
+            switch (code) {
+            case Qt::Key_Escape:
+            case Qt::Key_Tab:
+            case Qt::Key_F4:
+                return false; // Send the event on to Windows
+            case Qt::Key_Space:
+                // do not pass this key to windows, we will process it ourselves
+                showSystemMenu(receiver);
+                return true;
+            default:
+                break;
+            }
+        }
+
+        // Map SHIFT + Tab to SHIFT + BackTab, QShortcutMap knows about this translation
+        if (code == Qt::Key_Tab && (state & Qt::ShiftModifier) == Qt::ShiftModifier)
+            code = Qt::Key_Backtab;
+
+        // If we have a record, it means that the key is already pressed, the state is the same
+        // so, we have an auto-repeating key
+        if (rec) {
+            if (code < Qt::Key_Shift || code > Qt::Key_ScrollLock) {
+                QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyRelease, code,
+                                                               Qt::KeyboardModifier(state), scancode, quint32(msg.wParam), nModifiers, rec->text, true);
+                QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyPress, code,
+                                                               Qt::KeyboardModifier(state), scancode, quint32(msg.wParam), nModifiers, rec->text, true);
+                result = true;
+            }
+        }
+        // No record of the key being previous pressed, so we now send a QEvent::KeyPress event,
+        // and store the key data into our records.
+        else {
+            const QString text = uch.isNull() ? QString() : QString(uch);
+            const char a = uch.row() ? char(0) : char(uch.cell());
+            const Qt::KeyboardModifiers modifiers(state);
+#ifndef QT_NO_SHORTCUT
+            // Is Qt interested in the context menu key?
+            if (modifiers == Qt::SHIFT && code == Qt::Key_F10
+                && !QGuiApplicationPrivate::instance()->shortcutMap.hasShortcutForKeySequence(QKeySequence(Qt::SHIFT | Qt::Key_F10))) {
+                return false;
+            }
+#endif // !QT_NO_SHORTCUT
+            key_recorder.storeKey(int(msg.wParam), a, state, text);
+
+            // QTBUG-71210
+            // VK_PACKET specifies multiple characters. The system only sends the first
+            // character of this sequence for each.
+            if (msg.wParam == VK_PACKET)
+                code = asciiToKeycode(char(uch.cell()), state);
+
+            QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyPress, code,
+                                                           modifiers, scancode, quint32(msg.wParam), nModifiers, text, false);
+            result =true;
+            bool store = true;
+            // Alt+<alphanumerical> go to the Win32 menu system if unhandled by Qt
+            if (msgType == WM_SYSKEYDOWN && !result && a) {
+                HWND parent = GetParent(QWindowsWindow::handleOf(receiver));
+                while (parent) {
+                    if (GetMenu(parent)) {
+                        SendMessage(parent, WM_SYSCOMMAND, SC_KEYMENU, a);
+                        store = false;
+                        result = true;
+                        break;
+                    }
+                    parent = GetParent(parent);
+                }
+            }
+            if (!store)
+                key_recorder.findKey(int(msg.wParam), true);
+        }
+    }
+
+    // KEYUP -----------------------------------------------------------------------------------
+    else {
+        // Try to locate the key in our records, and remove it if it exists.
+        // The key may not be in our records if, for example, the down event was handled by
+        // win32 natively, or our window gets focus while a key is already press, but now gets
+        // the key release event.
+        const KeyRecord *rec = key_recorder.findKey(int(msg.wParam), true);
+        if (!rec && !(code == Qt::Key_Shift
+                      || code == Qt::Key_Control
+                      || code == Qt::Key_Meta
+                      || code == Qt::Key_Alt)) {
+
+            // Workaround for QTBUG-77153:
+            // The Surface Pen eraser button generates Meta+F18/19/20 keystrokes,
+            // but when it is not touching the screen the Fn Down is eaten and only
+            // a Fn Up with the previous state as "not pressed" is generated, which
+            // would be ignored. We detect this case and synthesize the expected events.
+            if ((msg.lParam & 0x40000000) == 0 &&
+                    Qt::KeyboardModifier(state) == Qt::NoModifier &&
+                    ((code == Qt::Key_F18) || (code == Qt::Key_F19) || (code == Qt::Key_F20))) {
+                QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyPress, code,
+                                                               Qt::MetaModifier, scancode,
+                                                               quint32(msg.wParam), MetaLeft);
+                QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyRelease, code,
+                                                               Qt::NoModifier, scancode,
+                                                               quint32(msg.wParam), 0);
+                result = true;
+            }
+        } else {
+            if (!code)
+                code = asciiToKeycode(rec->ascii ? char(rec->ascii) : char(msg.wParam), state);
+
+            // Map SHIFT + Tab to SHIFT + BackTab, QShortcutMap knows about this translation
+            if (code == Qt::Key_Tab && (state & Qt::ShiftModifier) == Qt::ShiftModifier)
+                code = Qt::Key_Backtab;
+            QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyRelease, code,
+                                                           Qt::KeyboardModifier(state), scancode, quint32(msg.wParam),
+                                                           nModifiers,
+                                                           (rec ? rec->text : QString()), false);
+            result = true;
+            // don't pass Alt to Windows unless we are embedded in a non-Qt window
+            if (code == Qt::Key_Alt) {
+                const QWindowsContext *context = QWindowsContext::instance();
+                HWND parent = GetParent(QWindowsWindow::handleOf(receiver));
+                while (parent) {
+                    if (!context->findPlatformWindow(parent) && GetMenu(parent)) {
+                        result = false;
+                        break;
+                    }
+                    parent = GetParent(parent);
+                }
+            }
+        }
+    }
+    return result;
+}
+
+Qt::KeyboardModifiers QWindowsKeyMapper::queryKeyboardModifiers() const
+{
+    Qt::KeyboardModifiers modifiers = Qt::NoModifier;
+    if (GetKeyState(VK_SHIFT) < 0)
+        modifiers |= Qt::ShiftModifier;
+    if (GetKeyState(VK_CONTROL) < 0)
+        modifiers |= Qt::ControlModifier;
+    if (GetKeyState(VK_MENU) < 0)
+        modifiers |= Qt::AltModifier;
+    if (GetKeyState(VK_LWIN) < 0 || GetKeyState(VK_RWIN) < 0)
+        modifiers |= Qt::MetaModifier;
+    return modifiers;
+}
+
+QList<QKeyCombination> QWindowsKeyMapper::possibleKeyCombinations(const QKeyEvent *e) const
+{
+    QList<QKeyCombination> result;
+
+
+    const quint32 nativeVirtualKey = e->nativeVirtualKey();
+    if (nativeVirtualKey > 255)
+        return result;
+
+    const KeyboardLayoutItem &kbItem = keyLayout[nativeVirtualKey];
+    if (!kbItem.exists)
+        return result;
+
+    quint32 baseKey = kbItem.qtKey[0];
+    Qt::KeyboardModifiers keyMods = e->modifiers();
+    if (baseKey == Qt::Key_Return && (e->nativeModifiers() & ExtendedKey)) {
+        result << (Qt::Key_Enter | keyMods);
+        return result;
+    }
+
+    // The base key is _always_ valid, of course
+    result << QKeyCombination::fromCombined(int(baseKey) + int(keyMods));
+
+    for (size_t i = 1; i < NumMods; ++i) {
+        Qt::KeyboardModifiers neededMods = ModsTbl[i];
+        quint32 key = kbItem.qtKey[i];
+        if (key && key != baseKey && ((keyMods & neededMods) == neededMods)) {
+            const Qt::KeyboardModifiers missingMods = keyMods & ~neededMods;
+            const auto matchedKey = QKeyCombination::fromCombined(int(key) + int(missingMods));
+            const auto it = std::find_if(result.begin(), result.end(),
+                [key](auto keyCombination) {
+                    return keyCombination.key() == key;
+                });
+            // QTBUG-67200: Use the match with the least modifiers (prefer
+            // Shift+9 over Alt + Shift + 9) resulting in more missing modifiers.
+            if (it == result.end())
+                result << matchedKey;
+            else if (missingMods > it->keyboardModifiers())
+                *it = matchedKey;
+        }
+    }
+    qCDebug(lcQpaEvents) << __FUNCTION__  << e << "nativeVirtualKey="
+        << Qt::showbase << Qt::hex << e->nativeVirtualKey() << Qt::dec << Qt::noshowbase
+        << e->modifiers() << kbItem << "\n  returns" << result;
+    return result;
+}
+
+QT_END_NAMESPACE
diff --git a/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp
index 9f1f25db8c..0a0b8bdf56 100644
--- a/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp
+++ b/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp
@@ -48,7 +48,7 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q
     *result = 0;
     const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
 
-    if (!GetPointerType(pointerId, &m_pointerType)) {
+    if (!QWindowsContext::user32dll.getPointerType(pointerId, &m_pointerType)) {
         qWarning() << "GetPointerType() failed:" << qt_error_string();
         return false;
     }
@@ -62,12 +62,12 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q
     }
     case QT_PT_TOUCH: {
         quint32 pointerCount = 0;
-        if (!GetPointerFrameTouchInfo(pointerId, &pointerCount, nullptr)) {
+        if (!QWindowsContext::user32dll.getPointerFrameTouchInfo(pointerId, &pointerCount, nullptr)) {
             qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string();
             return false;
         }
         QVarLengthArray<POINTER_TOUCH_INFO, 10> touchInfo(pointerCount);
-        if (!GetPointerFrameTouchInfo(pointerId, &pointerCount, touchInfo.data())) {
+        if (!QWindowsContext::user32dll.getPointerFrameTouchInfo(pointerId, &pointerCount, touchInfo.data())) {
             qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string();
             return false;
         }
@@ -80,10 +80,10 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q
         // dispatch any skipped frames if event compression is disabled by the app
         if (historyCount > 1 && !QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) {
             touchInfo.resize(pointerCount * historyCount);
-            if (!GetPointerFrameTouchInfoHistory(pointerId,
-                                                 &historyCount,
-                                                 &pointerCount,
-                                                 touchInfo.data())) {
+            if (!QWindowsContext::user32dll.getPointerFrameTouchInfoHistory(pointerId,
+                                                                            &historyCount,
+                                                                            &pointerCount,
+                                                                            touchInfo.data())) {
                 qWarning() << "GetPointerFrameTouchInfoHistory() failed:" << qt_error_string();
                 return false;
             }
@@ -101,7 +101,7 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q
     }
     case QT_PT_PEN: {
         POINTER_PEN_INFO penInfo;
-        if (!GetPointerPenInfo(pointerId, &penInfo)) {
+        if (!QWindowsContext::user32dll.getPointerPenInfo(pointerId, &penInfo)) {
             qWarning() << "GetPointerPenInfo() failed:" << qt_error_string();
             return false;
         }
@@ -113,7 +113,7 @@ bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, Q
                 || !QCoreApplication::testAttribute(Qt::AA_CompressTabletEvents))) {
             QVarLengthArray<POINTER_PEN_INFO, 10> penInfoHistory(historyCount);
 
-            if (!GetPointerPenInfoHistory(pointerId, &historyCount, penInfoHistory.data())) {
+            if (!QWindowsContext::user32dll.getPointerPenInfoHistory(pointerId, &historyCount, penInfoHistory.data())) {
                 qWarning() << "GetPointerPenInfoHistory() failed:" << qt_error_string();
                 return false;
             }
@@ -520,7 +520,7 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
         inputIds.insert(touchPoint.id);
 
         // Avoid getting repeated messages for this frame if there are multiple pointerIds
-        SkipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId);
+        QWindowsContext::user32dll.skipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId);
     }
 
     // Some devices send touches for each finger in a different message/frame, instead of consolidating
@@ -567,7 +567,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
     auto *penInfo = static_cast<POINTER_PEN_INFO *>(vPenInfo);
 
     RECT pRect, dRect;
-    if (!GetPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect))
+    if (!QWindowsContext::user32dll.getPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect))
         return false;
 
     const auto systemId = (qint64)penInfo->pointerInfo.sourceDevice;
diff --git a/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp.orig b/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp.orig
new file mode 100644
index 0000000000..9f1f25db8c
--- /dev/null
+++ b/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp.orig
@@ -0,0 +1,868 @@
+// Copyright (C) 2019 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtCore/qt_windows.h>
+
+#include "qwindowspointerhandler.h"
+#include "qwindowsmousehandler.h"
+#if QT_CONFIG(tabletevent)
+#  include "qwindowstabletsupport.h"
+#endif
+#include "qwindowskeymapper.h"
+#include "qwindowscontext.h"
+#include "qwindowswindow.h"
+#include "qwindowsintegration.h"
+#include "qwindowsscreen.h"
+
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qscreen.h>
+#include <QtGui/qpointingdevice.h>
+#include <QtGui/qwindow.h>
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtCore/qvarlengtharray.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qqueue.h>
+
+#include <algorithm>
+
+#include <windowsx.h>
+
+QT_BEGIN_NAMESPACE
+
+enum {
+    QT_PT_POINTER  = 1,
+    QT_PT_TOUCH    = 2,
+    QT_PT_PEN      = 3,
+    QT_PT_MOUSE    = 4,
+    QT_PT_TOUCHPAD = 5, // MinGW is missing PT_TOUCHPAD
+};
+
+qint64 QWindowsPointerHandler::m_nextInputDeviceId = 1;
+
+QWindowsPointerHandler::~QWindowsPointerHandler()
+{
+}
+
+bool QWindowsPointerHandler::translatePointerEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et, MSG msg, LRESULT *result)
+{
+    *result = 0;
+    const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
+
+    if (!GetPointerType(pointerId, &m_pointerType)) {
+        qWarning() << "GetPointerType() failed:" << qt_error_string();
+        return false;
+    }
+
+    switch (m_pointerType) {
+    case QT_PT_POINTER:
+    case QT_PT_MOUSE:
+    case QT_PT_TOUCHPAD: {
+        // Let Mouse/TouchPad be handled using legacy messages.
+        return false;
+    }
+    case QT_PT_TOUCH: {
+        quint32 pointerCount = 0;
+        if (!GetPointerFrameTouchInfo(pointerId, &pointerCount, nullptr)) {
+            qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string();
+            return false;
+        }
+        QVarLengthArray<POINTER_TOUCH_INFO, 10> touchInfo(pointerCount);
+        if (!GetPointerFrameTouchInfo(pointerId, &pointerCount, touchInfo.data())) {
+            qWarning() << "GetPointerFrameTouchInfo() failed:" << qt_error_string();
+            return false;
+        }
+
+        if (!pointerCount)
+            return false;
+
+        // The history count is the same for all the touchpoints in touchInfo
+        quint32 historyCount = touchInfo[0].pointerInfo.historyCount;
+        // dispatch any skipped frames if event compression is disabled by the app
+        if (historyCount > 1 && !QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) {
+            touchInfo.resize(pointerCount * historyCount);
+            if (!GetPointerFrameTouchInfoHistory(pointerId,
+                                                 &historyCount,
+                                                 &pointerCount,
+                                                 touchInfo.data())) {
+                qWarning() << "GetPointerFrameTouchInfoHistory() failed:" << qt_error_string();
+                return false;
+            }
+
+            // history frames are returned with the most recent frame first so we iterate backwards
+            bool result = true;
+            for (auto it = touchInfo.rbegin(), end = touchInfo.rend(); it != end; it += pointerCount) {
+                result &= translateTouchEvent(window, hwnd, et, msg,
+                                              &(*(it + (pointerCount - 1))), pointerCount);
+            }
+            return result;
+        }
+
+        return translateTouchEvent(window, hwnd, et, msg, touchInfo.data(), pointerCount);
+    }
+    case QT_PT_PEN: {
+        POINTER_PEN_INFO penInfo;
+        if (!GetPointerPenInfo(pointerId, &penInfo)) {
+            qWarning() << "GetPointerPenInfo() failed:" << qt_error_string();
+            return false;
+        }
+
+        quint32 historyCount = penInfo.pointerInfo.historyCount;
+        // dispatch any skipped frames if generic or tablet event compression is disabled by the app
+        if (historyCount > 1
+            && (!QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)
+                || !QCoreApplication::testAttribute(Qt::AA_CompressTabletEvents))) {
+            QVarLengthArray<POINTER_PEN_INFO, 10> penInfoHistory(historyCount);
+
+            if (!GetPointerPenInfoHistory(pointerId, &historyCount, penInfoHistory.data())) {
+                qWarning() << "GetPointerPenInfoHistory() failed:" << qt_error_string();
+                return false;
+            }
+
+            // history frames are returned with the most recent frame first so we iterate backwards
+            bool result = true;
+            for (auto it = penInfoHistory.rbegin(), end = penInfoHistory.rend(); it != end; ++it) {
+                result &= translatePenEvent(window, hwnd, et, msg, &(*(it)));
+            }
+            return result;
+        }
+
+        return translatePenEvent(window, hwnd, et, msg, &penInfo);
+    }
+    }
+    return false;
+}
+
+namespace {
+struct MouseEvent {
+    QEvent::Type type;
+    Qt::MouseButton button;
+};
+} // namespace
+
+static inline Qt::MouseButton extraButton(WPARAM wParam) // for WM_XBUTTON...
+{
+    return GET_XBUTTON_WPARAM(wParam) == XBUTTON1 ? Qt::BackButton : Qt::ForwardButton;
+}
+
+static inline MouseEvent eventFromMsg(const MSG &msg)
+{
+    switch (msg.message) {
+    case WM_MOUSEMOVE:
+        return {QEvent::MouseMove, Qt::NoButton};
+    case WM_LBUTTONDOWN:
+        return {QEvent::MouseButtonPress, Qt::LeftButton};
+    case WM_LBUTTONUP:
+        return {QEvent::MouseButtonRelease, Qt::LeftButton};
+    case WM_LBUTTONDBLCLK: // Qt QPA does not handle double clicks, send as press
+        return {QEvent::MouseButtonPress, Qt::LeftButton};
+    case WM_MBUTTONDOWN:
+        return {QEvent::MouseButtonPress, Qt::MiddleButton};
+    case WM_MBUTTONUP:
+        return {QEvent::MouseButtonRelease, Qt::MiddleButton};
+    case WM_MBUTTONDBLCLK:
+        return {QEvent::MouseButtonPress, Qt::MiddleButton};
+    case WM_RBUTTONDOWN:
+        return {QEvent::MouseButtonPress, Qt::RightButton};
+    case WM_RBUTTONUP:
+        return {QEvent::MouseButtonRelease, Qt::RightButton};
+    case WM_RBUTTONDBLCLK:
+        return {QEvent::MouseButtonPress, Qt::RightButton};
+    case WM_XBUTTONDOWN:
+        return {QEvent::MouseButtonPress, extraButton(msg.wParam)};
+    case WM_XBUTTONUP:
+        return {QEvent::MouseButtonRelease, extraButton(msg.wParam)};
+    case WM_XBUTTONDBLCLK:
+        return {QEvent::MouseButtonPress, extraButton(msg.wParam)};
+    case WM_NCMOUSEMOVE:
+        return {QEvent::NonClientAreaMouseMove, Qt::NoButton};
+    case WM_NCLBUTTONDOWN:
+        return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton};
+    case WM_NCLBUTTONUP:
+        return {QEvent::NonClientAreaMouseButtonRelease, Qt::LeftButton};
+    case WM_NCLBUTTONDBLCLK:
+        return {QEvent::NonClientAreaMouseButtonPress, Qt::LeftButton};
+    case WM_NCMBUTTONDOWN:
+        return {QEvent::NonClientAreaMouseButtonPress, Qt::MiddleButton};
+    case WM_NCMBUTTONUP:
+        return {QEvent::NonClientAreaMouseButtonRelease, Qt::MiddleButton};
+    case WM_NCMBUTTONDBLCLK:
+        return {QEvent::NonClientAreaMouseButtonPress, Qt::MiddleButton};
+    case WM_NCRBUTTONDOWN:
+        return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton};
+    case WM_NCRBUTTONUP:
+        return {QEvent::NonClientAreaMouseButtonRelease, Qt::RightButton};
+    case WM_NCRBUTTONDBLCLK:
+        return {QEvent::NonClientAreaMouseButtonPress, Qt::RightButton};
+    default: // WM_MOUSELEAVE
+        break;
+    }
+    return {QEvent::None, Qt::NoButton};
+}
+
+static Qt::MouseButtons mouseButtonsFromKeyState(WPARAM keyState)
+{
+    Qt::MouseButtons result = Qt::NoButton;
+    if (keyState & MK_LBUTTON)
+        result |= Qt::LeftButton;
+    if (keyState & MK_RBUTTON)
+        result |= Qt::RightButton;
+    if (keyState & MK_MBUTTON)
+        result |= Qt::MiddleButton;
+    if (keyState & MK_XBUTTON1)
+        result |= Qt::XButton1;
+    if (keyState & MK_XBUTTON2)
+        result |= Qt::XButton2;
+    return result;
+}
+
+static Qt::MouseButtons queryMouseButtons()
+{
+    Qt::MouseButtons result = Qt::NoButton;
+    const bool mouseSwapped = GetSystemMetrics(SM_SWAPBUTTON);
+    if (GetAsyncKeyState(VK_LBUTTON) < 0)
+        result |= mouseSwapped ? Qt::RightButton: Qt::LeftButton;
+    if (GetAsyncKeyState(VK_RBUTTON) < 0)
+        result |= mouseSwapped ? Qt::LeftButton : Qt::RightButton;
+    if (GetAsyncKeyState(VK_MBUTTON) < 0)
+        result |= Qt::MiddleButton;
+    if (GetAsyncKeyState(VK_XBUTTON1) < 0)
+        result |= Qt::XButton1;
+    if (GetAsyncKeyState(VK_XBUTTON2) < 0)
+        result |= Qt::XButton2;
+    return result;
+}
+
+static QWindow *getWindowUnderPointer(QWindow *window, QPoint globalPos)
+{
+    QWindowsWindow *platformWindow = static_cast<QWindowsWindow *>(window->handle());
+
+    QWindow *currentWindowUnderPointer = platformWindow->hasMouseCapture() ?
+                QWindowsScreen::windowAt(globalPos, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT) : window;
+
+    while (currentWindowUnderPointer && currentWindowUnderPointer->flags() & Qt::WindowTransparentForInput)
+        currentWindowUnderPointer = currentWindowUnderPointer->parent();
+
+    // QTBUG-44332: When Qt is running at low integrity level and
+    // a Qt Window is parented on a Window of a higher integrity process
+    // using QWindow::fromWinId() (for example, Qt running in a browser plugin)
+    // ChildWindowFromPointEx() may not find the Qt window (failing with ERROR_ACCESS_DENIED)
+    if (!currentWindowUnderPointer) {
+        const QRect clientRect(QPoint(0, 0), window->size());
+        if (clientRect.contains(globalPos))
+            currentWindowUnderPointer = window;
+    }
+    return currentWindowUnderPointer;
+}
+
+static bool trackLeave(HWND hwnd)
+{
+    TRACKMOUSEEVENT tme;
+    tme.cbSize = sizeof(TRACKMOUSEEVENT);
+    tme.dwFlags = TME_LEAVE;
+    tme.hwndTrack = hwnd;
+    tme.dwHoverTime = HOVER_DEFAULT;
+    return TrackMouseEvent(&tme);
+}
+
+static bool isValidWheelReceiver(QWindow *candidate)
+{
+    if (candidate) {
+        const QWindow *toplevel = QWindowsWindow::topLevelOf(candidate);
+        if (toplevel->handle() && toplevel->handle()->isForeignWindow())
+            return true;
+        if (const QWindowsWindow *ww = QWindowsWindow::windowsWindowOf(toplevel))
+            return !ww->testFlag(QWindowsWindow::BlockedByModal);
+    }
+    return false;
+}
+
+QWindowsPointerHandler::QPointingDevicePtr QWindowsPointerHandler::createTouchDevice(bool mouseEmulation)
+{
+     const int digitizers = GetSystemMetrics(SM_DIGITIZER);
+     if (!(digitizers & (NID_INTEGRATED_TOUCH | NID_EXTERNAL_TOUCH)))
+         return nullptr;
+     const int tabletPc = GetSystemMetrics(SM_TABLETPC);
+     const int maxTouchPoints = GetSystemMetrics(SM_MAXIMUMTOUCHES);
+     const QPointingDevice::DeviceType type = (digitizers & NID_INTEGRATED_TOUCH)
+         ? QInputDevice::DeviceType::TouchScreen : QInputDevice::DeviceType::TouchPad;
+     QInputDevice::Capabilities capabilities = QInputDevice::Capability::Position
+         | QInputDevice::Capability::Area
+         | QInputDevice::Capability::NormalizedPosition;
+     if (type != QInputDevice::DeviceType::TouchScreen) {
+         capabilities.setFlag(QInputDevice::Capability::MouseEmulation);
+         capabilities.setFlag(QInputDevice::Capability::Scroll);
+     } else if (mouseEmulation) {
+         capabilities.setFlag(QInputDevice::Capability::MouseEmulation);
+     }
+
+     const int flags = digitizers & ~NID_READY;
+     qCDebug(lcQpaEvents) << "Digitizers:" << Qt::hex << Qt::showbase << flags
+         << "Ready:" << (digitizers & NID_READY) << Qt::dec << Qt::noshowbase
+         << "Tablet PC:" << tabletPc << "Max touch points:" << maxTouchPoints << "Capabilities:" << capabilities;
+
+     const int buttonCount = type == QInputDevice::DeviceType::TouchScreen ? 1 : 3;
+     // TODO: use system-provided name and device ID rather than empty-string and m_nextInputDeviceId
+     const qint64 systemId = m_nextInputDeviceId++ | (qint64(flags << 2));
+     auto d = new QPointingDevice(QString(), systemId, type,
+                                  QPointingDevice::PointerType::Finger,
+                                  capabilities, maxTouchPoints, buttonCount,
+                                  QString(), QPointingDeviceUniqueId::fromNumericId(systemId));
+     return QPointingDevicePtr(d);
+}
+
+void QWindowsPointerHandler::clearEvents()
+{
+    m_lastEventType = QEvent::None;
+    m_lastEventButton = Qt::NoButton;
+}
+
+void QWindowsPointerHandler::handleCaptureRelease(QWindow *window,
+                                                  QWindow *currentWindowUnderPointer,
+                                                  HWND hwnd,
+                                                  QEvent::Type eventType,
+                                                  Qt::MouseButtons mouseButtons)
+{
+    auto *platformWindow = static_cast<QWindowsWindow *>(window->handle());
+
+    // Qt expects the platform plugin to capture the mouse on any button press until release.
+    if (!platformWindow->hasMouseCapture() && eventType == QEvent::MouseButtonPress) {
+
+        platformWindow->setMouseGrabEnabled(true);
+        platformWindow->setFlag(QWindowsWindow::AutoMouseCapture);
+        qCDebug(lcQpaEvents) << "Automatic mouse capture " << window;
+
+        // Implement "Click to focus" for native child windows (unless it is a native widget window).
+        if (!window->isTopLevel() && !window->inherits("QWidgetWindow") && QGuiApplication::focusWindow() != window)
+            window->requestActivate();
+
+    } else if (platformWindow->hasMouseCapture()
+               && platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)
+               && eventType == QEvent::MouseButtonRelease
+               && !mouseButtons) {
+
+        platformWindow->setMouseGrabEnabled(false);
+        qCDebug(lcQpaEvents) << "Releasing automatic mouse capture " << window;
+    }
+
+    // Enter new window: track to generate leave event.
+    // If there is an active capture, only track if the current window is capturing,
+    // so we don't get extra leave when cursor leaves the application.
+    if (window != m_currentWindow &&
+            (!platformWindow->hasMouseCapture() || currentWindowUnderPointer == window)) {
+        trackLeave(hwnd);
+        m_currentWindow =  window;
+    }
+}
+
+void QWindowsPointerHandler::handleEnterLeave(QWindow *window,
+                                              QWindow *currentWindowUnderPointer,
+                                              QPoint globalPos)
+{
+    auto *platformWindow = static_cast<QWindowsWindow *>(window->handle());
+    const bool hasCapture = platformWindow->hasMouseCapture();
+
+    // No enter or leave events are sent as long as there is an autocapturing window.
+    if (!hasCapture || !platformWindow->testFlag(QWindowsWindow::AutoMouseCapture)) {
+
+        // Leave is needed if:
+        // 1) There is no capture and we move from a window to another window.
+        //    Note: Leaving the application entirely is handled in translateMouseEvent(WM_MOUSELEAVE).
+        // 2) There is capture and we move out of the capturing window.
+        // 3) There is a new capture and we were over another window.
+        if ((m_windowUnderPointer && m_windowUnderPointer != currentWindowUnderPointer
+                && (!hasCapture || window == m_windowUnderPointer))
+            || (hasCapture && m_previousCaptureWindow != window && m_windowUnderPointer
+                && m_windowUnderPointer != window)) {
+
+            qCDebug(lcQpaEvents) << "Leaving window " << m_windowUnderPointer;
+            QWindowSystemInterface::handleLeaveEvent(m_windowUnderPointer);
+
+            if (hasCapture && currentWindowUnderPointer != window) {
+                // Clear tracking if capturing and current window is not the capturing window
+                // to avoid leave when mouse actually leaves the application.
+                m_currentWindow = nullptr;
+                // We are not officially in any window, but we need to set some cursor to clear
+                // whatever cursor the left window had, so apply the cursor of the capture window.
+                platformWindow->applyCursor();
+            }
+        }
+
+        // Enter is needed if:
+        // 1) There is no capture and we move to a new window.
+        // 2) There is capture and we move into the capturing window.
+        // 3) The capture just ended and we are over non-capturing window.
+        if ((currentWindowUnderPointer && m_windowUnderPointer != currentWindowUnderPointer
+                && (!hasCapture || currentWindowUnderPointer == window))
+            || (m_previousCaptureWindow && !hasCapture && currentWindowUnderPointer
+                && currentWindowUnderPointer != m_previousCaptureWindow)) {
+
+            QPoint wumLocalPos;
+            if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(currentWindowUnderPointer)) {
+                wumLocalPos = wumPlatformWindow->mapFromGlobal(globalPos);
+                wumPlatformWindow->applyCursor();
+            }
+            qCDebug(lcQpaEvents) << "Entering window " << currentWindowUnderPointer;
+            QWindowSystemInterface::handleEnterEvent(currentWindowUnderPointer, wumLocalPos, globalPos);
+        }
+
+        // We need to track m_windowUnderPointer separately from m_currentWindow, as Windows
+        // mouse tracking will not trigger WM_MOUSELEAVE for leaving window when mouse capture is set.
+        m_windowUnderPointer = currentWindowUnderPointer;
+    }
+
+    m_previousCaptureWindow = hasCapture ? window : nullptr;
+}
+
+bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
+                                                 QtWindows::WindowsEventType et,
+                                                 MSG msg, PVOID vTouchInfo, quint32 count)
+{
+    Q_UNUSED(hwnd);
+
+    auto *touchInfo = static_cast<POINTER_TOUCH_INFO *>(vTouchInfo);
+
+    if (et & QtWindows::NonClientEventFlag)
+        return false; // Let DefWindowProc() handle Non Client messages.
+
+    if (count < 1)
+        return false;
+
+    if (msg.message == WM_POINTERCAPTURECHANGED) {
+        const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+        QWindowSystemInterface::handleTouchCancelEvent(window, m_touchDevice.data(),
+                                                       keyMapper->queryKeyboardModifiers());
+        m_lastTouchPoints.clear();
+        return true;
+    }
+
+    if (msg.message == WM_POINTERLEAVE) {
+        for (quint32 i = 0; i < count; ++i) {
+            const quint32 pointerId = touchInfo[i].pointerInfo.pointerId;
+            int id = m_touchInputIDToTouchPointID.value(pointerId, -1);
+            if (id != -1)
+                m_lastTouchPoints.remove(id);
+        }
+        // Send LeaveEvent to reset hover when the last finger leaves the touch screen (QTBUG-62912)
+        QWindowSystemInterface::handleEnterLeaveEvent(nullptr, window);
+    }
+
+    // Only handle down/up/update, ignore others like WM_POINTERENTER, WM_POINTERLEAVE, etc.
+    if (msg.message > WM_POINTERUP)
+        return false;
+
+    const QScreen *screen = window->screen();
+    if (!screen)
+        screen = QGuiApplication::primaryScreen();
+    if (!screen)
+        return false;
+
+    const QRect screenGeometry = screen->geometry();
+
+    QList<QWindowSystemInterface::TouchPoint> touchPoints;
+
+    if (QWindowsContext::verbose > 1)
+        qCDebug(lcQpaEvents).noquote().nospace() << Qt::showbase
+                << __FUNCTION__
+                << " message=" << Qt::hex << msg.message
+                << " count=" << Qt::dec << count;
+
+    QEventPoint::States allStates;
+    QSet<int> inputIds;
+
+    for (quint32 i = 0; i < count; ++i) {
+        if (QWindowsContext::verbose > 1)
+            qCDebug(lcQpaEvents).noquote().nospace() << Qt::showbase
+                    << "    TouchPoint id=" << touchInfo[i].pointerInfo.pointerId
+                    << " frame=" << touchInfo[i].pointerInfo.frameId
+                    << " flags=" << Qt::hex << touchInfo[i].pointerInfo.pointerFlags;
+
+        QWindowSystemInterface::TouchPoint touchPoint;
+        const quint32 pointerId = touchInfo[i].pointerInfo.pointerId;
+        int id = m_touchInputIDToTouchPointID.value(pointerId, -1);
+        if (id == -1) {
+            // Start tracking after fingers touch the screen. Ignore bogus updates after touch is released.
+            if ((touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_DOWN) == 0)
+                continue;
+            id = m_touchInputIDToTouchPointID.size();
+            m_touchInputIDToTouchPointID.insert(pointerId, id);
+        }
+        touchPoint.id = id;
+        touchPoint.pressure = (touchInfo[i].touchMask & TOUCH_MASK_PRESSURE) ?
+                    touchInfo[i].pressure / 1024.0 : 1.0;
+        if (m_lastTouchPoints.contains(touchPoint.id))
+            touchPoint.normalPosition = m_lastTouchPoints.value(touchPoint.id).normalPosition;
+
+        const QPointF screenPos = QPointF(touchInfo[i].pointerInfo.ptPixelLocation.x,
+                                          touchInfo[i].pointerInfo.ptPixelLocation.y);
+
+        if (touchInfo[i].touchMask & TOUCH_MASK_CONTACTAREA)
+            touchPoint.area.setSize(QSizeF(touchInfo[i].rcContact.right - touchInfo[i].rcContact.left,
+                                           touchInfo[i].rcContact.bottom - touchInfo[i].rcContact.top));
+        touchPoint.area.moveCenter(screenPos);
+        QPointF normalPosition = QPointF(screenPos.x() / screenGeometry.width(),
+                                         screenPos.y() / screenGeometry.height());
+        const bool stationaryTouchPoint = (normalPosition == touchPoint.normalPosition);
+        touchPoint.normalPosition = normalPosition;
+
+        if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_DOWN) {
+            touchPoint.state = QEventPoint::State::Pressed;
+            m_lastTouchPoints.insert(touchPoint.id, touchPoint);
+        } else if (touchInfo[i].pointerInfo.pointerFlags & POINTER_FLAG_UP) {
+            touchPoint.state = QEventPoint::State::Released;
+            m_lastTouchPoints.remove(touchPoint.id);
+        } else {
+            touchPoint.state = stationaryTouchPoint ? QEventPoint::State::Stationary : QEventPoint::State::Updated;
+            m_lastTouchPoints.insert(touchPoint.id, touchPoint);
+        }
+        allStates |= touchPoint.state;
+
+        touchPoints.append(touchPoint);
+        inputIds.insert(touchPoint.id);
+
+        // Avoid getting repeated messages for this frame if there are multiple pointerIds
+        SkipPointerFrameMessages(touchInfo[i].pointerInfo.pointerId);
+    }
+
+    // Some devices send touches for each finger in a different message/frame, instead of consolidating
+    // them in the same frame as we were expecting. We account for missing unreleased touches here.
+    for (auto tp : std::as_const(m_lastTouchPoints)) {
+        if (!inputIds.contains(tp.id)) {
+            tp.state = QEventPoint::State::Stationary;
+            allStates |= tp.state;
+            touchPoints.append(tp);
+        }
+    }
+
+    if (touchPoints.count() == 0)
+        return false;
+
+    // all touch points released, forget the ids we've seen.
+    if (allStates == QEventPoint::State::Released)
+        m_touchInputIDToTouchPointID.clear();
+
+    const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+    QWindowSystemInterface::handleTouchEvent(window, m_touchDevice.data(), touchPoints,
+                                             keyMapper->queryKeyboardModifiers());
+    return false; // Allow mouse messages to be generated.
+}
+
+#if QT_CONFIG(tabletevent)
+QWindowsPointerHandler::QPointingDevicePtr QWindowsPointerHandler::findTabletDevice(QPointingDevice::PointerType pointerType) const
+{
+    for (const auto &d : m_tabletDevices) {
+        if (d->pointerType() == pointerType)
+            return d;
+    }
+    return {};
+}
+#endif
+
+bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWindows::WindowsEventType et,
+                                               MSG msg, PVOID vPenInfo)
+{
+#if QT_CONFIG(tabletevent)
+    if (et & QtWindows::NonClientEventFlag)
+        return false; // Let DefWindowProc() handle Non Client messages.
+
+    auto *penInfo = static_cast<POINTER_PEN_INFO *>(vPenInfo);
+
+    RECT pRect, dRect;
+    if (!GetPointerDeviceRects(penInfo->pointerInfo.sourceDevice, &pRect, &dRect))
+        return false;
+
+    const auto systemId = (qint64)penInfo->pointerInfo.sourceDevice;
+    const QPoint globalPos = QPoint(penInfo->pointerInfo.ptPixelLocation.x, penInfo->pointerInfo.ptPixelLocation.y);
+    const QPoint localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, globalPos);
+    const QPointF hiResGlobalPos = QPointF(dRect.left + qreal(penInfo->pointerInfo.ptHimetricLocation.x - pRect.left)
+                                           / (pRect.right - pRect.left) * (dRect.right - dRect.left),
+                                           dRect.top + qreal(penInfo->pointerInfo.ptHimetricLocation.y - pRect.top)
+                                           / (pRect.bottom - pRect.top) * (dRect.bottom - dRect.top));
+    const bool hasPressure = (penInfo->penMask & PEN_MASK_PRESSURE) != 0;
+    const bool hasRotation =  (penInfo->penMask & PEN_MASK_ROTATION) != 0;
+    const qreal pressure = hasPressure ? qreal(penInfo->pressure) / 1024.0 : 0.5;
+    const qreal rotation = hasRotation ? qreal(penInfo->rotation) : 0.0;
+    const qreal tangentialPressure = 0.0;
+    const bool hasTiltX = (penInfo->penMask & PEN_MASK_TILT_X) != 0;
+    const bool hasTiltY = (penInfo->penMask & PEN_MASK_TILT_Y) != 0;
+    const int xTilt = hasTiltX ? penInfo->tiltX : 0;
+    const int yTilt = hasTiltY ? penInfo->tiltY : 0;
+    const int z = 0;
+
+    if (QWindowsContext::verbose > 1)
+        qCDebug(lcQpaEvents).noquote().nospace() << Qt::showbase
+            << __FUNCTION__ << " systemId=" << systemId
+            << " globalPos=" << globalPos << " localPos=" << localPos << " hiResGlobalPos=" << hiResGlobalPos
+            << " message=" << Qt::hex << msg.message
+            << " flags=" << Qt::hex << penInfo->pointerInfo.pointerFlags;
+
+    QPointingDevice::PointerType type;
+    // Since it may be the middle button, so if the checks fail then it should
+    // be set to Middle if it was used.
+    Qt::MouseButtons mouseButtons = queryMouseButtons();
+
+    const bool pointerInContact = IS_POINTER_INCONTACT_WPARAM(msg.wParam);
+    if (pointerInContact)
+        mouseButtons = Qt::LeftButton;
+
+    if (penInfo->penFlags & (PEN_FLAG_ERASER | PEN_FLAG_INVERTED)) {
+        type = QPointingDevice::PointerType::Eraser;
+    } else {
+        type = QPointingDevice::PointerType::Pen;
+        if (pointerInContact && penInfo->penFlags & PEN_FLAG_BARREL)
+            mouseButtons = Qt::RightButton; // Either left or right, not both
+    }
+
+    auto device = findTabletDevice(type);
+    if (device.isNull()) {
+        QInputDevice::Capabilities caps(QInputDevice::Capability::Position
+                                        | QInputDevice::Capability::MouseEmulation
+                                        | QInputDevice::Capability::Hover);
+        if (hasPressure)
+             caps |= QInputDevice::Capability::Pressure;
+        if (hasRotation)
+            caps |= QInputDevice::Capability::Rotation;
+        if (hasTiltX)
+            caps |= QInputDevice::Capability::XTilt;
+        if (hasTiltY)
+            caps |= QInputDevice::Capability::YTilt;
+        const qint64 uniqueId = systemId | (qint64(type) << 32L);
+        device.reset(new QPointingDevice(QStringLiteral("wmpointer"),
+                                         systemId, QInputDevice::DeviceType::Stylus,
+                                         type, caps, 1, 3, QString(),
+                                         QPointingDeviceUniqueId::fromNumericId(uniqueId)));
+        QWindowSystemInterface::registerInputDevice(device.data());
+        m_tabletDevices.append(device);
+    }
+
+    const auto uniqueId = device->uniqueId().numericId();
+    m_activeTabletDevice = device;
+
+    switch (msg.message) {
+    case WM_POINTERENTER: {
+        QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, device.data(), true);
+        m_windowUnderPointer = window;
+        // The local coordinates may fall outside the window.
+        // Wait until the next update to send the enter event.
+        m_needsEnterOnPointerUpdate = true;
+        break;
+    }
+    case WM_POINTERLEAVE:
+        if (m_windowUnderPointer && m_windowUnderPointer == m_currentWindow) {
+            QWindowSystemInterface::handleLeaveEvent(m_windowUnderPointer);
+            m_windowUnderPointer = nullptr;
+            m_currentWindow = nullptr;
+        }
+        QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, device.data(), false);
+        break;
+    case WM_POINTERDOWN:
+    case WM_POINTERUP:
+    case WM_POINTERUPDATE: {
+        QWindow *target = QGuiApplicationPrivate::tabletDevicePoint(uniqueId).target; // Pass to window that grabbed it.
+        if (!target && m_windowUnderPointer)
+            target = m_windowUnderPointer;
+        if (!target)
+            target = window;
+
+        if (m_needsEnterOnPointerUpdate) {
+            m_needsEnterOnPointerUpdate = false;
+            if (window != m_currentWindow) {
+                // make sure we subscribe to leave events for this window
+                trackLeave(hwnd);
+
+                QWindowSystemInterface::handleEnterEvent(window, localPos, globalPos);
+                m_currentWindow = window;
+                if (QWindowsWindow *wumPlatformWindow = QWindowsWindow::windowsWindowOf(target))
+                    wumPlatformWindow->applyCursor();
+            }
+        }
+        const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+        const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
+
+        QWindowSystemInterface::handleTabletEvent(target, device.data(),
+                                                  localPos, hiResGlobalPos, mouseButtons,
+                                                  pressure, xTilt, yTilt, tangentialPressure,
+                                                  rotation, z, keyModifiers);
+        return false;  // Allow mouse messages to be generated.
+    }
+    }
+    return true;
+#else
+    Q_UNUSED(window);
+    Q_UNUSED(hwnd);
+    Q_UNUSED(et);
+    Q_UNUSED(msg);
+    Q_UNUSED(vPenInfo);
+    return false;
+#endif
+}
+
+static inline bool isMouseEventSynthesizedFromPenOrTouch()
+{
+    // For details, see
+    // https://docs.microsoft.com/en-us/windows/desktop/tablet/system-events-and-mouse-messages
+    const LONG_PTR SIGNATURE_MASK = 0xFFFFFF00;
+    const LONG_PTR MI_WP_SIGNATURE = 0xFF515700;
+
+    return ((::GetMessageExtraInfo() & SIGNATURE_MASK) == MI_WP_SIGNATURE);
+}
+
+bool QWindowsPointerHandler::translateMouseWheelEvent(QWindow *window,
+                                                      QWindow *currentWindowUnderPointer,
+                                                      MSG msg,
+                                                      QPoint globalPos,
+                                                      Qt::KeyboardModifiers keyModifiers)
+{
+    QWindow *receiver = currentWindowUnderPointer;
+    if (!isValidWheelReceiver(receiver))
+        receiver = window;
+    if (!isValidWheelReceiver(receiver))
+        return true;
+
+    int delta = GET_WHEEL_DELTA_WPARAM(msg.wParam);
+
+    // Qt horizontal wheel rotation orientation is opposite to the one in WM_MOUSEHWHEEL
+    if (msg.message == WM_MOUSEHWHEEL)
+        delta = -delta;
+
+    const QPoint angleDelta = (msg.message == WM_MOUSEHWHEEL || (keyModifiers & Qt::AltModifier)) ?
+                QPoint(delta, 0) : QPoint(0, delta);
+
+    QPoint localPos = QWindowsGeometryHint::mapFromGlobal(receiver, globalPos);
+
+    QWindowSystemInterface::handleWheelEvent(receiver, localPos, globalPos, QPoint(), angleDelta, keyModifiers);
+    return true;
+}
+
+// Process legacy mouse messages here.
+bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
+                                                 HWND hwnd,
+                                                 QtWindows::WindowsEventType et,
+                                                 MSG msg,
+                                                 LRESULT *result)
+{
+    *result = 0;
+
+    QPoint localPos;
+    QPoint globalPos;
+    QPoint eventPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));
+
+    if ((et == QtWindows::MouseWheelEvent) || (et & QtWindows::NonClientEventFlag)) {
+        globalPos = eventPos;
+        localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, eventPos);
+    } else {
+        if (QWindowsBaseWindow::isRtlLayout(hwnd))  {
+            RECT clientArea;
+            GetClientRect(hwnd, &clientArea);
+            eventPos.setX(clientArea.right - eventPos.x());
+        }
+
+        globalPos = QWindowsGeometryHint::mapToGlobal(hwnd, eventPos);
+        auto targetHwnd = hwnd;
+        if (auto *pw = window->handle())
+            targetHwnd = HWND(pw->winId());
+        localPos = targetHwnd == hwnd
+            ? eventPos
+            : QWindowsGeometryHint::mapFromGlobal(targetHwnd, globalPos);
+    }
+
+    const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+    const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
+    QWindow *currentWindowUnderPointer = getWindowUnderPointer(window, globalPos);
+
+    if (et == QtWindows::MouseWheelEvent)
+        return translateMouseWheelEvent(window, currentWindowUnderPointer, msg, globalPos, keyModifiers);
+
+    // Windows sends a mouse move with no buttons pressed to signal "Enter"
+    // when a window is shown over the cursor. Discard the event and only use
+    // it for generating QEvent::Enter to be consistent with other platforms -
+    // X11 and macOS.
+    bool discardEvent = false;
+    if (msg.message == WM_MOUSEMOVE) {
+        Q_CONSTINIT static QPoint lastMouseMovePos;
+        if (msg.wParam == 0 && (m_windowUnderPointer.isNull() || globalPos == lastMouseMovePos))
+            discardEvent = true;
+        lastMouseMovePos = globalPos;
+    }
+
+    Qt::MouseEventSource source = Qt::MouseEventNotSynthesized;
+    const QPointingDevice *device = QWindowsMouseHandler::primaryMouse();
+
+    // Following the logic of the old mouse handler, only events synthesized
+    // for touch screen are marked as such. On some systems, using the bit 7 of
+    // the extra msg info for checking if synthesized for touch does not work,
+    // so we use the pointer type of the last pointer message.
+    if (isMouseEventSynthesizedFromPenOrTouch()) {
+        switch (m_pointerType) {
+        case QT_PT_TOUCH:
+            if (QWindowsIntegration::instance()->options() & QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch)
+                return false;
+            source = Qt::MouseEventSynthesizedBySystem;
+            if (!m_touchDevice.isNull())
+                device = m_touchDevice.data();
+            break;
+        case QT_PT_PEN:
+#if QT_CONFIG(tabletevent)
+            qCDebug(lcQpaTablet) << "ignoring synth-mouse event for tablet event from" << device;
+            return false;
+#endif
+            break;
+        }
+    }
+
+    const MouseEvent mouseEvent = eventFromMsg(msg);
+    Qt::MouseButtons mouseButtons;
+
+    if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick)
+        mouseButtons = queryMouseButtons();
+    else
+        mouseButtons = mouseButtonsFromKeyState(msg.wParam);
+
+    // When the left/right mouse buttons are pressed over the window title bar
+    // WM_NCLBUTTONDOWN/WM_NCRBUTTONDOWN messages are received. But no UP
+    // messages are received on release, only WM_NCMOUSEMOVE/WM_MOUSEMOVE.
+    // We detect it and generate the missing release events here. (QTBUG-75678)
+    // The last event vars are cleared on QWindowsContext::handleExitSizeMove()
+    // to avoid generating duplicated release events.
+    if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress
+            && (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove)
+            && (m_lastEventButton & mouseButtons) == 0) {
+            auto releaseType = mouseEvent.type == QEvent::NonClientAreaMouseMove ?
+                QEvent::NonClientAreaMouseButtonRelease : QEvent::MouseButtonRelease;
+            QWindowSystemInterface::handleMouseEvent(window, device, localPos, globalPos, mouseButtons, m_lastEventButton,
+                                                     releaseType, keyModifiers, source);
+    }
+    m_lastEventType = mouseEvent.type;
+    m_lastEventButton = mouseEvent.button;
+
+    if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) {
+        QWindowSystemInterface::handleMouseEvent(window, device, localPos, globalPos, mouseButtons,
+                                                           mouseEvent.button, mouseEvent.type, keyModifiers, source);
+        return false; // Allow further event processing
+    }
+
+    if (msg.message == WM_MOUSELEAVE) {
+        if (window == m_currentWindow) {
+            QWindow *leaveTarget = m_windowUnderPointer ? m_windowUnderPointer : m_currentWindow;
+            qCDebug(lcQpaEvents) << "Leaving window " << leaveTarget;
+            QWindowSystemInterface::handleLeaveEvent(leaveTarget);
+            m_windowUnderPointer = nullptr;
+            m_currentWindow = nullptr;
+        }
+        return true;
+    }
+
+    handleCaptureRelease(window, currentWindowUnderPointer, hwnd, mouseEvent.type, mouseButtons);
+    handleEnterLeave(window, currentWindowUnderPointer, globalPos);
+
+    if (!discardEvent && mouseEvent.type != QEvent::None) {
+        QWindowSystemInterface::handleMouseEvent(window, device, localPos, globalPos, mouseButtons,
+                                                 mouseEvent.button, mouseEvent.type, keyModifiers, source);
+    }
+
+    // QTBUG-48117, force synchronous handling for the extra buttons so that WM_APPCOMMAND
+    // is sent for unhandled WM_XBUTTONDOWN.
+    return (msg.message != WM_XBUTTONUP && msg.message != WM_XBUTTONDOWN && msg.message != WM_XBUTTONDBLCLK)
+        || QWindowSystemInterface::flushWindowSystemEvents();
+}
+
+QT_END_NAMESPACE
diff --git a/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp b/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp
index a50f9fd4b0..a8f65209b3 100644
--- a/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp
+++ b/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp
@@ -43,10 +43,12 @@ static inline QDpi deviceDPI(HDC hdc)
 
 static inline QDpi monitorDPI(HMONITOR hMonitor)
 {
-    UINT dpiX;
-    UINT dpiY;
-    if (SUCCEEDED(GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY)))
-        return QDpi(dpiX, dpiY);
+    if (QWindowsContext::shcoredll.isValid()) {
+        UINT dpiX;
+        UINT dpiY;
+        if (SUCCEEDED(QWindowsContext::shcoredll.getDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY)))
+            return QDpi(dpiX, dpiY);
+    }
     return {0, 0};
 }
 
@@ -577,47 +579,51 @@ QRect QWindowsScreen::virtualGeometry(const QPlatformScreen *screen) // cf QScre
 bool QWindowsScreen::setOrientationPreference(Qt::ScreenOrientation o)
 {
     bool result = false;
-    ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE;
-    switch (o) {
-    case Qt::PrimaryOrientation:
-        break;
-    case Qt::PortraitOrientation:
-        orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT;
-        break;
-    case Qt::LandscapeOrientation:
-        orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE;
-        break;
-    case Qt::InvertedPortraitOrientation:
-        orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED;
-        break;
-    case Qt::InvertedLandscapeOrientation:
-        orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED;
-        break;
+    if (QWindowsContext::user32dll.setDisplayAutoRotationPreferences) {
+        ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE;
+        switch (o) {
+        case Qt::PrimaryOrientation:
+            break;
+        case Qt::PortraitOrientation:
+            orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT;
+            break;
+        case Qt::LandscapeOrientation:
+            orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE;
+            break;
+        case Qt::InvertedPortraitOrientation:
+            orientationPreference = ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED;
+            break;
+        case Qt::InvertedLandscapeOrientation:
+            orientationPreference = ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED;
+            break;
+        }
+        result = QWindowsContext::user32dll.setDisplayAutoRotationPreferences(orientationPreference);
     }
-    result = SetDisplayAutoRotationPreferences(orientationPreference);
     return result;
 }
 
 Qt::ScreenOrientation QWindowsScreen::orientationPreference()
 {
     Qt::ScreenOrientation result = Qt::PrimaryOrientation;
-    ORIENTATION_PREFERENCE orientationPreference = ORIENTATION_PREFERENCE_NONE;
-    if (GetDisplayAutoRotationPreferences(&orientationPreference)) {
-        switch (orientationPreference) {
-        case ORIENTATION_PREFERENCE_NONE:
-            break;
-        case ORIENTATION_PREFERENCE_LANDSCAPE:
-            result = Qt::LandscapeOrientation;
-            break;
-        case ORIENTATION_PREFERENCE_PORTRAIT:
-            result = Qt::PortraitOrientation;
-            break;
-        case ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED:
-            result = Qt::InvertedLandscapeOrientation;
-            break;
-        case ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED:
-            result = Qt::InvertedPortraitOrientation;
-            break;
+    if (QWindowsContext::user32dll.getDisplayAutoRotationPreferences) {
+        DWORD orientationPreference = ORIENTATION_PREFERENCE_NONE;
+        if (QWindowsContext::user32dll.getDisplayAutoRotationPreferences(&orientationPreference)) {
+            switch (orientationPreference) {
+            case ORIENTATION_PREFERENCE_NONE:
+                break;
+            case ORIENTATION_PREFERENCE_LANDSCAPE:
+                result = Qt::LandscapeOrientation;
+                break;
+            case ORIENTATION_PREFERENCE_PORTRAIT:
+                result = Qt::PortraitOrientation;
+                break;
+            case ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED:
+                result = Qt::InvertedLandscapeOrientation;
+                break;
+            case ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED:
+                result = Qt::InvertedPortraitOrientation;
+                break;
+            }
         }
     }
     return result;
diff --git a/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp b/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp
index 2a01b0fa0d..314410dce7 100644
--- a/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp
+++ b/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp
@@ -44,6 +44,8 @@
 
 #include <algorithm>
 
+#include <VersionHelpers.h>
+
 #if QT_CONFIG(cpp_winrt)
 #   include <QtCore/private/qt_winrtbase_p.h>
 
@@ -672,8 +674,14 @@ void QWindowsTheme::refreshFonts()
     fixedFont.setStyleHint(QFont::TypeWriter);
 
     LOGFONT lfIconTitleFont;
-    SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi);
-    const QFont iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont, dpi);
+    QFont iconTitleFont;
+    if (QWindowsContext::user32dll.systemParametersInfoForDpi) {
+        QWindowsContext::user32dll.systemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi);
+        iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont, dpi);
+    } else {
+        SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0);
+        iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont);
+    }
 
     m_fonts[SystemFont] = new QFont(QWindowsFontDatabase::systemDefaultFont());
     m_fonts[MenuFont] = new QFont(menuFont);
diff --git a/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp.orig b/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp.orig
new file mode 100644
index 0000000000..2a01b0fa0d
--- /dev/null
+++ b/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp.orig
@@ -0,0 +1,1160 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtCore/qt_windows.h>
+
+#include "qwindowstheme.h"
+#include "qwindowsmenu.h"
+#include "qwindowsdialoghelpers.h"
+#include "qwindowscontext.h"
+#include "qwindowsiconengine.h"
+#include "qwindowsintegration.h"
+#if QT_CONFIG(systemtrayicon)
+#  include "qwindowssystemtrayicon.h"
+#endif
+#include "qwindowsscreen.h"
+#include <commctrl.h>
+#include <objbase.h>
+#ifndef Q_CC_MINGW
+#  include <commoncontrols.h>
+#endif
+#include <shellapi.h>
+
+#include <QtCore/qvariant.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qsysinfo.h>
+#include <QtCore/qcache.h>
+#include <QtCore/qthread.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qwaitcondition.h>
+#include <QtCore/qoperatingsystemversion.h>
+#include <QtGui/qcolor.h>
+#include <QtGui/qpalette.h>
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qpixmapcache.h>
+#include <qpa/qwindowsysteminterface.h>
+#include <QtGui/private/qabstractfileiconengine_p.h>
+#include <QtGui/private/qwindowsfontdatabase_p.h>
+#include <private/qhighdpiscaling_p.h>
+#include <private/qsystemlibrary_p.h>
+#include <private/qwinregistry_p.h>
+#include <QtCore/private/qfunctions_win_p.h>
+
+#include <algorithm>
+
+#if QT_CONFIG(cpp_winrt)
+#   include <QtCore/private/qt_winrtbase_p.h>
+
+#   include <winrt/Windows.UI.ViewManagement.h>
+#endif // QT_CONFIG(cpp_winrt)
+
+#if defined(__IImageList_INTERFACE_DEFINED__) && defined(__IID_DEFINED__)
+#  define USE_IIMAGELIST
+#endif
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+static inline bool booleanSystemParametersInfo(UINT what, bool defaultValue)
+{
+    BOOL result;
+    if (SystemParametersInfo(what, 0, &result, 0))
+        return result != FALSE;
+    return defaultValue;
+}
+
+static inline DWORD dWordSystemParametersInfo(UINT what, DWORD defaultValue)
+{
+    DWORD result;
+    if (SystemParametersInfo(what, 0, &result, 0))
+        return result;
+    return defaultValue;
+}
+
+static inline QColor mixColors(const QColor &c1, const QColor &c2)
+{
+    return {(c1.red() + c2.red()) / 2,
+            (c1.green() + c2.green()) / 2,
+            (c1.blue() + c2.blue()) / 2};
+}
+
+enum AccentColorLevel {
+    AccentColorDarkest,
+    AccentColorNormal,
+    AccentColorLightest
+};
+
+#if QT_CONFIG(cpp_winrt)
+static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color)
+{
+    return QColor(color.R, color.G, color.B, color.A);
+}
+#endif
+
+[[maybe_unused]] [[nodiscard]] static inline QColor qt_accentColor(AccentColorLevel level)
+{
+#if QT_CONFIG(cpp_winrt)
+    using namespace winrt::Windows::UI::ViewManagement;
+    const auto settings = UISettings();
+    const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
+    const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
+    const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
+#else
+    const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)");
+    if (!registry.isValid())
+        return {};
+    const QVariant value = registry.value(L"AccentColor");
+    if (!value.isValid())
+        return {};
+    // The retrieved value is in the #AABBGGRR format, we need to
+    // convert it to the #AARRGGBB format which Qt expects.
+    const QColor abgr = QColor::fromRgba(qvariant_cast<DWORD>(value));
+    if (!abgr.isValid())
+        return {};
+    const QColor accent = QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha());
+    const QColor accentLight = accent.lighter(120);
+    const QColor accentDarkest = accent.darker(120 * 120 * 120);
+#endif
+    if (level == AccentColorDarkest)
+        return accentDarkest;
+    else if (level == AccentColorLightest)
+        return accentLight;
+    return accent;
+}
+
+static inline QColor getSysColor(int index)
+{
+    COLORREF cr = GetSysColor(index);
+    return QColor(GetRValue(cr), GetGValue(cr), GetBValue(cr));
+}
+
+// QTBUG-48823/Windows 10: SHGetFileInfo() (as called by item views on file system
+// models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the
+// behavior by running it in a thread.
+
+struct QShGetFileInfoParams
+{
+    QShGetFileInfoParams(const QString &fn, DWORD a, SHFILEINFO *i, UINT f, bool *r)
+        : fileName(fn), attributes(a), flags(f), info(i), result(r)
+    { }
+
+    const QString &fileName;
+    const DWORD attributes;
+    const UINT flags;
+    SHFILEINFO *const info;
+    bool *const result;
+};
+
+class QShGetFileInfoThread : public QThread
+{
+public:
+    explicit QShGetFileInfoThread()
+        : QThread(), m_params(nullptr)
+    {
+        connect(this, &QThread::finished, this, &QObject::deleteLater);
+    }
+
+    void run() override
+    {
+        QComHelper comHelper(COINIT_MULTITHREADED);
+
+        QMutexLocker readyLocker(&m_readyMutex);
+        while (!m_cancelled.loadRelaxed()) {
+            if (!m_params && !m_cancelled.loadRelaxed()
+                && !m_readyCondition.wait(&m_readyMutex, QDeadlineTimer(1000ll)))
+                continue;
+
+            if (m_params) {
+                const QString fileName = m_params->fileName;
+                SHFILEINFO info;
+                const bool result = SHGetFileInfo(reinterpret_cast<const wchar_t *>(fileName.utf16()),
+                                                  m_params->attributes, &info, sizeof(SHFILEINFO),
+                                                  m_params->flags);
+                m_doneMutex.lock();
+                if (!m_cancelled.loadRelaxed()) {
+                    *m_params->result = result;
+                    memcpy(m_params->info, &info, sizeof(SHFILEINFO));
+                }
+                m_params = nullptr;
+
+                m_doneCondition.wakeAll();
+                m_doneMutex.unlock();
+            }
+        }
+    }
+
+    bool runWithParams(QShGetFileInfoParams *params, qint64 timeOutMSecs)
+    {
+        QMutexLocker doneLocker(&m_doneMutex);
+
+        m_readyMutex.lock();
+        m_params = params;
+        m_readyCondition.wakeAll();
+        m_readyMutex.unlock();
+
+        return m_doneCondition.wait(&m_doneMutex, QDeadlineTimer(timeOutMSecs));
+    }
+
+    void cancel()
+    {
+        QMutexLocker doneLocker(&m_doneMutex);
+        m_cancelled.storeRelaxed(1);
+        m_readyCondition.wakeAll();
+    }
+
+private:
+    QShGetFileInfoParams *m_params;
+    QAtomicInt m_cancelled;
+    QWaitCondition m_readyCondition;
+    QWaitCondition m_doneCondition;
+    QMutex m_readyMutex;
+    QMutex m_doneMutex;
+};
+
+static bool shGetFileInfoBackground(const QString &fileName, DWORD attributes,
+                                    SHFILEINFO *info, UINT flags,
+                                    qint64 timeOutMSecs = 5000)
+{
+    static QShGetFileInfoThread *getFileInfoThread = nullptr;
+    if (!getFileInfoThread) {
+        getFileInfoThread = new QShGetFileInfoThread;
+        getFileInfoThread->start();
+    }
+
+    bool result = false;
+    QShGetFileInfoParams params(fileName, attributes, info, flags, &result);
+    if (!getFileInfoThread->runWithParams(&params, timeOutMSecs)) {
+        // Cancel and reset getFileInfoThread. It'll
+        // be reinitialized the next time we get called.
+        getFileInfoThread->cancel();
+        getFileInfoThread = nullptr;
+        qWarning().noquote() << "SHGetFileInfo() timed out for " << fileName;
+        return false;
+    }
+    return result;
+}
+
+// from QStyle::standardPalette
+static inline QPalette standardPalette()
+{
+    QColor backgroundColor(0xd4, 0xd0, 0xc8); // win 2000 grey
+    QColor lightColor(backgroundColor.lighter());
+    QColor darkColor(backgroundColor.darker());
+    const QBrush darkBrush(darkColor);
+    QColor midColor(Qt::gray);
+    QPalette palette(Qt::black, backgroundColor, lightColor, darkColor,
+                     midColor, Qt::black, Qt::white);
+    palette.setBrush(QPalette::Disabled, QPalette::WindowText, darkBrush);
+    palette.setBrush(QPalette::Disabled, QPalette::Text, darkBrush);
+    palette.setBrush(QPalette::Disabled, QPalette::ButtonText, darkBrush);
+    palette.setBrush(QPalette::Disabled, QPalette::Base, QBrush(backgroundColor));
+    return palette;
+}
+
+static QColor placeHolderColor(QColor textColor)
+{
+    textColor.setAlpha(128);
+    return textColor;
+}
+
+/*
+    This is used when the theme is light mode, and when the theme is dark but the
+    application doesn't support dark mode. In the latter case, we need to check.
+*/
+static void populateLightSystemBasePalette(QPalette &result)
+{
+    const QColor background = getSysColor(COLOR_BTNFACE);
+    const QColor textColor = getSysColor(COLOR_WINDOWTEXT);
+
+    const QColor accent = qt_accentColor(AccentColorNormal);
+    const QColor accentDarkest = qt_accentColor(AccentColorDarkest);
+
+    const QColor linkColor = accent;
+    const QColor btnFace = background;
+    const QColor btnHighlight = getSysColor(COLOR_BTNHIGHLIGHT);
+
+    result.setColor(QPalette::Highlight, getSysColor(COLOR_HIGHLIGHT));
+    result.setColor(QPalette::WindowText, getSysColor(COLOR_WINDOWTEXT));
+    result.setColor(QPalette::Button, btnFace);
+    result.setColor(QPalette::Light, btnHighlight);
+    result.setColor(QPalette::Dark, getSysColor(COLOR_BTNSHADOW));
+    result.setColor(QPalette::Mid, result.button().color().darker(150));
+    result.setColor(QPalette::Text, textColor);
+    result.setColor(QPalette::PlaceholderText, placeHolderColor(textColor));
+    result.setColor(QPalette::BrightText, btnHighlight);
+    result.setColor(QPalette::Base, getSysColor(COLOR_WINDOW));
+    result.setColor(QPalette::Window, btnFace);
+    result.setColor(QPalette::ButtonText, getSysColor(COLOR_BTNTEXT));
+    result.setColor(QPalette::Midlight, getSysColor(COLOR_3DLIGHT));
+    result.setColor(QPalette::Shadow, getSysColor(COLOR_3DDKSHADOW));
+    result.setColor(QPalette::HighlightedText, getSysColor(COLOR_HIGHLIGHTTEXT));
+    result.setColor(QPalette::Accent, accent);
+
+    result.setColor(QPalette::Link, linkColor);
+    result.setColor(QPalette::LinkVisited, accentDarkest);
+    result.setColor(QPalette::Inactive, QPalette::Button, result.button().color());
+    result.setColor(QPalette::Inactive, QPalette::Window, result.window().color());
+    result.setColor(QPalette::Inactive, QPalette::Light, result.light().color());
+    result.setColor(QPalette::Inactive, QPalette::Dark, result.dark().color());
+
+    if (result.midlight() == result.button())
+        result.setColor(QPalette::Midlight, result.button().color().lighter(110));
+}
+
+static void populateDarkSystemBasePalette(QPalette &result)
+{
+#if QT_CONFIG(cpp_winrt)
+    using namespace winrt::Windows::UI::ViewManagement;
+    const auto settings = UISettings();
+
+    // We have to craft a palette from these colors. The settings.UIElementColor(UIElementType) API
+    // returns the old system colors, not the dark mode colors. If the background is black (which it
+    // usually), then override it with a dark gray instead so that we can go up and down the lightness.
+    const QColor foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground));
+    const QColor background = [&settings]() -> QColor {
+        auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background));
+        if (systemBackground == Qt::black)
+            systemBackground = QColor(0x1E, 0x1E, 0x1E);
+        return systemBackground;
+    }();
+
+    const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
+    const QColor accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1));
+    const QColor accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2));
+    const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
+    const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
+    const QColor accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2));
+    const QColor accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3));
+#else
+    const QColor foreground = Qt::white;
+    const QColor background = QColor(0x1E, 0x1E, 0x1E);
+    const QColor accent = qt_accentColor(AccentColorNormal);
+    const QColor accentDark = accent.darker(120);
+    const QColor accentDarker = accentDark.darker(120);
+    const QColor accentDarkest = accentDarker.darker(120);
+    const QColor accentLight = accent.lighter(120);
+    const QColor accentLighter = accentLight.lighter(120);
+    const QColor accentLightest = accentLighter.lighter(120);
+#endif
+    const QColor linkColor = accent;
+    const QColor buttonColor = background.lighter(200);
+
+    result.setColor(QPalette::All, QPalette::WindowText, foreground);
+    result.setColor(QPalette::All, QPalette::Text, foreground);
+    result.setColor(QPalette::All, QPalette::BrightText, accentLightest);
+
+    result.setColor(QPalette::All, QPalette::Button, buttonColor);
+    result.setColor(QPalette::All, QPalette::ButtonText, foreground);
+    result.setColor(QPalette::All, QPalette::Light, buttonColor.lighter(200));
+    result.setColor(QPalette::All, QPalette::Midlight, buttonColor.lighter(150));
+    result.setColor(QPalette::All, QPalette::Dark, buttonColor.darker(200));
+    result.setColor(QPalette::All, QPalette::Mid, buttonColor.darker(150));
+    result.setColor(QPalette::All, QPalette::Shadow, Qt::black);
+
+    result.setColor(QPalette::All, QPalette::Base, background.lighter(150));
+    result.setColor(QPalette::All, QPalette::Window, background);
+
+    result.setColor(QPalette::All, QPalette::Highlight, accent);
+    result.setColor(QPalette::All, QPalette::HighlightedText, accent.lightness() > 128 ? Qt::black : Qt::white);
+    result.setColor(QPalette::All, QPalette::Link, linkColor);
+    result.setColor(QPalette::All, QPalette::LinkVisited, accentDarkest);
+    result.setColor(QPalette::All, QPalette::AlternateBase, accentDarkest);
+    result.setColor(QPalette::All, QPalette::ToolTipBase, buttonColor);
+    result.setColor(QPalette::All, QPalette::ToolTipText, foreground.darker(120));
+    result.setColor(QPalette::All, QPalette::PlaceholderText, placeHolderColor(foreground));
+    result.setColor(QPalette::All, QPalette::Accent, accent);
+}
+
+static inline QPalette toolTipPalette(const QPalette &systemPalette, bool light)
+{
+    QPalette result(systemPalette);
+    const QColor tipBgColor = light ? getSysColor(COLOR_INFOBK)
+                                    : systemPalette.button().color();
+    const QColor tipTextColor = light ? getSysColor(COLOR_INFOTEXT)
+                                      : systemPalette.buttonText().color().darker(120);
+
+    result.setColor(QPalette::All, QPalette::Button, tipBgColor);
+    result.setColor(QPalette::All, QPalette::Window, tipBgColor);
+    result.setColor(QPalette::All, QPalette::Text, tipTextColor);
+    result.setColor(QPalette::All, QPalette::WindowText, tipTextColor);
+    result.setColor(QPalette::All, QPalette::ButtonText, tipTextColor);
+    result.setColor(QPalette::All, QPalette::Button, tipBgColor);
+    result.setColor(QPalette::All, QPalette::Window, tipBgColor);
+    result.setColor(QPalette::All, QPalette::Text, tipTextColor);
+    result.setColor(QPalette::All, QPalette::WindowText, tipTextColor);
+    result.setColor(QPalette::All, QPalette::ButtonText, tipTextColor);
+    result.setColor(QPalette::All, QPalette::ToolTipBase, tipBgColor);
+    result.setColor(QPalette::All, QPalette::ToolTipText, tipTextColor);
+    const QColor disabled = mixColors(result.windowText().color(), result.button().color());
+    result.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
+    result.setColor(QPalette::Disabled, QPalette::Text, disabled);
+    result.setColor(QPalette::Disabled, QPalette::ToolTipText, disabled);
+    result.setColor(QPalette::Disabled, QPalette::Base, Qt::white);
+    result.setColor(QPalette::Disabled, QPalette::BrightText, Qt::white);
+    result.setColor(QPalette::Disabled, QPalette::ToolTipBase, Qt::white);
+    return result;
+}
+
+static inline QPalette menuPalette(const QPalette &systemPalette, bool light)
+{
+    if (!light)
+        return systemPalette;
+
+    QPalette result(systemPalette);
+    const QColor menuColor = getSysColor(COLOR_MENU);
+    const QColor menuTextColor = getSysColor(COLOR_MENUTEXT);
+    const QColor disabled = getSysColor(COLOR_GRAYTEXT);
+    // we might need a special color group for the result.
+    result.setColor(QPalette::Active, QPalette::Button, menuColor);
+    result.setColor(QPalette::Active, QPalette::Text, menuTextColor);
+    result.setColor(QPalette::Active, QPalette::WindowText, menuTextColor);
+    result.setColor(QPalette::Active, QPalette::ButtonText, menuTextColor);
+    result.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
+    result.setColor(QPalette::Disabled, QPalette::Text, disabled);
+    const bool isFlat = booleanSystemParametersInfo(SPI_GETFLATMENU, false);
+    const QColor highlightColor = getSysColor(isFlat ? COLOR_MENUHILIGHT : COLOR_HIGHLIGHT);
+    result.setColor(QPalette::Disabled, QPalette::Highlight, highlightColor);
+    result.setColor(QPalette::Disabled, QPalette::HighlightedText, disabled);
+    result.setColor(QPalette::Disabled, QPalette::Button,
+                    result.color(QPalette::Active, QPalette::Button));
+    result.setColor(QPalette::Inactive, QPalette::Button,
+                    result.color(QPalette::Active, QPalette::Button));
+    result.setColor(QPalette::Inactive, QPalette::Text,
+                    result.color(QPalette::Active, QPalette::Text));
+    result.setColor(QPalette::Inactive, QPalette::WindowText,
+                    result.color(QPalette::Active, QPalette::WindowText));
+    result.setColor(QPalette::Inactive, QPalette::ButtonText,
+                    result.color(QPalette::Active, QPalette::ButtonText));
+    result.setColor(QPalette::Inactive, QPalette::Highlight,
+                    result.color(QPalette::Active, QPalette::Highlight));
+    result.setColor(QPalette::Inactive, QPalette::HighlightedText,
+                    result.color(QPalette::Active, QPalette::HighlightedText));
+    result.setColor(QPalette::Inactive, QPalette::ButtonText,
+                    systemPalette.color(QPalette::Inactive, QPalette::Dark));
+    return result;
+}
+
+static inline QPalette *menuBarPalette(const QPalette &menuPalette, bool light)
+{
+    QPalette *result = nullptr;
+    if (!light || !booleanSystemParametersInfo(SPI_GETFLATMENU, false))
+        return result;
+
+    result = new QPalette(menuPalette);
+    const QColor menubar(getSysColor(COLOR_MENUBAR));
+    result->setColor(QPalette::Active, QPalette::Button, menubar);
+    result->setColor(QPalette::Disabled, QPalette::Button, menubar);
+    result->setColor(QPalette::Inactive, QPalette::Button, menubar);
+    return result;
+}
+
+const char *QWindowsTheme::name = "windows";
+QWindowsTheme *QWindowsTheme::m_instance = nullptr;
+
+QWindowsTheme::QWindowsTheme()
+{
+    m_instance = this;
+    std::fill(m_fonts, m_fonts + NFonts, nullptr);
+    std::fill(m_palettes, m_palettes + NPalettes, nullptr);
+    refresh();
+    refreshIconPixmapSizes();
+}
+
+QWindowsTheme::~QWindowsTheme()
+{
+    clearPalettes();
+    clearFonts();
+    m_instance = nullptr;
+}
+
+static inline QStringList iconThemeSearchPaths()
+{
+    const QFileInfo appDir(QCoreApplication::applicationDirPath() + "/icons"_L1);
+    return appDir.isDir() ? QStringList(appDir.absoluteFilePath()) : QStringList();
+}
+
+static inline QStringList styleNames()
+{
+    QStringList styles = { QStringLiteral("WindowsVista"), QStringLiteral("Windows") };
+    if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11)
+        styles.prepend(QStringLiteral("Windows11"));
+    return styles;
+}
+
+static inline int uiEffects()
+{
+    int result = QPlatformTheme::HoverEffect;
+    if (booleanSystemParametersInfo(SPI_GETUIEFFECTS, false))
+        result |= QPlatformTheme::GeneralUiEffect;
+    if (booleanSystemParametersInfo(SPI_GETMENUANIMATION, false))
+        result |= QPlatformTheme::AnimateMenuUiEffect;
+    if (booleanSystemParametersInfo(SPI_GETMENUFADE, false))
+        result |= QPlatformTheme::FadeMenuUiEffect;
+    if (booleanSystemParametersInfo(SPI_GETCOMBOBOXANIMATION, false))
+        result |= QPlatformTheme::AnimateComboUiEffect;
+    if (booleanSystemParametersInfo(SPI_GETTOOLTIPANIMATION, false))
+        result |= QPlatformTheme::AnimateTooltipUiEffect;
+    return result;
+}
+
+QVariant QWindowsTheme::themeHint(ThemeHint hint) const
+{
+    switch (hint) {
+    case UseFullScreenForPopupMenu:
+        return QVariant(true);
+    case DialogButtonBoxLayout:
+        return QVariant(QPlatformDialogHelper::WinLayout);
+    case IconThemeSearchPaths:
+        return QVariant(iconThemeSearchPaths());
+    case StyleNames:
+        return QVariant(styleNames());
+    case TextCursorWidth:
+        return QVariant(int(dWordSystemParametersInfo(SPI_GETCARETWIDTH, 1u)));
+    case DropShadow:
+        return QVariant(booleanSystemParametersInfo(SPI_GETDROPSHADOW, false));
+    case MaximumScrollBarDragDistance:
+        return QVariant(qRound(qreal(QWindowsContext::instance()->defaultDPI()) * 1.375));
+    case KeyboardScheme:
+        return QVariant(int(WindowsKeyboardScheme));
+    case UiEffects:
+        return QVariant(uiEffects());
+    case IconPixmapSizes:
+        return QVariant::fromValue(m_fileIconSizes);
+    case DialogSnapToDefaultButton:
+        return QVariant(booleanSystemParametersInfo(SPI_GETSNAPTODEFBUTTON, false));
+    case ContextMenuOnMouseRelease:
+        return QVariant(true);
+    case WheelScrollLines: {
+        int result = 3;
+        const DWORD scrollLines = dWordSystemParametersInfo(SPI_GETWHEELSCROLLLINES, DWORD(result));
+        if (scrollLines != DWORD(-1)) // Special value meaning "scroll one screen", unimplemented in Qt.
+            result = int(scrollLines);
+        return QVariant(result);
+    }
+    case MouseDoubleClickDistance:
+        return GetSystemMetrics(SM_CXDOUBLECLK);
+    case MenuBarFocusOnAltPressRelease:
+        return true;
+    default:
+        break;
+    }
+    return QPlatformTheme::themeHint(hint);
+}
+
+Qt::ColorScheme QWindowsTheme::colorScheme() const
+{
+    if (queryHighContrast())
+        return Qt::ColorScheme::Unknown;
+    return QWindowsContext::isDarkMode() ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
+}
+
+void QWindowsTheme::clearPalettes()
+{
+    qDeleteAll(m_palettes, m_palettes + NPalettes);
+    std::fill(m_palettes, m_palettes + NPalettes, nullptr);
+}
+
+void QWindowsTheme::refreshPalettes()
+{
+    if (!QGuiApplication::desktopSettingsAware())
+        return;
+    const bool light =
+        !QWindowsContext::isDarkMode()
+        || !QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle);
+    clearPalettes();
+    m_palettes[SystemPalette] = new QPalette(QWindowsTheme::systemPalette(light ? Qt::ColorScheme::Light : Qt::ColorScheme::Dark));
+    m_palettes[ToolTipPalette] = new QPalette(toolTipPalette(*m_palettes[SystemPalette], light));
+    m_palettes[MenuPalette] = new QPalette(menuPalette(*m_palettes[SystemPalette], light));
+    m_palettes[MenuBarPalette] = menuBarPalette(*m_palettes[MenuPalette], light);
+    if (!light) {
+        m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]);
+        m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, qt_accentColor(AccentColorNormal));
+        m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, qt_accentColor(AccentColorLightest));
+        m_palettes[CheckBoxPalette]->setColor(QPalette::Inactive, QPalette::Base, qt_accentColor(AccentColorDarkest));
+        m_palettes[RadioButtonPalette] = new QPalette(*m_palettes[CheckBoxPalette]);
+    }
+}
+
+QPalette QWindowsTheme::systemPalette(Qt::ColorScheme colorScheme)
+{
+    QPalette result = standardPalette();
+
+    switch (colorScheme) {
+    case Qt::ColorScheme::Light:
+        populateLightSystemBasePalette(result);
+        break;
+    case Qt::ColorScheme::Dark:
+        populateDarkSystemBasePalette(result);
+        break;
+    default:
+        qFatal("Unknown color scheme");
+        break;
+    }
+
+    if (result.window() != result.base()) {
+        result.setColor(QPalette::Inactive, QPalette::Highlight,
+                        result.color(QPalette::Inactive, QPalette::Window));
+        result.setColor(QPalette::Inactive, QPalette::HighlightedText,
+                        result.color(QPalette::Inactive, QPalette::Text));
+        result.setColor(QPalette::Inactive, QPalette::Accent,
+                        result.color(QPalette::Inactive, QPalette::Window));
+    }
+
+    const QColor disabled = mixColors(result.windowText().color(), result.button().color());
+
+    result.setColorGroup(QPalette::Disabled, result.windowText(), result.button(),
+                         result.light(), result.dark(), result.mid(),
+                         result.text(), result.brightText(), result.base(),
+                         result.window());
+    result.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
+    result.setColor(QPalette::Disabled, QPalette::Text, disabled);
+    result.setColor(QPalette::Disabled, QPalette::ButtonText, disabled);
+    result.setColor(QPalette::Disabled, QPalette::Highlight, result.color(QPalette::Highlight));
+    result.setColor(QPalette::Disabled, QPalette::HighlightedText, result.color(QPalette::HighlightedText));
+    result.setColor(QPalette::Disabled, QPalette::Accent, disabled);
+    result.setColor(QPalette::Disabled, QPalette::Base, result.window().color());
+    return result;
+}
+
+void QWindowsTheme::clearFonts()
+{
+    qDeleteAll(m_fonts, m_fonts + NFonts);
+    std::fill(m_fonts, m_fonts + NFonts, nullptr);
+}
+
+void QWindowsTheme::refresh()
+{
+    refreshPalettes();
+    refreshFonts();
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug d, const NONCLIENTMETRICS &m)
+{
+    QDebugStateSaver saver(d);
+    d.nospace();
+    d.noquote();
+    d << "NONCLIENTMETRICS(iMenu=" << m.iMenuWidth  << 'x' << m.iMenuHeight
+      << ", lfCaptionFont=";
+    QWindowsFontDatabase::debugFormat(d, m.lfCaptionFont);
+    d << ", lfSmCaptionFont=";
+    QWindowsFontDatabase::debugFormat(d, m.lfSmCaptionFont);
+    d << ", lfMenuFont=";
+    QWindowsFontDatabase::debugFormat(d, m.lfMenuFont);
+    d <<  ", lfMessageFont=";
+    QWindowsFontDatabase::debugFormat(d, m.lfMessageFont);
+    d <<", lfStatusFont=";
+    QWindowsFontDatabase::debugFormat(d, m.lfStatusFont);
+    d << ')';
+    return d;
+}
+#endif // QT_NO_DEBUG_STREAM
+
+void QWindowsTheme::refreshFonts()
+{
+    clearFonts();
+    if (!QGuiApplication::desktopSettingsAware())
+        return;
+
+    const int dpi = 96;
+    NONCLIENTMETRICS ncm;
+    QWindowsContext::nonClientMetrics(&ncm, dpi);
+    qCDebug(lcQpaWindow) << __FUNCTION__ << ncm;
+
+    const QFont menuFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMenuFont, dpi);
+    const QFont messageBoxFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont, dpi);
+    const QFont statusFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfStatusFont, dpi);
+    const QFont titleFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfCaptionFont, dpi);
+    QFont fixedFont(QStringLiteral("Courier New"), messageBoxFont.pointSize());
+    fixedFont.setStyleHint(QFont::TypeWriter);
+
+    LOGFONT lfIconTitleFont;
+    SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi);
+    const QFont iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont, dpi);
+
+    m_fonts[SystemFont] = new QFont(QWindowsFontDatabase::systemDefaultFont());
+    m_fonts[MenuFont] = new QFont(menuFont);
+    m_fonts[MenuBarFont] = new QFont(menuFont);
+    m_fonts[MessageBoxFont] = new QFont(messageBoxFont);
+    m_fonts[TipLabelFont] = new QFont(statusFont);
+    m_fonts[StatusBarFont] = new QFont(statusFont);
+    m_fonts[MdiSubWindowTitleFont] = new QFont(titleFont);
+    m_fonts[DockWidgetTitleFont] = new QFont(titleFont);
+    m_fonts[ItemViewFont] = new QFont(iconTitleFont);
+    m_fonts[FixedFont] = new QFont(fixedFont);
+}
+
+enum FileIconSize {
+    // Standard icons obtainable via shGetFileInfo(), SHGFI_SMALLICON, SHGFI_LARGEICON
+    SmallFileIcon, LargeFileIcon,
+    // Larger icons obtainable via SHGetImageList()
+    ExtraLargeFileIcon,
+    JumboFileIcon, // Vista onwards
+    FileIconSizeCount
+};
+
+bool QWindowsTheme::usePlatformNativeDialog(DialogType type) const
+{
+    return QWindowsDialogs::useHelper(type);
+}
+
+QPlatformDialogHelper *QWindowsTheme::createPlatformDialogHelper(DialogType type) const
+{
+    return QWindowsDialogs::createHelper(type);
+}
+
+#if QT_CONFIG(systemtrayicon)
+QPlatformSystemTrayIcon *QWindowsTheme::createPlatformSystemTrayIcon() const
+{
+    return new QWindowsSystemTrayIcon;
+}
+#endif
+
+void QWindowsTheme::windowsThemeChanged(QWindow * window)
+{
+    refresh();
+    QWindowSystemInterface::handleThemeChange(window);
+}
+
+static int fileIconSizes[FileIconSizeCount];
+
+void QWindowsTheme::refreshIconPixmapSizes()
+{
+    // Standard sizes: 16, 32, 48, 256
+    fileIconSizes[SmallFileIcon] = GetSystemMetrics(SM_CXSMICON); // corresponds to SHGFI_SMALLICON);
+    fileIconSizes[LargeFileIcon] = GetSystemMetrics(SM_CXICON); // corresponds to SHGFI_LARGEICON
+    fileIconSizes[ExtraLargeFileIcon] =
+        fileIconSizes[LargeFileIcon] + fileIconSizes[LargeFileIcon] / 2;
+    fileIconSizes[JumboFileIcon] = 8 * fileIconSizes[LargeFileIcon]; // empirical, has not been observed to work
+
+#ifdef USE_IIMAGELIST
+    int *availEnd = fileIconSizes + JumboFileIcon + 1;
+#else
+    int *availEnd = fileIconSizes + LargeFileIcon + 1;
+#endif // USE_IIMAGELIST
+    m_fileIconSizes = QAbstractFileIconEngine::toSizeList(fileIconSizes, availEnd);
+    qCDebug(lcQpaWindow) << __FUNCTION__ << m_fileIconSizes;
+}
+
+// Defined in qpixmap_win.cpp
+Q_GUI_EXPORT QPixmap qt_pixmapFromWinHICON(HICON icon);
+
+static QPixmap loadIconFromShell32(int resourceId, QSizeF size)
+{
+    if (const HMODULE hmod = QSystemLibrary::load(L"shell32")) {
+        auto iconHandle =
+            static_cast<HICON>(LoadImage(hmod, MAKEINTRESOURCE(resourceId),
+                                         IMAGE_ICON, int(size.width()), int(size.height()), 0));
+        if (iconHandle) {
+            QPixmap iconpixmap = qt_pixmapFromWinHICON(iconHandle);
+            DestroyIcon(iconHandle);
+            return iconpixmap;
+        }
+    }
+    return QPixmap();
+}
+
+QPixmap QWindowsTheme::standardPixmap(StandardPixmap sp, const QSizeF &pixmapSize) const
+{
+    int resourceId = -1;
+    SHSTOCKICONID stockId = SIID_INVALID;
+    UINT stockFlags = 0;
+    LPCTSTR iconName = nullptr;
+    switch (sp) {
+    case DriveCDIcon:
+        stockId = SIID_DRIVECD;
+        resourceId = 12;
+        break;
+    case DriveDVDIcon:
+        stockId = SIID_DRIVEDVD;
+        resourceId = 12;
+        break;
+    case DriveNetIcon:
+        stockId = SIID_DRIVENET;
+        resourceId = 10;
+        break;
+    case DriveHDIcon:
+        stockId = SIID_DRIVEFIXED;
+        resourceId = 9;
+        break;
+    case DriveFDIcon:
+        stockId = SIID_DRIVE35;
+        resourceId = 7;
+        break;
+    case FileLinkIcon:
+        stockFlags = SHGSI_LINKOVERLAY;
+        Q_FALLTHROUGH();
+    case FileIcon:
+        stockId = SIID_DOCNOASSOC;
+        resourceId = 1;
+        break;
+    case DirLinkIcon:
+        stockFlags = SHGSI_LINKOVERLAY;
+        Q_FALLTHROUGH();
+    case DirClosedIcon:
+    case DirIcon:
+        stockId = SIID_FOLDER;
+        resourceId = 4;
+        break;
+    case DesktopIcon:
+        resourceId = 35;
+        break;
+    case ComputerIcon:
+        resourceId = 16;
+        break;
+    case DirLinkOpenIcon:
+        stockFlags = SHGSI_LINKOVERLAY;
+        Q_FALLTHROUGH();
+    case DirOpenIcon:
+        stockId = SIID_FOLDEROPEN;
+        resourceId = 5;
+        break;
+    case FileDialogNewFolder:
+        stockId = SIID_FOLDER;
+        resourceId = 319;
+        break;
+    case DirHomeIcon:
+        resourceId = 235;
+        break;
+    case TrashIcon:
+        stockId = SIID_RECYCLER;
+        resourceId = 191;
+        break;
+    case MessageBoxInformation:
+        stockId = SIID_INFO;
+        iconName = IDI_INFORMATION;
+        break;
+    case MessageBoxWarning:
+        stockId = SIID_WARNING;
+        iconName = IDI_WARNING;
+        break;
+    case MessageBoxCritical:
+        stockId = SIID_ERROR;
+        iconName = IDI_ERROR;
+        break;
+    case MessageBoxQuestion:
+        stockId = SIID_HELP;
+        iconName = IDI_QUESTION;
+        break;
+    case VistaShield:
+        stockId = SIID_SHIELD;
+        break;
+    default:
+        break;
+    }
+
+    if (stockId != SIID_INVALID) {
+        SHSTOCKICONINFO iconInfo;
+        memset(&iconInfo, 0, sizeof(iconInfo));
+        iconInfo.cbSize = sizeof(iconInfo);
+        stockFlags |= SHGSI_ICONLOCATION;
+        if (SHGetStockIconInfo(stockId, stockFlags, &iconInfo) == S_OK) {
+            const auto iconSize = pixmapSize.width();
+            HICON icon;
+            if (SHDefExtractIcon(iconInfo.szPath, iconInfo.iIcon, 0, &icon, nullptr, iconSize) == S_OK) {
+                QPixmap pixmap = qt_pixmapFromWinHICON(icon);
+                DestroyIcon(icon);
+                return pixmap;
+            }
+        }
+    }
+
+    if (resourceId != -1) {
+        QPixmap pixmap = loadIconFromShell32(resourceId, pixmapSize);
+        if (!pixmap.isNull()) {
+            if (sp == FileLinkIcon || sp == DirLinkIcon || sp == DirLinkOpenIcon) {
+                QPainter painter(&pixmap);
+                QPixmap link = loadIconFromShell32(30, pixmapSize);
+                painter.drawPixmap(0, 0, int(pixmapSize.width()), int(pixmapSize.height()), link);
+            }
+            return pixmap;
+        }
+    }
+
+    if (iconName) {
+        HICON iconHandle = LoadIcon(nullptr, iconName);
+        QPixmap pixmap = qt_pixmapFromWinHICON(iconHandle);
+        DestroyIcon(iconHandle);
+        if (!pixmap.isNull())
+            return pixmap;
+    }
+
+    return QPlatformTheme::standardPixmap(sp, pixmapSize);
+}
+
+enum { // Shell image list ids
+    sHIL_EXTRALARGE = 0x2, // 48x48 or user-defined
+    sHIL_JUMBO = 0x4 // 256x256 (Vista or later)
+};
+
+static QString dirIconPixmapCacheKey(int iIcon, int iconSize, int imageListSize)
+{
+    QString key = "qt_dir_"_L1 + QString::number(iIcon);
+    if (iconSize == SHGFI_LARGEICON)
+        key += u'l';
+    switch (imageListSize) {
+    case sHIL_EXTRALARGE:
+        key += u'e';
+        break;
+    case sHIL_JUMBO:
+        key += u'j';
+        break;
+    }
+    return key;
+}
+
+template <typename T>
+class FakePointer
+{
+public:
+
+    static_assert(sizeof(T) <= sizeof(void *), "FakePointers can only go that far.");
+
+    static FakePointer *create(T thing)
+    {
+        return reinterpret_cast<FakePointer *>(qintptr(thing));
+    }
+
+    T operator * () const
+    {
+        return T(qintptr(this));
+    }
+
+    void operator delete (void *) {}
+};
+
+// Shell image list helper functions.
+
+static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info)
+{
+    QPixmap result;
+#ifdef USE_IIMAGELIST
+    // For MinGW:
+    static const IID iID_IImageList = {0x46eb5926, 0x582e, 0x4017, {0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x9, 0x50}};
+
+    IImageList *imageList = nullptr;
+    HRESULT hr = SHGetImageList(iImageList, iID_IImageList, reinterpret_cast<void **>(&imageList));
+    if (hr != S_OK)
+        return result;
+    HICON hIcon;
+    hr = imageList->GetIcon(info.iIcon, ILD_TRANSPARENT, &hIcon);
+    if (hr == S_OK) {
+        result = qt_pixmapFromWinHICON(hIcon);
+        DestroyIcon(hIcon);
+    }
+    imageList->Release();
+#else
+    Q_UNUSED(iImageList);
+    Q_UNUSED(info);
+#endif // USE_IIMAGELIST
+    return result;
+}
+
+class QWindowsFileIconEngine : public QAbstractFileIconEngine
+{
+public:
+    explicit QWindowsFileIconEngine(const QFileInfo &info, QPlatformTheme::IconOptions opts) :
+        QAbstractFileIconEngine(info, opts) {}
+
+    QList<QSize> availableSizes(QIcon::Mode = QIcon::Normal, QIcon::State = QIcon::Off) override
+    { return QWindowsTheme::instance()->availableFileIconSizes(); }
+
+protected:
+    QString cacheKey() const override;
+    QPixmap filePixmap(const QSize &size, QIcon::Mode mode, QIcon::State) override;
+};
+
+QString QWindowsFileIconEngine::cacheKey() const
+{
+    // Cache directories unless custom or drives, which have custom icons depending on type
+    if ((options() & QPlatformTheme::DontUseCustomDirectoryIcons) && fileInfo().isDir() && !fileInfo().isRoot())
+        return QStringLiteral("qt_/directory/");
+    if (!fileInfo().isFile())
+        return QString();
+    // Return "" for .exe, .lnk and .ico extensions.
+    // It is faster to just look at the file extensions;
+    // avoiding slow QFileInfo::isExecutable() (QTBUG-13182)
+    QString suffix = fileInfo().suffix();
+    if (!suffix.compare(u"exe", Qt::CaseInsensitive)
+        || !suffix.compare(u"lnk", Qt::CaseInsensitive)
+        || !suffix.compare(u"ico", Qt::CaseInsensitive)) {
+        return QString();
+    }
+    return "qt_."_L1
+        + (suffix.isEmpty() ? fileInfo().fileName() : std::move(suffix).toUpper()); // handle "Makefile"                                    ;)
+}
+
+QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon::State)
+{
+    QComHelper comHelper;
+
+    static QCache<QString, FakePointer<int> > dirIconEntryCache(1000);
+    Q_CONSTINIT static QMutex mx;
+    static int defaultFolderIIcon = -1;
+    const bool useDefaultFolderIcon = options() & QPlatformTheme::DontUseCustomDirectoryIcons;
+
+    QPixmap pixmap;
+    const QString filePath = QDir::toNativeSeparators(fileInfo().filePath());
+    const int width = int(size.width());
+    const int iconSize = width > fileIconSizes[SmallFileIcon] ? SHGFI_LARGEICON : SHGFI_SMALLICON;
+    const int requestedImageListSize =
+#ifdef USE_IIMAGELIST
+        width > fileIconSizes[ExtraLargeFileIcon]
+            ? sHIL_JUMBO
+            : (width > fileIconSizes[LargeFileIcon] ? sHIL_EXTRALARGE : 0);
+#else
+        0;
+#endif // !USE_IIMAGELIST
+    bool cacheableDirIcon = fileInfo().isDir() && !fileInfo().isRoot();
+    if (cacheableDirIcon) {
+        QMutexLocker locker(&mx);
+        int iIcon = (useDefaultFolderIcon && defaultFolderIIcon >= 0) ? defaultFolderIIcon
+                                                                      : **dirIconEntryCache.object(filePath);
+        if (iIcon) {
+            QPixmapCache::find(dirIconPixmapCacheKey(iIcon, iconSize, requestedImageListSize),
+                               &pixmap);
+            if (pixmap.isNull()) // Let's keep both caches in sync
+                dirIconEntryCache.remove(filePath);
+            else
+                return pixmap;
+        }
+    }
+
+    SHFILEINFO info;
+    unsigned int flags = SHGFI_ICON | iconSize | SHGFI_SYSICONINDEX | SHGFI_ADDOVERLAYS | SHGFI_OVERLAYINDEX;
+    DWORD attributes = 0;
+    QString path = filePath;
+    if (cacheableDirIcon && useDefaultFolderIcon) {
+        flags |= SHGFI_USEFILEATTRIBUTES;
+        attributes |= FILE_ATTRIBUTE_DIRECTORY;
+        path = QStringLiteral("dummy");
+    } else if (!fileInfo().exists()) {
+        flags |= SHGFI_USEFILEATTRIBUTES;
+        attributes |= FILE_ATTRIBUTE_NORMAL;
+    }
+    const bool val = shGetFileInfoBackground(path, attributes, &info, flags);
+
+    // Even if GetFileInfo returns a valid result, hIcon can be empty in some cases
+    if (val && info.hIcon) {
+        QString key;
+        if (cacheableDirIcon) {
+            if (useDefaultFolderIcon && defaultFolderIIcon < 0)
+                defaultFolderIIcon = info.iIcon;
+
+            //using the unique icon index provided by windows save us from duplicate keys
+            key = dirIconPixmapCacheKey(info.iIcon, iconSize, requestedImageListSize);
+            QPixmapCache::find(key, &pixmap);
+            if (!pixmap.isNull()) {
+                QMutexLocker locker(&mx);
+                dirIconEntryCache.insert(filePath, FakePointer<int>::create(info.iIcon));
+            }
+        }
+
+        if (pixmap.isNull()) {
+            if (requestedImageListSize) {
+                pixmap = pixmapFromShellImageList(requestedImageListSize, info);
+                if (pixmap.isNull() && requestedImageListSize == sHIL_JUMBO)
+                    pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, info);
+            }
+            if (pixmap.isNull())
+                pixmap = qt_pixmapFromWinHICON(info.hIcon);
+            if (!pixmap.isNull()) {
+                if (cacheableDirIcon) {
+                    QMutexLocker locker(&mx);
+                    QPixmapCache::insert(key, pixmap);
+                    dirIconEntryCache.insert(filePath, FakePointer<int>::create(info.iIcon));
+                }
+            } else {
+                qWarning("QWindowsTheme::fileIconPixmap() no icon found");
+            }
+        }
+        DestroyIcon(info.hIcon);
+    }
+
+    return pixmap;
+}
+
+QIcon QWindowsTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions) const
+{
+    return QIcon(new QWindowsFileIconEngine(fileInfo, iconOptions));
+}
+
+QIconEngine *QWindowsTheme::createIconEngine(const QString &iconName) const
+{
+    return new QWindowsIconEngine(iconName);
+}
+
+static inline bool doUseNativeMenus()
+{
+    const unsigned options = QWindowsIntegration::instance()->options();
+    if ((options & QWindowsIntegration::NoNativeMenus) != 0)
+        return false;
+    if ((options & QWindowsIntegration::AlwaysUseNativeMenus) != 0)
+        return true;
+    // "Auto" mode: For non-widget or Quick Controls 2 applications
+    if (!QCoreApplication::instance()->inherits("QApplication"))
+        return true;
+    const QWindowList &topLevels = QGuiApplication::topLevelWindows();
+    for (const QWindow *t : topLevels) {
+        if (t->inherits("QQuickApplicationWindow"))
+            return true;
+    }
+    return false;
+}
+
+bool QWindowsTheme::useNativeMenus()
+{
+    static const bool result = doUseNativeMenus();
+    return result;
+}
+
+bool QWindowsTheme::queryDarkMode()
+{
+    if (queryHighContrast()) {
+        return false;
+    }
+    const auto setting = QWinRegistryKey(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)")
+                         .dwordValue(L"AppsUseLightTheme");
+    return setting.second && setting.first == 0;
+}
+
+bool QWindowsTheme::queryHighContrast()
+{
+    HIGHCONTRAST hcf = {};
+    hcf.cbSize = static_cast<UINT>(sizeof(HIGHCONTRAST));
+    if (SystemParametersInfo(SPI_GETHIGHCONTRAST, hcf.cbSize, &hcf, FALSE))
+        return hcf.dwFlags & HCF_HIGHCONTRASTON;
+    return false;
+}
+
+QPlatformMenuItem *QWindowsTheme::createPlatformMenuItem() const
+{
+    qCDebug(lcQpaMenus) << __FUNCTION__;
+    return QWindowsTheme::useNativeMenus() ? new QWindowsMenuItem : nullptr;
+}
+
+QPlatformMenu *QWindowsTheme::createPlatformMenu() const
+{
+    qCDebug(lcQpaMenus) << __FUNCTION__;
+    // We create a popup menu here, since it will likely be used as context
+    // menu. Submenus should be created the factory functions of
+    // QPlatformMenu/Bar. Note though that Quick Controls 1 will use this
+    // function for submenus as well, but this has been found to work.
+    return QWindowsTheme::useNativeMenus() ? new QWindowsPopupMenu : nullptr;
+}
+
+QPlatformMenuBar *QWindowsTheme::createPlatformMenuBar() const
+{
+    qCDebug(lcQpaMenus) << __FUNCTION__;
+    return QWindowsTheme::useNativeMenus() ? new QWindowsMenuBar : nullptr;
+}
+
+void QWindowsTheme::showPlatformMenuBar()
+{
+    qCDebug(lcQpaMenus) << __FUNCTION__;
+}
+
+QT_END_NAMESPACE
diff --git a/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp.rej b/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp.rej
new file mode 100644
index 0000000000..e6de77b1fe
--- /dev/null
+++ b/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp.rej
@@ -0,0 +1,138 @@
+--- qtbase/src/plugins/platforms/windows/qwindowstheme.cpp
++++ qtbase/src/plugins/platforms/windows/qwindowstheme.cpp
+@@ -245,19 +247,21 @@ static QColor placeHolderColor(QColor textColor)
+ */
+ static void populateLightSystemBasePalette(QPalette &result)
+ {
+-    const QColor background = getSysColor(COLOR_BTNFACE);
+-    const QColor textColor = getSysColor(COLOR_WINDOWTEXT);
++    QColor background = getSysColor(COLOR_BTNFACE);
++    QColor textColor = getSysColor(COLOR_WINDOWTEXT);
++    QColor accent = qt_accentColor();
++    QColor accentDarkest = accent.darker(120 * 120 * 120);
+ 
+ #if QT_CONFIG(cpp_winrt)
+-    // respect the Windows 11 accent color
+-    using namespace winrt::Windows::UI::ViewManagement;
+-    const auto settings = UISettings();
++    if (IsWindows10OrGreater())
++    {
++        // respect the Windows 11 accent color
++        using namespace winrt::Windows::UI::ViewManagement;
++        const auto settings = UISettings();
+ 
+-    const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
+-    const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
+-#else
+-    const QColor accent = qt_accentColor();
+-    const QColor accentDarkest = accent.darker(120 * 120 * 120);
++        accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
++        accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));    
++    }
+ #endif
+ 
+     const QColor linkColor = accent;
+@@ -294,39 +298,43 @@ static void populateLightSystemBasePalette(QPalette &result)
+ 
+ static void populateDarkSystemBasePalette(QPalette &result)
+ {
++    QColor foreground = Qt::white;
++    QColor background = QColor(0x1E, 0x1E, 0x1E);
++    QColor accent = qt_accentColor();
++    QColor accentDark = accent.darker(120);
++    QColor accentDarker = accentDark.darker(120);
++    QColor accentDarkest = accentDarker.darker(120);
++    QColor accentLight = accent.lighter(120);
++    QColor accentLighter = accentLight.lighter(120);
++    QColor accentLightest = accentLighter.lighter(120);
++
+ #if QT_CONFIG(cpp_winrt)
+-    using namespace winrt::Windows::UI::ViewManagement;
+-    const auto settings = UISettings();
+-
+-    // We have to craft a palette from these colors. The settings.UIElementColor(UIElementType) API
+-    // returns the old system colors, not the dark mode colors. If the background is black (which it
+-    // usually), then override it with a dark gray instead so that we can go up and down the lightness.
+-    const QColor foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground));
+-    const QColor background = [&settings]() -> QColor {
+-        auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background));
+-        if (systemBackground == Qt::black)
+-            systemBackground = QColor(0x1E, 0x1E, 0x1E);
+-        return systemBackground;
+-    }();
+-
+-    const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
+-    const QColor accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1));
+-    const QColor accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2));
+-    const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
+-    const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
+-    const QColor accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2));
+-    const QColor accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3));
+-#else
+-    const QColor foreground = Qt::white;
+-    const QColor background = QColor(0x1E, 0x1E, 0x1E);
+-    const QColor accent = qt_accentColor();
+-    const QColor accentDark = accent.darker(120);
+-    const QColor accentDarker = accentDark.darker(120);
+-    const QColor accentDarkest = accentDarker.darker(120);
+-    const QColor accentLight = accent.lighter(120);
+-    const QColor accentLighter = accentLight.lighter(120);
+-    const QColor accentLightest = accentLighter.lighter(120);
++    if (IsWindows10OrGreater())
++    {
++        using namespace winrt::Windows::UI::ViewManagement;
++        const auto settings = UISettings();
++
++        // We have to craft a palette from these colors. The settings.UIElementColor(UIElementType) API
++        // returns the old system colors, not the dark mode colors. If the background is black (which it
++        // usually), then override it with a dark gray instead so that we can go up and down the lightness.
++        foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground));
++        background = [&settings]() -> QColor {
++            auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background));
++            if (systemBackground == Qt::black)
++                systemBackground = QColor(0x1E, 0x1E, 0x1E);
++            return systemBackground;
++        }();
++
++        accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
++        accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1));
++        accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2));
++        accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
++        accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
++        accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2));
++        accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3));
++    }
+ #endif
++
+     const QColor linkColor = accent;
+     const QColor buttonColor = background.lighter(200);
+ 
+@@ -553,16 +561,19 @@ void QWindowsTheme::refreshPalettes()
+     m_palettes[MenuPalette] = new QPalette(menuPalette(*m_palettes[SystemPalette], light));
+     m_palettes[MenuBarPalette] = menuBarPalette(*m_palettes[MenuPalette], light);
+     if (!light) {
++        QColor accent = qt_accentColor();
++        QColor accentLight = accent.lighter(120);
++        QColor accentDarkest = accent.darker(120 * 120 * 120);
++
+ #if QT_CONFIG(cpp_winrt)
+-        using namespace winrt::Windows::UI::ViewManagement;
+-        const auto settings = UISettings();
+-        const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
+-        const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
+-        const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
+-#else
+-        const QColor accent = qt_accentColor();
+-        const QColor accentLight = accent.lighter(120);
+-        const QColor accentDarkest = accent.darker(120 * 120 * 120);
++        if (IsWindows10OrGreater())
++        {
++            using namespace winrt::Windows::UI::ViewManagement;
++            const auto settings = UISettings();
++            accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
++            accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
++            accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));    
++        }    
+ #endif
+         m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]);
+         m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, accent);
diff --git a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
index c2c093faf4..d4910f0645 100644
--- a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
@@ -520,11 +520,15 @@ static inline void updateGLWindowSettings(const QWindow *w, HWND hwnd, Qt::Windo
 
 [[nodiscard]] static inline int getResizeBorderThickness(const UINT dpi)
 {
-    // The width of the padded border will always be 0 if DWM composition is
-    // disabled, but since it will always be enabled and can't be programtically
-    // disabled from Windows 8, we are safe to go.
-    return GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi)
-           + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
+    if (QWindowsContext::user32dll.getSystemMetricsForDpi) {
+        // The width of the padded border will always be 0 if DWM composition is
+        // disabled, but since it will always be enabled and can't be programtically
+        // disabled from Windows 8, we are safe to go.
+        return QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CXSIZEFRAME, dpi)
+               + QWindowsContext::user32dll.getSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
+    }
+    
+    return GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
 }
 
 /*!
@@ -534,13 +538,17 @@ static inline void updateGLWindowSettings(const QWindow *w, HWND hwnd, Qt::Windo
 
 static QMargins invisibleMargins(QPoint screenPoint)
 {
-    POINT pt = {screenPoint.x(), screenPoint.y()};
-    if (HMONITOR hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL)) {
-        UINT dpiX;
-        UINT dpiY;
-        if (SUCCEEDED(GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) {
-            const int gap = getResizeBorderThickness(dpiX);
-            return QMargins(gap, 0, gap, gap);
+    if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10) {
+        POINT pt = {screenPoint.x(), screenPoint.y()};
+        if (HMONITOR hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL)) {
+            if (QWindowsContext::shcoredll.isValid()) {
+                UINT dpiX;
+                UINT dpiY;
+                if (SUCCEEDED(QWindowsContext::shcoredll.getDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) {
+                    const int gap = getResizeBorderThickness(dpiX);
+                    return QMargins(gap, 0, gap, gap);
+                }
+            }
         }
     }
     return QMargins();
@@ -548,9 +556,12 @@ static QMargins invisibleMargins(QPoint screenPoint)
 
 [[nodiscard]] static inline QMargins invisibleMargins(const HWND hwnd)
 {
-    const UINT dpi = GetDpiForWindow(hwnd);
-    const int gap = getResizeBorderThickness(dpi);
-    return QMargins(gap, 0, gap, gap);
+    if (QWindowsContext::user32dll.getDpiForWindow) {
+        const UINT dpi = QWindowsContext::user32dll.getDpiForWindow(hwnd);
+        const int gap = getResizeBorderThickness(dpi);
+        return QMargins(gap, 0, gap, gap);
+    }
+    return QMargins();
 }
 
 /*!
@@ -1068,7 +1079,8 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, DWORD style, DWORD exStyl
         return {};
     RECT rect = {0,0,0,0};
     style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs.
-    if (AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, unsigned(qRound(dpi))) == FALSE) {
+    if (QWindowsContext::user32dll.adjustWindowRectExForDpi &&
+        QWindowsContext::user32dll.adjustWindowRectExForDpi(&rect, style, FALSE, exStyle, unsigned(qRound(dpi))) == FALSE) {
         qErrnoWarning("%s: AdjustWindowRectExForDpi failed", __FUNCTION__);
     }
     const QMargins result(qAbs(rect.left), qAbs(rect.top),
@@ -1561,7 +1573,8 @@ void QWindowsWindow::initialize()
             QWindowSystemInterface::handleGeometryChange<QWindowSystemInterface::SynchronousDelivery>(w, obtainedGeometry);
         }
     }
-    QWindowsWindow::setSavedDpi(GetDpiForWindow(handle()));
+    QWindowsWindow::setSavedDpi(QWindowsContext::user32dll.getDpiForWindow ?
+        QWindowsContext::user32dll.getDpiForWindow(handle()) : 96);
 }
 
 QSurfaceFormat QWindowsWindow::format() const
@@ -2056,17 +2069,20 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam)
 
 void QWindowsWindow::handleDpiChangedAfterParent(HWND hwnd)
 {
-    const UINT dpi = GetDpiForWindow(hwnd);
-    const qreal scale = dpiRelativeScale(dpi);
-    setSavedDpi(dpi);
-
-    checkForScreenChanged(QWindowsWindow::FromDpiChange);
-
-    // Child windows do not get WM_GETDPISCALEDSIZE messages to inform
-    // Windows about the new size, so we need to manually scale them.
-    QRect currentGeometry = geometry();
-    QRect scaledGeometry = QRect(currentGeometry.topLeft() * scale, currentGeometry.size() * scale);
-    setGeometry(scaledGeometry);
+    if (QWindowsContext::user32dll.getDpiForWindow)
+    {
+        const UINT dpi = QWindowsContext::user32dll.getDpiForWindow(hwnd);
+        const qreal scale = dpiRelativeScale(dpi);
+        setSavedDpi(dpi);
+
+        checkForScreenChanged(QWindowsWindow::FromDpiChange);
+
+        // Child windows do not get WM_GETDPISCALEDSIZE messages to inform
+        // Windows about the new size, so we need to manually scale them.
+        QRect currentGeometry = geometry();
+        QRect scaledGeometry = QRect(currentGeometry.topLeft() * scale, currentGeometry.size() * scale);
+        setGeometry(scaledGeometry);
+    }    
 }
 
 static QRect normalFrameGeometry(HWND hwnd)
diff --git a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp.orig b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp.orig
new file mode 100644
index 0000000000..c2c093faf4
--- /dev/null
+++ b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp.orig
@@ -0,0 +1,3483 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtCore/qt_windows.h>
+
+#include "qwindowswindow.h"
+#include "qwindowscontext.h"
+#if QT_CONFIG(draganddrop)
+#  include "qwindowsdrag.h"
+#endif
+#include "qwindowsscreen.h"
+#include "qwindowsintegration.h"
+#include "qwindowsmenu.h"
+#include "qwindowsnativeinterface.h"
+#if QT_CONFIG(dynamicgl)
+#  include "qwindowsglcontext.h"
+#else
+#  include "qwindowsopenglcontext.h"
+#endif
+#include "qwindowsopengltester.h"
+#ifdef QT_NO_CURSOR
+#  include "qwindowscursor.h"
+#endif
+
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qscreen.h>
+#include <QtGui/qwindow.h>
+#include <QtGui/qregion.h>
+#include <QtGui/qopenglcontext.h>
+#include <QtGui/private/qwindowsthemecache_p.h>
+#include <private/qwindow_p.h> // QWINDOWSIZE_MAX
+#include <private/qguiapplication_p.h>
+#include <private/qhighdpiscaling_p.h>
+#include <qpa/qwindowsysteminterface.h>
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qlibraryinfo.h>
+
+#include <dwmapi.h>
+
+#if QT_CONFIG(vulkan)
+#include "qwindowsvulkaninstance.h"
+#endif
+
+#include <shellscalingapi.h>
+
+QT_BEGIN_NAMESPACE
+
+using QWindowCreationContextPtr = QSharedPointer<QWindowCreationContext>;
+
+enum {
+    defaultWindowWidth = 160,
+    defaultWindowHeight = 160
+};
+
+Q_GUI_EXPORT HICON qt_pixmapToWinHICON(const QPixmap &);
+
+static QByteArray debugWinStyle(DWORD style)
+{
+    QByteArray rc = "0x";
+    rc += QByteArray::number(qulonglong(style), 16);
+    if (style & WS_POPUP)
+        rc += " WS_POPUP";
+    if (style & WS_CHILD)
+        rc += " WS_CHILD";
+    if (style & WS_OVERLAPPED)
+        rc += " WS_OVERLAPPED";
+    if (style & WS_CLIPSIBLINGS)
+        rc += " WS_CLIPSIBLINGS";
+    if (style & WS_CLIPCHILDREN)
+        rc += " WS_CLIPCHILDREN";
+    if (style & WS_THICKFRAME)
+        rc += " WS_THICKFRAME";
+    if (style & WS_DLGFRAME)
+        rc += " WS_DLGFRAME";
+    if (style & WS_SYSMENU)
+        rc += " WS_SYSMENU";
+    if (style & WS_MINIMIZEBOX)
+        rc += " WS_MINIMIZEBOX";
+    if (style & WS_MAXIMIZEBOX)
+        rc += " WS_MAXIMIZEBOX";
+    if (style & WS_BORDER)
+        rc += " WS_BORDER";
+    if (style & WS_CAPTION)
+        rc += " WS_CAPTION";
+    if (style & WS_CHILDWINDOW)
+        rc += " WS_CHILDWINDOW";
+    if (style & WS_DISABLED)
+        rc += " WS_DISABLED";
+    if (style & WS_GROUP)
+        rc += " WS_GROUP";
+    if (style & WS_HSCROLL)
+        rc += " WS_HSCROLL";
+    if (style & WS_ICONIC)
+        rc += " WS_ICONIC";
+    if (style & WS_MAXIMIZE)
+        rc += " WS_MAXIMIZE";
+    if (style & WS_MINIMIZE)
+        rc += " WS_MINIMIZE";
+    if (style & WS_SIZEBOX)
+        rc += " WS_SIZEBOX";
+    if (style & WS_TABSTOP)
+        rc += " WS_TABSTOP";
+    if (style & WS_TILED)
+        rc += " WS_TILED";
+    if (style & WS_VISIBLE)
+        rc += " WS_VISIBLE";
+    if (style & WS_VSCROLL)
+        rc += " WS_VSCROLL";
+    return rc;
+}
+
+static QByteArray debugWinExStyle(DWORD exStyle)
+{
+    QByteArray rc = "0x";
+    rc += QByteArray::number(qulonglong(exStyle), 16);
+    if (exStyle & WS_EX_TOOLWINDOW)
+        rc += " WS_EX_TOOLWINDOW";
+    if (exStyle & WS_EX_CONTEXTHELP)
+        rc += " WS_EX_CONTEXTHELP";
+    if (exStyle & WS_EX_LAYERED)
+        rc += " WS_EX_LAYERED";
+    if (exStyle & WS_EX_DLGMODALFRAME)
+        rc += " WS_EX_DLGMODALFRAME";
+    if (exStyle & WS_EX_LAYOUTRTL)
+        rc += " WS_EX_LAYOUTRTL";
+    if (exStyle & WS_EX_NOINHERITLAYOUT)
+        rc += " WS_EX_NOINHERITLAYOUT";
+    if (exStyle & WS_EX_ACCEPTFILES)
+        rc += " WS_EX_ACCEPTFILES";
+    if (exStyle & WS_EX_APPWINDOW)
+        rc += " WS_EX_APPWINDOW";
+    if (exStyle & WS_EX_CLIENTEDGE)
+        rc += " WS_EX_CLIENTEDGE";
+    if (exStyle & WS_EX_COMPOSITED)
+        rc += " WS_EX_COMPOSITED";
+    if (exStyle & WS_EX_CONTROLPARENT)
+        rc += " WS_EX_CONTROLPARENT";
+    if (exStyle & WS_EX_LEFT)
+        rc += " WS_EX_LEFT";
+    if (exStyle & WS_EX_LEFTSCROLLBAR)
+        rc += " WS_EX_LEFTSCROLLBAR";
+    if (exStyle & WS_EX_LTRREADING)
+        rc += " WS_EX_LTRREADING";
+    if (exStyle & WS_EX_MDICHILD)
+        rc += " WS_EX_MDICHILD";
+    if (exStyle & WS_EX_NOACTIVATE)
+        rc += " WS_EX_NOACTIVATE";
+    if (exStyle & WS_EX_NOPARENTNOTIFY)
+        rc += " WS_EX_NOPARENTNOTIFY";
+    if (exStyle & WS_EX_NOREDIRECTIONBITMAP)
+        rc += " WS_EX_NOREDIRECTIONBITMAP";
+    if (exStyle & WS_EX_RIGHT)
+        rc += " WS_EX_RIGHT";
+    if (exStyle & WS_EX_RIGHTSCROLLBAR)
+        rc += " WS_EX_RIGHTSCROLLBAR";
+    if (exStyle & WS_EX_RTLREADING)
+        rc += " WS_EX_RTLREADING";
+    if (exStyle & WS_EX_STATICEDGE)
+        rc += " WS_EX_STATICEDGE";
+    if (exStyle & WS_EX_TOPMOST)
+        rc += " WS_EX_TOPMOST";
+    if (exStyle & WS_EX_TRANSPARENT)
+        rc += " WS_EX_TRANSPARENT";
+    if (exStyle & WS_EX_WINDOWEDGE)
+        rc += " WS_EX_WINDOWEDGE";
+    return rc;
+}
+
+static QByteArray debugWinSwpPos(UINT flags)
+{
+    QByteArray rc = "0x";
+    rc += QByteArray::number(flags, 16);
+    if (flags & SWP_FRAMECHANGED)
+        rc += " SWP_FRAMECHANGED";
+    if (flags & SWP_HIDEWINDOW)
+        rc += " SWP_HIDEWINDOW";
+    if (flags & SWP_NOACTIVATE)
+        rc += " SWP_NOACTIVATE";
+    if (flags & SWP_NOCOPYBITS)
+        rc += " SWP_NOCOPYBITS";
+    if (flags & SWP_NOMOVE)
+        rc += " SWP_NOMOVE";
+    if (flags & SWP_NOOWNERZORDER)
+        rc += " SWP_NOOWNERZORDER";
+    if (flags & SWP_NOREDRAW)
+        rc += " SWP_NOREDRAW";
+    if (flags & SWP_NOSENDCHANGING)
+        rc += " SWP_NOSENDCHANGING";
+    if (flags & SWP_NOSIZE)
+        rc += " SWP_NOSIZE";
+    if (flags & SWP_NOZORDER)
+        rc += " SWP_NOZORDER";
+    if (flags & SWP_SHOWWINDOW)
+        rc += " SWP_SHOWWINDOW";
+    if (flags & SWP_ASYNCWINDOWPOS)
+        rc += " SWP_ASYNCWINDOWPOS";
+    if (flags & SWP_DEFERERASE)
+        rc += " SWP_DEFERERASE";
+    if (flags & SWP_DRAWFRAME)
+        rc += " SWP_DRAWFRAME";
+    if (flags & SWP_NOREPOSITION)
+        rc += " SWP_NOREPOSITION";
+    return rc;
+}
+
+[[nodiscard]] static inline QByteArray debugWindowPlacementFlags(const UINT flags)
+{
+    QByteArray rc = "0x";
+    rc += QByteArray::number(flags, 16);
+    if (flags & WPF_SETMINPOSITION)
+        rc += " WPF_SETMINPOSITION";
+    if (flags & WPF_RESTORETOMAXIMIZED)
+        rc += " WPF_RESTORETOMAXIMIZED";
+    if (flags & WPF_ASYNCWINDOWPLACEMENT)
+        rc += " WPF_ASYNCWINDOWPLACEMENT";
+    return rc;
+}
+
+[[nodiscard]] static inline QByteArray debugShowWindowCmd(const UINT cmd)
+{
+    QByteArray rc = {};
+    rc += QByteArray::number(cmd);
+    if (cmd == SW_HIDE)
+        rc += " SW_HIDE";
+    if (cmd == SW_SHOWNORMAL)
+        rc += " SW_SHOWNORMAL";
+    if (cmd == SW_NORMAL)
+        rc += " SW_NORMAL";
+    if (cmd == SW_SHOWMINIMIZED)
+        rc += " SW_SHOWMINIMIZED";
+    if (cmd == SW_SHOWMAXIMIZED)
+        rc += " SW_SHOWMAXIMIZED";
+    if (cmd == SW_MAXIMIZE)
+        rc += " SW_MAXIMIZE";
+    if (cmd == SW_SHOWNOACTIVATE)
+        rc += " SW_SHOWNOACTIVATE";
+    if (cmd == SW_SHOW)
+        rc += " SW_SHOW";
+    if (cmd == SW_MINIMIZE)
+        rc += " SW_MINIMIZE";
+    if (cmd == SW_SHOWMINNOACTIVE)
+        rc += " SW_SHOWMINNOACTIVE";
+    if (cmd == SW_SHOWNA)
+        rc += " SW_SHOWNA";
+    if (cmd == SW_RESTORE)
+        rc += " SW_RESTORE";
+    if (cmd == SW_SHOWDEFAULT)
+        rc += " SW_SHOWDEFAULT";
+    if (cmd == SW_FORCEMINIMIZE)
+        rc += " SW_FORCEMINIMIZE";
+    return rc;
+}
+
+static inline QSize qSizeOfRect(const RECT &rect)
+{
+    return QSize(rect.right -rect.left, rect.bottom - rect.top);
+}
+
+static inline QRect qrectFromRECT(const RECT &rect)
+{
+    return QRect(QPoint(rect.left, rect.top), qSizeOfRect(rect));
+}
+
+static inline RECT RECTfromQRect(const QRect &rect)
+{
+    const int x = rect.left();
+    const int y = rect.top();
+    RECT result = { x, y, x + rect.width(), y + rect.height() };
+    return result;
+}
+
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug d, const RECT &r)
+{
+    QDebugStateSaver saver(d);
+    d.nospace();
+    d << "RECT(left=" << r.left << ", top=" << r.top
+        << ", right=" << r.right << ", bottom=" << r.bottom
+        << " (" << r.right - r.left << 'x' << r.bottom - r.top << "))";
+    return d;
+}
+
+QDebug operator<<(QDebug d, const POINT &p)
+{
+    QDebugStateSaver saver(d);
+    d.nospace();
+    d << "POINT(x=" << p.x << ", y=" << p.y << ')';
+    return d;
+}
+
+QDebug operator<<(QDebug d, const WINDOWPOS &wp)
+{
+    QDebugStateSaver saver(d);
+    d.nospace();
+    d.noquote();
+    d <<  "WINDOWPOS(flags=" << debugWinSwpPos(wp.flags) << ", hwnd="
+       << wp.hwnd << ", hwndInsertAfter=" << wp.hwndInsertAfter << ", x=" << wp.x
+       <<  ", y=" << wp.y << ", cx=" << wp.cx <<  ", cy=" << wp.cy << ')';
+    return d;
+}
+
+QDebug operator<<(QDebug d, const NCCALCSIZE_PARAMS &p)
+{
+    QDebugStateSaver saver(d);
+    d.nospace();
+    d << "NCCALCSIZE_PARAMS(rgrc=[" << p.rgrc[0] << ", " << p.rgrc[1] << ", "
+        << p.rgrc[2] << "], lppos=" << *p.lppos << ')';
+    return d;
+}
+
+QDebug operator<<(QDebug d, const MINMAXINFO &i)
+{
+    QDebugStateSaver saver(d);
+    d.nospace();
+    d << "MINMAXINFO(maxSize=" << i.ptMaxSize << ", "
+      << "maxpos=" << i.ptMaxPosition << ", "
+      << "maxtrack=" << i.ptMaxTrackSize << ", "
+      << "mintrack=" << i.ptMinTrackSize << ')';
+    return d;
+}
+
+QDebug operator<<(QDebug d, const WINDOWPLACEMENT &wp)
+{
+    QDebugStateSaver saver(d);
+    d.nospace();
+    d.noquote();
+    d <<  "WINDOWPLACEMENT(flags=" << debugWindowPlacementFlags(wp.flags) << ", showCmd="
+      << debugShowWindowCmd(wp.showCmd) << ", ptMinPosition=" << wp.ptMinPosition
+      << ", ptMaxPosition=" << wp.ptMaxPosition << ", rcNormalPosition="
+      << wp.rcNormalPosition << ')';
+    return d;
+}
+
+QDebug operator<<(QDebug d, const GUID &guid)
+{
+    QDebugStateSaver saver(d);
+    d.nospace();
+    d << '{' << Qt::hex << Qt::uppercasedigits << qSetPadChar(u'0')
+      << qSetFieldWidth(8) << guid.Data1
+      << qSetFieldWidth(0) << '-' << qSetFieldWidth(4)
+      << guid.Data2 << qSetFieldWidth(0) << '-' << qSetFieldWidth(4)
+      << guid.Data3 << qSetFieldWidth(0) << '-' << qSetFieldWidth(4)
+      << qSetFieldWidth(2) << guid.Data4[0] << guid.Data4[1]
+      << qSetFieldWidth(0) << '-' << qSetFieldWidth(2);
+    for (int i = 2; i < 8; ++i)
+        d << guid.Data4[i];
+    d << qSetFieldWidth(0) << '}';
+    return d;
+}
+#endif // !QT_NO_DEBUG_STREAM
+
+static void formatBriefRectangle(QDebug &d, const QRect &r)
+{
+    d << r.width() << 'x' << r.height() << Qt::forcesign << r.x() << r.y() << Qt::noforcesign;
+}
+
+static void formatBriefMargins(QDebug &d, const QMargins &m)
+{
+    d << m.left() << ", " << m.top() << ", " << m.right() << ", " << m.bottom();
+}
+
+// QTBUG-43872, for windows that do not have WS_EX_TOOLWINDOW set, WINDOWPLACEMENT
+// is in workspace/available area coordinates.
+static QPoint windowPlacementOffset(HWND hwnd, const QPoint &point)
+{
+    if (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW)
+        return QPoint(0, 0);
+    const QWindowsScreenManager &screenManager = QWindowsContext::instance()->screenManager();
+    const QWindowsScreen *screen = screenManager.screens().size() == 1
+        ? screenManager.screens().constFirst() : screenManager.screenAtDp(point);
+    if (screen)
+        return screen->availableGeometry().topLeft() - screen->geometry().topLeft();
+    return QPoint(0, 0);
+}
+
+// Return the frame geometry relative to the parent
+// if there is one.
+static inline QRect frameGeometry(HWND hwnd, bool topLevel)
+{
+    RECT rect = { 0, 0, 0, 0 };
+    if (topLevel) {
+        WINDOWPLACEMENT windowPlacement;
+        windowPlacement.length = sizeof(WINDOWPLACEMENT);
+        GetWindowPlacement(hwnd, &windowPlacement);
+        if (windowPlacement.showCmd == SW_SHOWMINIMIZED) {
+            const QRect result = qrectFromRECT(windowPlacement.rcNormalPosition);
+            return result.translated(windowPlacementOffset(hwnd, result.topLeft()));
+        }
+    }
+    GetWindowRect(hwnd, &rect); // Screen coordinates.
+    const HWND parent = GetParent(hwnd);
+    if (parent && !topLevel) {
+        const int width = rect.right - rect.left;
+        const int height = rect.bottom - rect.top;
+        POINT leftTop = { rect.left, rect.top };
+        screenToClient(parent, &leftTop);
+        rect.left = leftTop.x;
+        rect.top = leftTop.y;
+        rect.right = leftTop.x + width;
+        rect.bottom = leftTop.y + height;
+    }
+    return qrectFromRECT(rect);
+}
+
+// Return the visibility of the Window (except full screen since it is not a window state).
+static QWindow::Visibility windowVisibility_sys(HWND hwnd)
+{
+    if (!IsWindowVisible(hwnd))
+        return QWindow::Hidden;
+    WINDOWPLACEMENT windowPlacement;
+    windowPlacement.length = sizeof(WINDOWPLACEMENT);
+    if (GetWindowPlacement(hwnd, &windowPlacement)) {
+        switch (windowPlacement.showCmd) {
+        case SW_SHOWMINIMIZED:
+        case SW_MINIMIZE:
+        case SW_FORCEMINIMIZE:
+            return QWindow::Minimized;
+        case SW_SHOWMAXIMIZED:
+            return QWindow::Maximized;
+        default:
+            break;
+        }
+    }
+    return QWindow::Windowed;
+}
+
+static inline bool windowIsAccelerated(const QWindow *w)
+{
+    switch (w->surfaceType()) {
+    case QSurface::OpenGLSurface:
+    case QSurface::VulkanSurface:
+    case QSurface::Direct3DSurface:
+        return true;
+    default:
+        return false;
+    }
+}
+
+static bool applyBlurBehindWindow(HWND hwnd)
+{
+    DWM_BLURBEHIND blurBehind = {0, 0, nullptr, 0};
+
+    blurBehind.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
+    blurBehind.fEnable = TRUE;
+    blurBehind.hRgnBlur = CreateRectRgn(0, 0, -1, -1);
+
+    const bool result = DwmEnableBlurBehindWindow(hwnd, &blurBehind) == S_OK;
+
+    if (blurBehind.hRgnBlur)
+        DeleteObject(blurBehind.hRgnBlur);
+
+    return result;
+}
+
+// from qwidget_win.cpp, pass flags separately in case they have been "autofixed".
+static bool shouldShowMaximizeButton(const QWindow *w, Qt::WindowFlags flags)
+{
+    if ((flags & Qt::MSWindowsFixedSizeDialogHint) || !(flags & Qt::WindowMaximizeButtonHint))
+        return false;
+    // if the user explicitly asked for the maximize button, we try to add
+    // it even if the window has fixed size.
+    return (flags & Qt::CustomizeWindowHint) ||
+        w->maximumSize() == QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX);
+}
+
+bool QWindowsWindow::hasNoNativeFrame(HWND hwnd, Qt::WindowFlags flags)
+{
+    const LONG_PTR style = GetWindowLongPtr(hwnd, GWL_STYLE);
+    return (style & WS_CHILD) || (flags & Qt::FramelessWindowHint);
+}
+
+// Set the WS_EX_LAYERED flag on a HWND if required. This is required for
+// translucent backgrounds, not fully opaque windows and for
+// Qt::WindowTransparentForInput (in combination with WS_EX_TRANSPARENT).
+bool QWindowsWindow::setWindowLayered(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, qreal opacity)
+{
+    const LONG_PTR exStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
+    // Native children are frameless by nature, so check for that as well.
+    const bool needsLayered = (flags & Qt::WindowTransparentForInput)
+        || (hasAlpha && hasNoNativeFrame(hwnd, flags)) || opacity < 1.0;
+    const bool isLayered = (exStyle & WS_EX_LAYERED);
+    if (needsLayered != isLayered) {
+        if (needsLayered) {
+            SetWindowLongPtr(hwnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
+        } else {
+            SetWindowLongPtr(hwnd, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
+        }
+    }
+    return needsLayered;
+}
+
+static void setWindowOpacity(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, bool accelerated, qreal level)
+{
+    if (QWindowsWindow::setWindowLayered(hwnd, flags, hasAlpha, level)) {
+        const BYTE alpha = BYTE(qRound(255.0 * level));
+        if (hasAlpha && !accelerated && QWindowsWindow::hasNoNativeFrame(hwnd, flags)) {
+            // Non-GL windows with alpha: Use blend function to update.
+            BLENDFUNCTION blend = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA};
+            UpdateLayeredWindow(hwnd, nullptr, nullptr, nullptr, nullptr, nullptr, 0, &blend, ULW_ALPHA);
+        } else {
+            SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA);
+        }
+    } else if (IsWindowVisible(hwnd)) { // Repaint when switching from layered.
+        InvalidateRect(hwnd, nullptr, TRUE);
+    }
+}
+
+static inline void updateGLWindowSettings(const QWindow *w, HWND hwnd, Qt::WindowFlags flags, qreal opacity)
+{
+    const bool isAccelerated = windowIsAccelerated(w);
+    const bool hasAlpha = w->format().hasAlpha();
+
+    if (isAccelerated && hasAlpha)
+        applyBlurBehindWindow(hwnd);
+
+    setWindowOpacity(hwnd, flags, hasAlpha, isAccelerated, opacity);
+}
+
+[[nodiscard]] static inline int getResizeBorderThickness(const UINT dpi)
+{
+    // The width of the padded border will always be 0 if DWM composition is
+    // disabled, but since it will always be enabled and can't be programtically
+    // disabled from Windows 8, we are safe to go.
+    return GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi)
+           + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
+}
+
+/*!
+    Calculates the dimensions of the invisible borders within the
+    window frames which only exist on Windows 10 and onwards.
+*/
+
+static QMargins invisibleMargins(QPoint screenPoint)
+{
+    POINT pt = {screenPoint.x(), screenPoint.y()};
+    if (HMONITOR hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL)) {
+        UINT dpiX;
+        UINT dpiY;
+        if (SUCCEEDED(GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) {
+            const int gap = getResizeBorderThickness(dpiX);
+            return QMargins(gap, 0, gap, gap);
+        }
+    }
+    return QMargins();
+}
+
+[[nodiscard]] static inline QMargins invisibleMargins(const HWND hwnd)
+{
+    const UINT dpi = GetDpiForWindow(hwnd);
+    const int gap = getResizeBorderThickness(dpi);
+    return QMargins(gap, 0, gap, gap);
+}
+
+/*!
+    \class WindowCreationData
+    \brief Window creation code.
+
+    This struct gathers all information required to create a window.
+    Window creation is split in 3 steps:
+
+    \list
+    \li fromWindow() Gather all required information
+    \li create() Create the system handle.
+    \li initialize() Post creation initialization steps.
+    \endlist
+
+    The reason for this split is to also enable changing the QWindowFlags
+    by calling:
+
+    \list
+    \li fromWindow() Gather information and determine new system styles
+    \li applyWindowFlags() to apply the new window system styles.
+    \li initialize() Post creation initialization steps.
+    \endlist
+
+    Contains the window creation code formerly in qwidget_win.cpp.
+
+    \sa QWindowCreationContext
+    \internal
+*/
+
+struct WindowCreationData
+{
+    using WindowData = QWindowsWindowData;
+    enum Flags { ForceChild = 0x1, ForceTopLevel = 0x2 };
+
+    void fromWindow(const QWindow *w, const Qt::WindowFlags flags, unsigned creationFlags = 0);
+    inline WindowData create(const QWindow *w, const WindowData &data, QString title) const;
+    inline void applyWindowFlags(HWND hwnd) const;
+    void initialize(const QWindow *w, HWND h, bool frameChange, qreal opacityLevel) const;
+
+    Qt::WindowFlags flags;
+    HWND parentHandle = nullptr;
+    Qt::WindowType type = Qt::Widget;
+    unsigned style = 0;
+    unsigned exStyle = 0;
+    bool topLevel = false;
+    bool popup = false;
+    bool dialog = false;
+    bool tool = false;
+    bool embedded = false;
+    bool hasAlpha = false;
+};
+
+QDebug operator<<(QDebug debug, const WindowCreationData &d)
+{
+    QDebugStateSaver saver(debug);
+    debug.nospace();
+    debug.noquote();
+    debug << "WindowCreationData: " << d.flags
+        << "\n  topLevel=" << d.topLevel;
+     if (d.parentHandle)
+         debug << " parent=" << d.parentHandle;
+     debug << " popup=" << d.popup << " dialog=" << d.dialog
+        << " embedded=" << d.embedded << " tool=" << d.tool
+        << "\n  style=" << debugWinStyle(d.style);
+    if (d.exStyle)
+        debug << "\n  exStyle=" << debugWinExStyle(d.exStyle);
+    return debug;
+}
+
+// Fix top level window flags in case only the type flags are passed.
+static inline void fixTopLevelWindowFlags(Qt::WindowFlags &flags)
+{
+    // Not supported on Windows, also do correction when it is set.
+    flags &= ~Qt::WindowFullscreenButtonHint;
+    switch (flags) {
+    case Qt::Window:
+        flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinimizeButtonHint
+              |Qt::WindowMaximizeButtonHint|Qt::WindowCloseButtonHint;
+        break;
+    case Qt::Dialog:
+    case Qt::Tool:
+        flags |= Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint;
+        break;
+    default:
+        break;
+    }
+    if ((flags & Qt::WindowType_Mask) == Qt::SplashScreen)
+        flags |= Qt::FramelessWindowHint;
+}
+
+static QScreen *screenForDeviceName(const QWindow *w, const QString &name)
+{
+    const auto getDeviceName = [](const QScreen *screen) -> QString {
+        if (const auto s = static_cast<const QWindowsScreen *>(screen->handle()))
+            return s->data().deviceName;
+        return {};
+    };
+    QScreen *winScreen = w ? w->screen() : QGuiApplication::primaryScreen();
+    if (winScreen && getDeviceName(winScreen) != name) {
+        const auto screens = winScreen->virtualSiblings();
+        for (QScreen *screen : screens) {
+            if (getDeviceName(screen) == name)
+                return screen;
+        }
+    }
+    return winScreen;
+}
+
+static QPoint calcPosition(const QWindow *w, const QWindowCreationContextPtr &context, const QMargins &invMargins)
+{
+    const QPoint orgPos(context->frameX - invMargins.left(), context->frameY - invMargins.top());
+
+    if (!w || w->type() != Qt::Window)
+        return orgPos;
+
+    // Workaround for QTBUG-50371
+    const QScreen *screenForGL = QWindowsWindow::forcedScreenForGLWindow(w);
+    if (!screenForGL)
+        return orgPos;
+
+    const QPoint posFrame(context->frameX, context->frameY);
+    const QMargins margins = context->margins;
+    const QRect scrGeo = screenForGL->handle()->availableGeometry();
+
+    // Point is already in the required screen.
+    if (scrGeo.contains(orgPos))
+        return orgPos;
+
+    // If the visible part of the window is already in the
+    // required screen, just ignore the invisible offset.
+    if (scrGeo.contains(posFrame))
+        return posFrame;
+
+    // Find the original screen containing the coordinates.
+    const auto screens = screenForGL->virtualSiblings();
+    const QScreen *orgScreen = nullptr;
+    for (QScreen *screen : screens) {
+        if (screen->handle()->availableGeometry().contains(posFrame)) {
+            orgScreen = screen;
+            break;
+        }
+    }
+    const QPoint ctPos = QPoint(qMax(scrGeo.left(), scrGeo.center().x()
+                                     + (margins.right() - margins.left() - context->frameWidth)/2),
+                                qMax(scrGeo.top(), scrGeo.center().y()
+                                     + (margins.bottom() - margins.top() - context->frameHeight)/2));
+
+    // If initial coordinates were outside all screens, center the window on the required screen.
+    if (!orgScreen)
+        return ctPos;
+
+    const QRect orgGeo = orgScreen->handle()->availableGeometry();
+    const QRect orgFrame(QPoint(context->frameX, context->frameY),
+                         QSize(context->frameWidth, context->frameHeight));
+
+    // Window would be centered on orgScreen. Center it on the required screen.
+    if (orgGeo.center() == (orgFrame - margins).center())
+        return ctPos;
+
+    // Transform the coordinates to map them into the required screen.
+    const QPoint newPos(scrGeo.left() + ((posFrame.x() - orgGeo.left()) * scrGeo.width()) / orgGeo.width(),
+                        scrGeo.top() + ((posFrame.y() - orgGeo.top()) * scrGeo.height()) / orgGeo.height());
+    const QPoint newPosNoMargin(newPos.x() - invMargins.left(), newPos.y() - invMargins.top());
+
+    return scrGeo.contains(newPosNoMargin) ? newPosNoMargin : newPos;
+}
+
+void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flagsIn,
+                                    unsigned creationFlags)
+{
+    flags = flagsIn;
+
+    // Sometimes QWindow doesn't have a QWindow parent but does have a native parent window,
+    // e.g. in case of embedded ActiveQt servers. They should not be considered a top-level
+    // windows in such cases.
+    QVariant prop = w->property(QWindowsWindow::embeddedNativeParentHandleProperty);
+    if (prop.isValid()) {
+        embedded = true;
+        parentHandle = reinterpret_cast<HWND>(prop.value<WId>());
+    }
+
+    if (creationFlags & ForceChild) {
+        topLevel = false;
+    } else if (embedded) {
+        // Embedded native windows (for example Active X server windows) are by
+        // definition never toplevel, even though they do not have QWindow parents.
+        topLevel = false;
+    } else {
+        topLevel = (creationFlags & ForceTopLevel) ? true : w->isTopLevel();
+    }
+
+    if (topLevel)
+        fixTopLevelWindowFlags(flags);
+
+    type = static_cast<Qt::WindowType>(int(flags) & Qt::WindowType_Mask);
+    switch (type) {
+    case Qt::Dialog:
+    case Qt::Sheet:
+        dialog = true;
+        break;
+    case Qt::Drawer:
+    case Qt::Tool:
+        tool = true;
+        break;
+    case Qt::Popup:
+        popup = true;
+        break;
+    default:
+        break;
+    }
+    if ((flags & Qt::MSWindowsFixedSizeDialogHint))
+        dialog = true;
+
+    // This causes the title bar to drawn RTL and the close button
+    // to be left. Note that this causes:
+    // - All DCs created on the Window to have RTL layout (see SetLayout)
+    // - ClientToScreen() and ScreenToClient() to work in reverse as well.
+    // - Mouse event coordinates to be mirrored.
+    // - Positioning of child Windows.
+    if (QGuiApplication::layoutDirection() == Qt::RightToLeft
+        && (QWindowsIntegration::instance()->options() & QWindowsIntegration::RtlEnabled) != 0) {
+        exStyle |= WS_EX_LAYOUTRTL | WS_EX_NOINHERITLAYOUT;
+    }
+
+    // Parent: Use transient parent for top levels.
+    if (popup) {
+        flags |= Qt::WindowStaysOnTopHint; // a popup stays on top, no parent.
+    } else if (!embedded) {
+        if (const QWindow *parentWindow = topLevel ? w->transientParent() : w->parent())
+            parentHandle = QWindowsWindow::handleOf(parentWindow);
+    }
+
+    if (popup || (type == Qt::ToolTip) || (type == Qt::SplashScreen)) {
+        style = WS_POPUP;
+    } else if (topLevel) {
+        if (flags & Qt::FramelessWindowHint)
+            style = WS_POPUP;                // no border
+        else if (flags & Qt::WindowTitleHint)
+            style = WS_OVERLAPPED;
+        else
+            style = 0;
+    } else {
+        style = WS_CHILD;
+    }
+
+    style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN ;
+
+    if (topLevel) {
+        if ((type == Qt::Window || dialog || tool)) {
+            if (!(flags & Qt::FramelessWindowHint)) {
+                style |= WS_POPUP;
+                if (flags & Qt::MSWindowsFixedSizeDialogHint) {
+                    style |= WS_DLGFRAME;
+                } else {
+                    style |= WS_THICKFRAME;
+                }
+                if (flags & Qt::WindowTitleHint)
+                    style |= WS_CAPTION; // Contains WS_DLGFRAME
+            }
+            if (flags & Qt::WindowSystemMenuHint)
+                style |= WS_SYSMENU;
+            else if (dialog && (flags & Qt::WindowCloseButtonHint) && !(flags & Qt::FramelessWindowHint)) {
+                style |= WS_SYSMENU | WS_BORDER; // QTBUG-2027, dialogs without system menu.
+                exStyle |= WS_EX_DLGMODALFRAME;
+            }
+            const bool showMinimizeButton = flags & Qt::WindowMinimizeButtonHint;
+            if (showMinimizeButton)
+                style |= WS_MINIMIZEBOX;
+            const bool showMaximizeButton = shouldShowMaximizeButton(w, flags);
+            if (showMaximizeButton)
+                style |= WS_MAXIMIZEBOX;
+            if (showMinimizeButton || showMaximizeButton)
+                style |= WS_SYSMENU;
+            if (tool)
+                exStyle |= WS_EX_TOOLWINDOW;
+            if ((flags & Qt::WindowContextHelpButtonHint) && !showMinimizeButton
+                && !showMaximizeButton)
+                exStyle |= WS_EX_CONTEXTHELP;
+        } else {
+             exStyle |= WS_EX_TOOLWINDOW;
+        }
+
+        // make mouse events fall through this window
+        // NOTE: WS_EX_TRANSPARENT flag can make mouse inputs fall through a layered window
+        if (flagsIn & Qt::WindowTransparentForInput)
+            exStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT;
+
+        // Currently only compatible with D3D surfaces, use it with care.
+        if (qEnvironmentVariableIntValue("QT_QPA_DISABLE_REDIRECTION_SURFACE"))
+            exStyle |= WS_EX_NOREDIRECTIONBITMAP;
+    }
+}
+
+static inline bool shouldApplyDarkFrame(const QWindow *w)
+{
+    if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
+        return false;
+    // the application has explicitly opted out of dark frames
+    if (!QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames))
+        return false;
+
+    // if the application supports a dark border, and the palette is dark (window background color
+    // is darker than the text), then turn dark-border support on, otherwise use a light border.
+    auto *dWindow = QWindowPrivate::get(const_cast<QWindow*>(w));
+    const QPalette windowPal = dWindow->windowPalette();
+    return windowPal.color(QPalette::WindowText).lightness()
+         > windowPal.color(QPalette::Window).lightness();
+}
+
+QWindowsWindowData
+    WindowCreationData::create(const QWindow *w, const WindowData &data, QString title) const
+{
+    WindowData result;
+    result.flags = flags;
+
+    const auto appinst = reinterpret_cast<HINSTANCE>(GetModuleHandle(nullptr));
+
+    const QString windowClassName = QWindowsContext::instance()->registerWindowClass(w);
+
+    const QScreen *screen{};
+    const QRect rect = QPlatformWindow::initialGeometry(w, data.geometry,
+                                                        defaultWindowWidth, defaultWindowHeight,
+                                                        &screen);
+
+    if (title.isEmpty() && (result.flags & Qt::WindowTitleHint))
+        title = topLevel ? qAppName() : w->objectName();
+
+    const auto *titleUtf16 = reinterpret_cast<const wchar_t *>(title.utf16());
+    const auto *classNameUtf16 = reinterpret_cast<const wchar_t *>(windowClassName.utf16());
+
+    // Capture events before CreateWindowEx() returns. The context is cleared in
+    // the QWindowsWindow constructor.
+    const QWindowCreationContextPtr context(new QWindowCreationContext(w, screen, data.geometry,
+                                                                       rect, data.customMargins,
+                                                                       style, exStyle));
+    QWindowsContext::instance()->setWindowCreationContext(context);
+
+    const bool hasFrame = (style & (WS_DLGFRAME | WS_THICKFRAME))
+            && !(result.flags & Qt::FramelessWindowHint);
+    QMargins invMargins = topLevel && hasFrame && QWindowsGeometryHint::positionIncludesFrame(w)
+            ? invisibleMargins(QPoint(context->frameX, context->frameY)) : QMargins();
+
+    qCDebug(lcQpaWindow).nospace()
+        << "CreateWindowEx: " << w << " class=" << windowClassName << " title=" << title
+        << '\n' << *this << "\nrequested: " << rect << ": "
+        << context->frameWidth << 'x' <<  context->frameHeight
+        << '+' << context->frameX << '+' << context->frameY
+        << " custom margins: " << context->customMargins
+        << " invisible margins: " << invMargins;
+
+
+    QPoint pos = calcPosition(w, context, invMargins);
+
+    // Mirror the position when creating on a parent in RTL mode, ditto for the obtained geometry.
+    int mirrorParentWidth = 0;
+    if (!w->isTopLevel() && QWindowsBaseWindow::isRtlLayout(parentHandle)) {
+        RECT rect;
+        GetClientRect(parentHandle, &rect);
+        mirrorParentWidth = rect.right;
+    }
+    if (mirrorParentWidth != 0 && pos.x() != CW_USEDEFAULT && context->frameWidth != CW_USEDEFAULT)
+        pos.setX(mirrorParentWidth - context->frameWidth - pos.x());
+
+    result.hwnd = CreateWindowEx(exStyle, classNameUtf16, titleUtf16,
+                                 style,
+                                 pos.x(), pos.y(),
+                                 context->frameWidth, context->frameHeight,
+                                 parentHandle, nullptr, appinst, nullptr);
+    qCDebug(lcQpaWindow).nospace()
+        << "CreateWindowEx: returns " << w << ' ' << result.hwnd << " obtained geometry: "
+        << context->obtainedPos << context->obtainedSize << ' ' << context->margins;
+
+    if (!result.hwnd) {
+        qErrnoWarning("%s: CreateWindowEx failed", __FUNCTION__);
+        return result;
+    }
+
+    if (QWindowsContext::isDarkMode() && shouldApplyDarkFrame(w))
+        QWindowsWindow::setDarkBorderToWindow(result.hwnd, true);
+
+    if (mirrorParentWidth != 0) {
+        context->obtainedPos.setX(mirrorParentWidth - context->obtainedSize.width()
+                                  -  context->obtainedPos.x());
+    }
+
+    QRect obtainedGeometry(context->obtainedPos, context->obtainedSize);
+
+    result.geometry = obtainedGeometry;
+    result.restoreGeometry = frameGeometry(result.hwnd, topLevel);
+    result.fullFrameMargins = context->margins;
+    result.embedded = embedded;
+    result.hasFrame = hasFrame;
+    result.customMargins = context->customMargins;
+
+    return result;
+}
+
+void WindowCreationData::applyWindowFlags(HWND hwnd) const
+{
+    // Keep enabled and visible from the current style.
+    const LONG_PTR oldStyle = GetWindowLongPtr(hwnd, GWL_STYLE);
+    const LONG_PTR oldExStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
+
+    const LONG_PTR newStyle = style | (oldStyle & (WS_DISABLED|WS_VISIBLE));
+    if (oldStyle != newStyle)
+        SetWindowLongPtr(hwnd, GWL_STYLE, newStyle);
+    const LONG_PTR newExStyle = exStyle;
+    if (newExStyle != oldExStyle)
+        SetWindowLongPtr(hwnd, GWL_EXSTYLE, newExStyle);
+    qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << hwnd << *this
+        << "\n    Style from " << debugWinStyle(DWORD(oldStyle)) << "\n    to "
+        << debugWinStyle(DWORD(newStyle)) << "\n    ExStyle from "
+        << debugWinExStyle(DWORD(oldExStyle)) << " to "
+        << debugWinExStyle(DWORD(newExStyle));
+}
+
+void WindowCreationData::initialize(const QWindow *w, HWND hwnd, bool frameChange, qreal opacityLevel) const
+{
+    if (!hwnd)
+        return;
+    UINT swpFlags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER;
+    if (frameChange)
+        swpFlags |= SWP_FRAMECHANGED;
+    if (topLevel) {
+        swpFlags |= SWP_NOACTIVATE;
+        if ((flags & Qt::WindowStaysOnTopHint) || (type == Qt::ToolTip)) {
+            SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, swpFlags);
+            if (flags & Qt::WindowStaysOnBottomHint)
+                qWarning("QWidget: Incompatible window flags: the window can't be on top and on bottom at the same time");
+        } else if (flags & Qt::WindowStaysOnBottomHint) {
+            SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, swpFlags);
+        } else if (frameChange) { // Force WM_NCCALCSIZE with wParam=1 in case of custom margins.
+            SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, swpFlags);
+        }
+        if (flags & (Qt::CustomizeWindowHint|Qt::WindowTitleHint)) {
+            HMENU systemMenu = GetSystemMenu(hwnd, FALSE);
+            if (flags & Qt::WindowCloseButtonHint)
+                EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND|MF_ENABLED);
+            else
+                EnableMenuItem(systemMenu, SC_CLOSE, MF_BYCOMMAND|MF_GRAYED);
+        }
+        updateGLWindowSettings(w, hwnd, flags, opacityLevel);
+    } else { // child.
+        SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, swpFlags);
+    }
+}
+
+
+// Scaling helpers for size constraints.
+static QSize toNativeSizeConstrained(QSize dip, const QScreen *s)
+{
+    if (QHighDpiScaling::isActive()) {
+        const qreal factor = QHighDpiScaling::factor(s);
+        if (!qFuzzyCompare(factor, qreal(1))) {
+            if (dip.width() > 0 && dip.width() < QWINDOWSIZE_MAX)
+                dip.setWidth(qRound(qreal(dip.width()) * factor));
+            if (dip.height() > 0 && dip.height() < QWINDOWSIZE_MAX)
+                dip.setHeight(qRound(qreal(dip.height()) * factor));
+        }
+    }
+    return dip;
+}
+
+// Helper for checking if frame adjustment needs to be skipped
+// NOTE: Unmaximized frameless windows will skip margins calculation
+static bool shouldOmitFrameAdjustment(const Qt::WindowFlags flags, DWORD style)
+{
+    return flags.testFlag(Qt::FramelessWindowHint) && !(style & WS_MAXIMIZE);
+}
+
+// Helper for checking if frame adjustment needs to be skipped
+// NOTE: Unmaximized frameless windows will skip margins calculation
+static bool shouldOmitFrameAdjustment(const Qt::WindowFlags flags, HWND hwnd)
+{
+    DWORD style = hwnd != nullptr ? DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)) : 0;
+    return flags.testFlag(Qt::FramelessWindowHint) && !(style & WS_MAXIMIZE);
+}
+
+/*!
+    \class QWindowsGeometryHint
+    \brief Stores geometry constraints and provides utility functions.
+
+    Geometry constraints ready to apply to a MINMAXINFO taking frame
+    into account.
+
+    \internal
+*/
+
+QMargins QWindowsGeometryHint::frameOnPrimaryScreen(const QWindow *w, DWORD style, DWORD exStyle)
+{
+    if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style))
+        return {};
+    RECT rect = {0,0,0,0};
+    style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs.
+    if (AdjustWindowRectEx(&rect, style, FALSE, exStyle) == FALSE)
+        qErrnoWarning("%s: AdjustWindowRectEx failed", __FUNCTION__);
+    const QMargins result(qAbs(rect.left), qAbs(rect.top),
+                          qAbs(rect.right), qAbs(rect.bottom));
+    qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << " style="
+        << Qt::showbase << Qt::hex << style << " exStyle=" << exStyle << Qt::dec << Qt::noshowbase
+        << ' ' << rect << ' ' << result;
+    return result;
+}
+
+QMargins QWindowsGeometryHint::frameOnPrimaryScreen(const QWindow *w, HWND hwnd)
+{
+    return frameOnPrimaryScreen(w, DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)),
+                                DWORD(GetWindowLongPtr(hwnd, GWL_EXSTYLE)));
+}
+
+QMargins QWindowsGeometryHint::frame(const QWindow *w, DWORD style, DWORD exStyle, qreal dpi)
+{
+    if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style))
+        return {};
+    RECT rect = {0,0,0,0};
+    style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs.
+    if (AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, unsigned(qRound(dpi))) == FALSE) {
+        qErrnoWarning("%s: AdjustWindowRectExForDpi failed", __FUNCTION__);
+    }
+    const QMargins result(qAbs(rect.left), qAbs(rect.top),
+                          qAbs(rect.right), qAbs(rect.bottom));
+    qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << " style="
+        << Qt::showbase << Qt::hex << style << " exStyle=" << exStyle << Qt::dec << Qt::noshowbase
+        << " dpi=" << dpi
+        << ' ' << rect << ' ' << result;
+    return result;
+}
+
+QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd, DWORD style, DWORD exStyle)
+{
+    if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style))
+        return {};
+    if (QWindowsScreenManager::isSingleScreen())
+        return frameOnPrimaryScreen(w, style, exStyle);
+    auto &screenManager = QWindowsContext::instance()->screenManager();
+    auto screen = screenManager.screenForHwnd(hwnd);
+    if (!screen)
+        screen = screenManager.screens().value(0);
+    const auto dpi = screen ? screen->logicalDpi().first : qreal(96);
+    return frame(w, style, exStyle, dpi);
+}
+
+QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd)
+{
+    return frame(w, hwnd, DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)),
+                 DWORD(GetWindowLongPtr(hwnd, GWL_EXSTYLE)));
+}
+
+// For newly created windows.
+QMargins QWindowsGeometryHint::frame(const QWindow *w, const QRect &geometry,
+                                     DWORD style, DWORD exStyle)
+{
+    if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style))
+        return {};
+    if (QWindowsScreenManager::isSingleScreen()
+        || !QWindowsContext::shouldHaveNonClientDpiScaling(w)) {
+        return frameOnPrimaryScreen(w, style, exStyle);
+    }
+    qreal dpi = 96;
+    auto &screenManager = QWindowsContext::instance()->screenManager();
+    auto screen = screenManager.screenAtDp(geometry.center());
+    if (!screen)
+        screen = screenManager.screens().value(0);
+    if (screen)
+        dpi = screen->logicalDpi().first;
+    return QWindowsGeometryHint::frame(w, style, exStyle, dpi);
+}
+
+bool QWindowsGeometryHint::handleCalculateSize(const QMargins &customMargins, const MSG &msg, LRESULT *result)
+{
+    // NCCALCSIZE_PARAMS structure if wParam==TRUE
+    if (!msg.wParam || customMargins.isNull())
+        return false;
+    *result = DefWindowProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
+    auto *ncp = reinterpret_cast<NCCALCSIZE_PARAMS *>(msg.lParam);
+    const RECT oldClientArea = ncp->rgrc[0];
+    ncp->rgrc[0].left += customMargins.left();
+    ncp->rgrc[0].top += customMargins.top();
+    ncp->rgrc[0].right -= customMargins.right();
+    ncp->rgrc[0].bottom -= customMargins.bottom();
+    result = nullptr;
+    qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << oldClientArea << '+' << customMargins << "-->"
+        << ncp->rgrc[0] << ' ' << ncp->rgrc[1] << ' ' << ncp->rgrc[2]
+        << ' ' << ncp->lppos->cx << ',' << ncp->lppos->cy;
+    return true;
+}
+
+void QWindowsGeometryHint::frameSizeConstraints(const QWindow *w, const QScreen *screen,
+                                                const QMargins &margins,
+                                                QSize *minimumSize, QSize *maximumSize)
+{
+    *minimumSize = toNativeSizeConstrained(w->minimumSize(), screen);
+    *maximumSize = toNativeSizeConstrained(w->maximumSize(), screen);
+
+    const int maximumWidth = qMax(maximumSize->width(), minimumSize->width());
+    const int maximumHeight = qMax(maximumSize->height(), minimumSize->height());
+    const int frameWidth = margins.left() + margins.right();
+    const int frameHeight = margins.top() + margins.bottom();
+
+    if (minimumSize->width() > 0)
+        minimumSize->rwidth() += frameWidth;
+    if (minimumSize->height() > 0)
+        minimumSize->rheight() += frameHeight;
+    if (maximumWidth < QWINDOWSIZE_MAX)
+        maximumSize->setWidth(maximumWidth + frameWidth);
+    if (maximumHeight < QWINDOWSIZE_MAX)
+        maximumSize->setHeight(maximumHeight + frameHeight);
+}
+
+void QWindowsGeometryHint::applyToMinMaxInfo(const QWindow *w,
+                                             const QScreen *screen,
+                                             const QMargins &margins,
+                                             MINMAXINFO *mmi)
+{
+    QSize minimumSize;
+    QSize maximumSize;
+    frameSizeConstraints(w, screen, margins, &minimumSize, &maximumSize);
+    qCDebug(lcQpaWindow).nospace() << '>' << __FUNCTION__ << '<' << " min="
+        << minimumSize.width() << ',' << minimumSize.height()
+        << " max=" << maximumSize.width() << ',' << maximumSize.height()
+        << " margins=" << margins
+        << " in " << *mmi;
+
+    if (minimumSize.width() > 0)
+        mmi->ptMinTrackSize.x = minimumSize.width();
+    if (minimumSize.height() > 0)
+        mmi->ptMinTrackSize.y = minimumSize.height();
+
+    if (maximumSize.width() < QWINDOWSIZE_MAX)
+        mmi->ptMaxTrackSize.x = maximumSize.width();
+    if (maximumSize.height() < QWINDOWSIZE_MAX)
+        mmi->ptMaxTrackSize.y = maximumSize.height();
+    qCDebug(lcQpaWindow).nospace() << '<' << __FUNCTION__ << " out " << *mmi;
+}
+
+void QWindowsGeometryHint::applyToMinMaxInfo(const QWindow *w,
+                                             const QMargins &margins,
+                                             MINMAXINFO *mmi)
+{
+    applyToMinMaxInfo(w, w->screen(), margins, mmi);
+}
+
+bool QWindowsGeometryHint::positionIncludesFrame(const QWindow *w)
+{
+    return qt_window_private(const_cast<QWindow *>(w))->positionPolicy
+           == QWindowPrivate::WindowFrameInclusive;
+}
+
+/*!
+    \class QWindowsBaseWindow
+    \brief Base class for QWindowsForeignWindow, QWindowsWindow
+
+    The class provides some _sys() getters for querying window
+    data from a HWND and some _sys() setters.
+
+    Derived classes wrapping foreign windows may use them directly
+    to calculate geometry, margins, etc.
+
+    Derived classes representing windows created by Qt may defer
+    expensive calculations until change notifications are received.
+
+    \since 5.6
+    \internal
+*/
+
+bool QWindowsBaseWindow::isRtlLayout(HWND hwnd)
+{
+    return (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_LAYOUTRTL) != 0;
+}
+
+QWindowsBaseWindow *QWindowsBaseWindow::baseWindowOf(const QWindow *w)
+{
+    if (w) {
+        if (QPlatformWindow *pw = w->handle())
+            return static_cast<QWindowsBaseWindow *>(pw);
+    }
+    return nullptr;
+}
+
+HWND QWindowsBaseWindow::handleOf(const QWindow *w)
+{
+    const QWindowsBaseWindow *bw = QWindowsBaseWindow::baseWindowOf(w);
+    return bw ? bw->handle() : HWND(nullptr);
+}
+
+bool QWindowsBaseWindow::isTopLevel_sys() const
+{
+    const HWND parent = parentHwnd();
+    return !parent || parent == GetDesktopWindow();
+}
+
+QRect QWindowsBaseWindow::frameGeometry_sys() const
+{
+    return frameGeometry(handle(), isTopLevel());
+}
+
+QRect QWindowsBaseWindow::geometry_sys() const
+{
+    return frameGeometry_sys().marginsRemoved(fullFrameMargins());
+}
+
+QMargins QWindowsBaseWindow::frameMargins_sys() const
+{
+    return QWindowsGeometryHint::frame(window(), handle(), style(), exStyle());
+}
+
+std::optional<QWindowsBaseWindow::TouchWindowTouchTypes>
+    QWindowsBaseWindow::touchWindowTouchTypes_sys() const
+{
+    ULONG touchFlags = 0;
+    if (IsTouchWindow(handle(), &touchFlags) == FALSE)
+        return {};
+    TouchWindowTouchTypes result;
+    if ((touchFlags & TWF_FINETOUCH) != 0)
+        result.setFlag(TouchWindowTouchType::FineTouch);
+    if ((touchFlags & TWF_WANTPALM) != 0)
+        result.setFlag(TouchWindowTouchType::WantPalmTouch);
+    return result;
+}
+
+void QWindowsBaseWindow::hide_sys() // Normal hide, do not activate other windows.
+{
+    SetWindowPos(handle(), nullptr , 0, 0, 0, 0,
+                 SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
+}
+
+void QWindowsBaseWindow::raise_sys()
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << this << window();
+    const Qt::WindowType type = window()->type();
+    if (type == Qt::Popup
+        || type == Qt::SubWindow // Special case for QTBUG-63121: MDI subwindows with WindowStaysOnTopHint
+        || !(window()->flags() & Qt::WindowStaysOnBottomHint)) {
+        SetWindowPos(handle(), HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
+    }
+}
+
+void QWindowsBaseWindow::lower_sys()
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << this << window();
+    if (!(window()->flags() & Qt::WindowStaysOnTopHint))
+        SetWindowPos(handle(), HWND_BOTTOM, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
+}
+
+void QWindowsBaseWindow::setWindowTitle_sys(const QString &title)
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << this << window() << title;
+    SetWindowText(handle(), reinterpret_cast<const wchar_t *>(title.utf16()));
+}
+
+QPoint QWindowsBaseWindow::mapToGlobal(const QPoint &pos) const
+{
+    return QWindowsGeometryHint::mapToGlobal(handle(), pos);
+}
+
+QPoint QWindowsBaseWindow::mapFromGlobal(const QPoint &pos) const
+{
+    return QWindowsGeometryHint::mapFromGlobal(handle(), pos);
+}
+
+void QWindowsBaseWindow::setHasBorderInFullScreen(bool)
+{
+    Q_UNIMPLEMENTED();
+}
+
+bool QWindowsBaseWindow::hasBorderInFullScreen() const
+{
+    Q_UNIMPLEMENTED();
+    return false;
+}
+
+QMargins QWindowsBaseWindow::customMargins() const
+{
+    return {};
+}
+
+void QWindowsBaseWindow::setCustomMargins(const QMargins &)
+{
+    Q_UNIMPLEMENTED();
+}
+
+/*!
+    \class QWindowsDesktopWindow
+    \brief Window wrapping GetDesktopWindow not allowing any manipulation.
+    \since 5.6
+    \internal
+*/
+
+/*!
+    \class QWindowsForeignWindow
+    \brief Window wrapping a foreign native window.
+
+    QWindowsForeignWindow stores a native HWND and implements getters for
+    geometry, margins, etc. reparenting and geometry manipulation for use as a
+    child window in Qt.
+
+    \since 5.6
+    \internal
+*/
+
+QWindowsForeignWindow::QWindowsForeignWindow(QWindow *window, HWND hwnd)
+    : QWindowsBaseWindow(window)
+    , m_hwnd(hwnd)
+    , m_topLevelStyle(0)
+{
+}
+
+void QWindowsForeignWindow::setParent(const QPlatformWindow *newParentWindow)
+{
+    const bool wasTopLevel = isTopLevel_sys();
+    const HWND newParent = newParentWindow ? reinterpret_cast<HWND>(newParentWindow->winId()) : HWND(nullptr);
+    const bool isTopLevel = !newParent;
+    const DWORD oldStyle = style();
+    qCDebug(lcQpaWindow) << __FUNCTION__ << window() << "newParent="
+        << newParentWindow << newParent << "oldStyle=" << debugWinStyle(oldStyle);
+    SetParent(m_hwnd, newParent);
+    if (wasTopLevel != isTopLevel) { // Top level window flags need to be set/cleared manually.
+        DWORD newStyle = oldStyle;
+        if (isTopLevel) {
+            newStyle = m_topLevelStyle;
+        } else {
+            m_topLevelStyle = oldStyle;
+            newStyle &= ~(WS_OVERLAPPEDWINDOW | WS_POPUPWINDOW);
+            newStyle |= WS_CHILD;
+        }
+        SetWindowLongPtr(m_hwnd, GWL_STYLE, newStyle);
+    }
+}
+
+void QWindowsForeignWindow::setVisible(bool visible)
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << window() << visible;
+    if (visible)
+        ShowWindow(handle(), SW_SHOWNOACTIVATE);
+    else
+        hide_sys();
+}
+
+/*!
+    \class QWindowCreationContext
+    \brief Active Context for creating windows.
+
+    There is a phase in window creation (WindowCreationData::create())
+    in which events are sent before the system API CreateWindowEx() returns
+    the handle. These cannot be handled by the platform window as the association
+    of the unknown handle value to the window does not exist yet and as not
+    to trigger recursive handle creation, etc.
+
+    In that phase, an instance of  QWindowCreationContext is set on
+    QWindowsContext.
+
+    QWindowCreationContext stores the information to answer the initial
+    WM_GETMINMAXINFO and obtains the corrected size/position.
+
+    \sa WindowCreationData, QWindowsContext
+    \internal
+*/
+
+QWindowCreationContext::QWindowCreationContext(const QWindow *w, const QScreen *s,
+                                               const QRect &geometryIn, const QRect &geometry,
+                                               const QMargins &cm,
+                                               DWORD style, DWORD exStyle) :
+    window(w),
+    screen(s),
+    requestedGeometryIn(geometryIn),
+    requestedGeometry(geometry),
+    obtainedPos(geometryIn.topLeft()),
+    obtainedSize(geometryIn.size()),
+    margins(QWindowsGeometryHint::frame(w, geometry, style, exStyle))
+{
+    // Geometry of toplevels does not consider window frames.
+    // TODO: No concept of WA_wasMoved yet that would indicate a
+    // CW_USEDEFAULT unless set. For now, assume that 0,0 means 'default'
+    // for toplevels.
+
+    if (!(w->flags() & Qt::FramelessWindowHint))
+        customMargins = cm;
+
+    if (geometry.isValid()
+        || !qt_window_private(const_cast<QWindow *>(w))->resizeAutomatic) {
+        frameX = geometry.x();
+        frameY = geometry.y();
+        const QMargins effectiveMargins = margins + customMargins;
+        frameWidth = effectiveMargins.left() + geometry.width() + effectiveMargins.right();
+        frameHeight = effectiveMargins.top() + geometry.height() + effectiveMargins.bottom();
+        if (QWindowsMenuBar::menuBarOf(w) != nullptr) {
+            menuHeight = GetSystemMetrics(SM_CYMENU);
+            frameHeight += menuHeight;
+        }
+        const bool isDefaultPosition = !frameX && !frameY && w->isTopLevel();
+        if (!QWindowsGeometryHint::positionIncludesFrame(w) && !isDefaultPosition) {
+            frameX -= effectiveMargins.left();
+            frameY -= effectiveMargins.top();
+        }
+    }
+
+    qCDebug(lcQpaWindow).nospace()
+        << __FUNCTION__ << ' ' << w << ' ' << geometry
+        << " pos incl. frame=" << QWindowsGeometryHint::positionIncludesFrame(w)
+        << " frame=" << frameWidth << 'x' << frameHeight << '+'
+        << frameX << '+' << frameY
+        << " margins=" << margins << " custom margins=" << customMargins;
+}
+
+void QWindowCreationContext::applyToMinMaxInfo(MINMAXINFO *mmi) const
+{
+    QWindowsGeometryHint::applyToMinMaxInfo(window, screen, margins + customMargins, mmi);
+}
+
+/*!
+    \class QWindowsWindow
+    \brief Raster or OpenGL Window.
+
+    \list
+    \li Raster type: handleWmPaint() is implemented to
+       to bitblt the image. The DC can be accessed
+       via getDC/releaseDC, which has special handling
+       when within a paint event (in that case, the DC obtained
+       from BeginPaint() is returned).
+
+    \li Open GL: The first time QWindowsGLContext accesses
+       the handle, it sets up the pixelformat on the DC
+       which in turn sets it on the window (see flag
+       PixelFormatInitialized).
+       handleWmPaint() is empty (although required).
+    \endlist
+
+    \internal
+*/
+
+const char *QWindowsWindow::embeddedNativeParentHandleProperty = "_q_embedded_native_parent_handle";
+const char *QWindowsWindow::hasBorderInFullScreenProperty = "_q_has_border_in_fullscreen";
+bool QWindowsWindow::m_borderInFullScreenDefault = false;
+bool QWindowsWindow::m_inSetgeometry = false;
+
+QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data) :
+    QWindowsBaseWindow(aWindow),
+    m_data(data),
+    m_cursor(new CursorHandle)
+#if QT_CONFIG(vulkan)
+  , m_vkSurface(VK_NULL_HANDLE)
+#endif
+{
+    QWindowsContext::instance()->addWindow(m_data.hwnd, this);
+    const Qt::WindowType type = aWindow->type();
+    if (type == Qt::Desktop)
+        return; // No further handling for Qt::Desktop
+    if (aWindow->surfaceType() == QWindow::Direct3DSurface)
+        setFlag(Direct3DSurface);
+#if QT_CONFIG(opengl)
+    if (aWindow->surfaceType() == QWindow::OpenGLSurface)
+        setFlag(OpenGLSurface);
+#endif
+#if QT_CONFIG(vulkan)
+    if (aWindow->surfaceType() == QSurface::VulkanSurface)
+        setFlag(VulkanSurface);
+#endif
+    updateDropSite(window()->isTopLevel());
+
+    // Register touch unless if the flags are already set by a hook
+    // such as HCBT_CREATEWND
+    if (!touchWindowTouchTypes_sys().has_value())
+        registerTouchWindow();
+
+    const qreal opacity = qt_window_private(aWindow)->opacity;
+    if (!qFuzzyCompare(opacity, qreal(1.0)))
+        setOpacity(opacity);
+
+    setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
+
+    if (aWindow->isTopLevel())
+        setWindowIcon(aWindow->icon());
+    if (m_borderInFullScreenDefault || aWindow->property(hasBorderInFullScreenProperty).toBool())
+        setFlag(HasBorderInFullScreen);
+    clearFlag(WithinCreate);
+}
+
+QWindowsWindow::~QWindowsWindow()
+{
+    setFlag(WithinDestroy);
+    QWindowsThemeCache::clearThemeCache(m_data.hwnd);
+    if (testFlag(TouchRegistered))
+        UnregisterTouchWindow(m_data.hwnd);
+    destroyWindow();
+    destroyIcon();
+}
+
+void QWindowsWindow::initialize()
+{
+    // Clear the creation context as the window can be found in QWindowsContext's map.
+    QWindowCreationContextPtr creationContext =
+        QWindowsContext::instance()->setWindowCreationContext(QWindowCreationContextPtr());
+
+    QWindow *w = window();
+    setWindowState(w->windowStates());
+
+    // Trigger geometry change (unless it has a special state in which case setWindowState()
+    // will send the message) and screen change signals of QWindow.
+    if (w->type() != Qt::Desktop) {
+        const Qt::WindowState state = w->windowState();
+        const QRect obtainedGeometry(creationContext->obtainedPos, creationContext->obtainedSize);
+        QPlatformScreen *obtainedScreen = screenForGeometry(obtainedGeometry);
+        if (obtainedScreen && screen() != obtainedScreen)
+            QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(w, obtainedScreen->screen());
+        if (state != Qt::WindowMaximized && state != Qt::WindowFullScreen
+            && creationContext->requestedGeometryIn != obtainedGeometry) {
+            QWindowSystemInterface::handleGeometryChange<QWindowSystemInterface::SynchronousDelivery>(w, obtainedGeometry);
+        }
+    }
+    QWindowsWindow::setSavedDpi(GetDpiForWindow(handle()));
+}
+
+QSurfaceFormat QWindowsWindow::format() const
+{
+    return window()->requestedFormat();
+}
+
+void QWindowsWindow::fireExpose(const QRegion &region, bool force)
+{
+    if (region.isEmpty() && !force)
+        clearFlag(Exposed);
+    else
+        setFlag(Exposed);
+    QWindowSystemInterface::handleExposeEvent(window(), region);
+}
+
+void QWindowsWindow::fireFullExpose(bool force)
+{
+    fireExpose(QRect(QPoint(0, 0), m_data.geometry.size()), force);
+}
+
+void QWindowsWindow::destroyWindow()
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << this << window() << m_data.hwnd;
+    if (m_data.hwnd) { // Stop event dispatching before Window is destroyed.
+        setFlag(WithinDestroy);
+        // Clear any transient child relationships as Windows will otherwise destroy them (QTBUG-35499, QTBUG-36666)
+        const auto tlw = QGuiApplication::topLevelWindows();
+        for (QWindow *w : tlw) {
+            if (w->transientParent() == window()) {
+                if (QWindowsWindow *tw = QWindowsWindow::windowsWindowOf(w))
+                    tw->updateTransientParent();
+            }
+        }
+        QWindowsContext *context = QWindowsContext::instance();
+        if (context->windowUnderMouse() == window())
+            context->clearWindowUnderMouse();
+        if (hasMouseCapture())
+            setMouseGrabEnabled(false);
+        setDropSiteEnabled(false);
+#if QT_CONFIG(vulkan)
+        if (m_vkSurface) {
+            QVulkanInstance *inst = window()->vulkanInstance();
+            if (inst)
+                static_cast<QWindowsVulkanInstance *>(inst->handle())->destroySurface(m_vkSurface);
+            m_vkSurface = VK_NULL_HANDLE;
+        }
+#endif
+#ifndef QT_NO_OPENGL
+        if (m_surface) {
+            if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext())
+                staticOpenGLContext->destroyWindowSurface(m_surface);
+            m_surface = nullptr;
+        }
+#endif
+        DestroyWindow(m_data.hwnd);
+        context->removeWindow(m_data.hwnd);
+        m_data.hwnd = nullptr;
+    }
+}
+
+void QWindowsWindow::updateDropSite(bool topLevel)
+{
+    bool enabled = false;
+    bool parentIsEmbedded = false;
+
+    if (!topLevel) {
+        // if the parent window is a foreign window wrapped via QWindow::fromWinId, we need to enable the drop site
+        // on the first child window
+        const QWindow *parent = window()->parent();
+        if (parent && parent->handle() && parent->handle()->isForeignWindow())
+            parentIsEmbedded = true;
+    }
+
+    if (topLevel || parentIsEmbedded) {
+        switch (window()->type()) {
+        case Qt::Window:
+        case Qt::Dialog:
+        case Qt::Sheet:
+        case Qt::Drawer:
+        case Qt::Popup:
+        case Qt::Tool:
+            enabled = true;
+            break;
+        default:
+            break;
+        }
+    }
+    setDropSiteEnabled(enabled);
+}
+
+void QWindowsWindow::setDropSiteEnabled(bool dropEnabled)
+{
+    if (isDropSiteEnabled() == dropEnabled)
+        return;
+    qCDebug(lcQpaMime) << __FUNCTION__ << window() << dropEnabled;
+#if QT_CONFIG(clipboard) && QT_CONFIG(draganddrop)
+    if (dropEnabled) {
+        Q_ASSERT(m_data.hwnd);
+        m_dropTarget = new QWindowsOleDropTarget(window());
+        RegisterDragDrop(m_data.hwnd, m_dropTarget);
+        CoLockObjectExternal(m_dropTarget, true, true);
+    } else {
+        CoLockObjectExternal(m_dropTarget, false, true);
+        m_dropTarget->Release();
+        RevokeDragDrop(m_data.hwnd);
+        m_dropTarget = nullptr;
+    }
+#endif // QT_CONFIG(clipboard) && QT_CONFIG(draganddrop)
+}
+
+bool QWindowsWindow::m_screenForGLInitialized = false;
+
+void QWindowsWindow::displayChanged()
+{
+    m_screenForGLInitialized = false;
+}
+
+void QWindowsWindow::settingsChanged()
+{
+    m_screenForGLInitialized = false;
+}
+
+QScreen *QWindowsWindow::forcedScreenForGLWindow(const QWindow *w)
+{
+    static QString forceToScreen;
+    if (!m_screenForGLInitialized) {
+        forceToScreen = GpuDescription::detect().gpuSuitableScreen;
+        m_screenForGLInitialized = true;
+    }
+    return forceToScreen.isEmpty() ? nullptr : screenForDeviceName(w, forceToScreen);
+}
+
+// Returns topmost QWindowsWindow ancestor even if there are embedded windows in the chain.
+// Returns this window if it is the topmost ancestor.
+QWindow *QWindowsWindow::topLevelOf(QWindow *w)
+{
+    while (QWindow *parent = w->parent())
+        w = parent;
+
+    if (const QPlatformWindow *handle = w->handle()) {
+        const auto *ww = static_cast<const QWindowsWindow *>(handle);
+        if (ww->isEmbedded()) {
+            HWND parentHWND = GetAncestor(ww->handle(), GA_PARENT);
+            const HWND desktopHwnd = GetDesktopWindow();
+            const QWindowsContext *ctx = QWindowsContext::instance();
+            while (parentHWND && parentHWND != desktopHwnd) {
+                if (QWindowsWindow *ancestor = ctx->findPlatformWindow(parentHWND))
+                    return topLevelOf(ancestor->window());
+                parentHWND = GetAncestor(parentHWND, GA_PARENT);
+            }
+        }
+    }
+    return w;
+}
+
+QWindowsWindowData
+    QWindowsWindowData::create(const QWindow *w,
+                                       const QWindowsWindowData &parameters,
+                                       const QString &title)
+{
+    WindowCreationData creationData;
+    creationData.fromWindow(w, parameters.flags);
+    QWindowsWindowData result = creationData.create(w, parameters, title);
+    // Force WM_NCCALCSIZE (with wParam=1) via SWP_FRAMECHANGED for custom margin.
+    creationData.initialize(w, result.hwnd, !parameters.customMargins.isNull(), 1);
+    return result;
+}
+
+void QWindowsWindow::setVisible(bool visible)
+{
+    const QWindow *win = window();
+    qCDebug(lcQpaWindow) << __FUNCTION__ << this << win << m_data.hwnd << visible;
+    if (m_data.hwnd) {
+        if (visible) {
+            show_sys();
+
+            // When the window is layered, we won't get WM_PAINT, and "we" are in control
+            // over the rendering of the window
+            // There is nobody waiting for this, so we don't need to flush afterwards.
+            if (isLayered())
+                fireFullExpose();
+            // QTBUG-44928, QTBUG-7386: This is to resolve the problem where popups are
+            // opened from the system tray and not being implicitly activated
+
+            if (win->type() == Qt::Popup && !win->parent() && !QGuiApplication::focusWindow())
+                SetForegroundWindow(m_data.hwnd);
+        } else {
+            if (hasMouseCapture())
+                setMouseGrabEnabled(false);
+            if (window()->flags() & Qt::Popup) // from QWidgetPrivate::hide_sys(), activate other
+                ShowWindow(m_data.hwnd, SW_HIDE);
+            else
+                hide_sys();
+            fireExpose(QRegion());
+        }
+    }
+}
+
+bool QWindowsWindow::isVisible() const
+{
+    return m_data.hwnd && IsWindowVisible(m_data.hwnd);
+}
+
+bool QWindowsWindow::isActive() const
+{
+    // Check for native windows or children of the active native window.
+    if (const HWND activeHwnd = GetForegroundWindow())
+        if (m_data.hwnd == activeHwnd || IsChild(activeHwnd, m_data.hwnd))
+            return true;
+    return false;
+}
+
+bool QWindowsWindow::isAncestorOf(const QPlatformWindow *child) const
+{
+    const auto *childWindow = static_cast<const QWindowsWindow *>(child);
+    return IsChild(m_data.hwnd, childWindow->handle());
+}
+
+bool QWindowsWindow::isEmbedded() const
+{
+    return m_data.embedded;
+}
+
+QPoint QWindowsWindow::mapToGlobal(const QPoint &pos) const
+{
+    return m_data.hwnd ? QWindowsGeometryHint::mapToGlobal(m_data.hwnd, pos) : pos;
+}
+
+QPoint QWindowsWindow::mapFromGlobal(const QPoint &pos) const
+{
+    return m_data.hwnd ? QWindowsGeometryHint::mapFromGlobal(m_data.hwnd, pos) : pos;
+}
+
+// Update the transient parent for a toplevel window. The concept does not
+// really exist on Windows, the relationship is set by passing a parent along with !WS_CHILD
+// to window creation or by setting the parent using  GWL_HWNDPARENT (as opposed to
+// SetParent, which would make it a real child).
+
+#ifndef GWL_HWNDPARENT
+#    define GWL_HWNDPARENT (-8)
+#endif
+
+void QWindowsWindow::updateTransientParent() const
+{
+    if (window()->type() == Qt::Popup)
+        return; // QTBUG-34503, // a popup stays on top, no parent, see also WindowCreationData::fromWindow().
+    // Update transient parent.
+    const HWND oldTransientParent = GetWindow(m_data.hwnd, GW_OWNER);
+    HWND newTransientParent = nullptr;
+    if (const QWindow *tp = window()->transientParent())
+        if (const QWindowsWindow *tw = QWindowsWindow::windowsWindowOf(tp))
+            if (!tw->testFlag(WithinDestroy)) // Prevent destruction by parent window (QTBUG-35499, QTBUG-36666)
+                newTransientParent = tw->handle();
+
+    // QTSOLBUG-71: When using the MFC/winmigrate solution, it is possible that a child
+    // window is found, which can cause issues with modality. Loop up to top level.
+    while (newTransientParent && (GetWindowLongPtr(newTransientParent, GWL_STYLE) & WS_CHILD) != 0)
+        newTransientParent = GetParent(newTransientParent);
+
+    if (newTransientParent != oldTransientParent)
+        SetWindowLongPtr(m_data.hwnd, GWL_HWNDPARENT, LONG_PTR(newTransientParent));
+}
+
+static inline bool testShowWithoutActivating(const QWindow *window)
+{
+    // QWidget-attribute Qt::WA_ShowWithoutActivating .
+    const QVariant showWithoutActivating = window->property("_q_showWithoutActivating");
+    return showWithoutActivating.isValid() && showWithoutActivating.toBool();
+}
+
+static void setMinimizedGeometry(HWND hwnd, const QRect &r)
+{
+    WINDOWPLACEMENT windowPlacement;
+    windowPlacement.length = sizeof(WINDOWPLACEMENT);
+    if (GetWindowPlacement(hwnd, &windowPlacement)) {
+        windowPlacement.showCmd = SW_SHOWMINIMIZED;
+        windowPlacement.rcNormalPosition = RECTfromQRect(r);
+        SetWindowPlacement(hwnd, &windowPlacement);
+    }
+}
+
+static void setRestoreMaximizedFlag(HWND hwnd, bool set = true)
+{
+    // Let Windows know that we need to restore as maximized
+    WINDOWPLACEMENT windowPlacement;
+    windowPlacement.length = sizeof(WINDOWPLACEMENT);
+    if (GetWindowPlacement(hwnd, &windowPlacement)) {
+        if (set)
+            windowPlacement.flags |= WPF_RESTORETOMAXIMIZED;
+        else
+            windowPlacement.flags &= ~WPF_RESTORETOMAXIMIZED;
+        SetWindowPlacement(hwnd, &windowPlacement);
+    }
+}
+
+// partially from QWidgetPrivate::show_sys()
+void QWindowsWindow::show_sys() const
+{
+    int sm = SW_SHOWNORMAL;
+    bool fakedMaximize = false;
+    bool restoreMaximize = false;
+    const QWindow *w = window();
+    const Qt::WindowFlags flags = w->flags();
+    const Qt::WindowType type = w->type();
+    if (w->isTopLevel()) {
+        const Qt::WindowStates state = w->windowStates();
+        if (state & Qt::WindowMinimized) {
+            sm = SW_SHOWMINIMIZED;
+            if (!isVisible())
+                sm = SW_SHOWMINNOACTIVE;
+            if (state & Qt::WindowMaximized)
+                restoreMaximize = true;
+        } else {
+            updateTransientParent();
+            if (state & Qt::WindowMaximized) {
+                sm = SW_SHOWMAXIMIZED;
+                // Windows will not behave correctly when we try to maximize a window which does not
+                // have minimize nor maximize buttons in the window frame. Windows would then ignore
+                // non-available geometry, and rather maximize the widget to the full screen, minus the
+                // window frame (caption). So, we do a trick here, by adding a maximize button before
+                // maximizing the widget, and then remove the maximize button afterwards.
+                if (flags & Qt::WindowTitleHint &&
+                        !(flags & (Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint))) {
+                    fakedMaximize = TRUE;
+                    setStyle(style() | WS_MAXIMIZEBOX);
+                }
+            } // Qt::WindowMaximized
+        } // !Qt::WindowMinimized
+    }
+    if (type == Qt::Popup || type == Qt::ToolTip || type == Qt::Tool || testShowWithoutActivating(w))
+        sm = SW_SHOWNOACTIVATE;
+
+    if (w->windowStates() & Qt::WindowMaximized)
+        setFlag(WithinMaximize); // QTBUG-8361
+
+    ShowWindow(m_data.hwnd, sm);
+
+    clearFlag(WithinMaximize);
+
+    if (fakedMaximize) {
+        setStyle(style() & ~WS_MAXIMIZEBOX);
+        SetWindowPos(m_data.hwnd, nullptr, 0, 0, 0, 0,
+                     SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER
+                     | SWP_FRAMECHANGED);
+    }
+    if (restoreMaximize)
+        setRestoreMaximizedFlag(m_data.hwnd);
+}
+
+void QWindowsWindow::setParent(const QPlatformWindow *newParent)
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << window() << newParent;
+
+    if (m_data.hwnd)
+        setParent_sys(newParent);
+}
+
+void QWindowsWindow::setParent_sys(const QPlatformWindow *parent)
+{
+    // Use GetAncestor instead of GetParent, as GetParent can return owner window for toplevels
+    HWND oldParentHWND = parentHwnd();
+    HWND newParentHWND = nullptr;
+    if (parent) {
+        const auto *parentW = static_cast<const QWindowsWindow *>(parent);
+        newParentHWND = parentW->handle();
+
+    }
+
+    // NULL handle means desktop window, which also has its proper handle -> disambiguate
+    HWND desktopHwnd = GetDesktopWindow();
+    if (oldParentHWND == desktopHwnd)
+        oldParentHWND = nullptr;
+    if (newParentHWND == desktopHwnd)
+        newParentHWND = nullptr;
+
+    if (newParentHWND != oldParentHWND) {
+        const bool wasTopLevel = oldParentHWND == nullptr;
+        const bool isTopLevel = newParentHWND == nullptr;
+
+        setFlag(WithinSetParent);
+        SetParent(m_data.hwnd, newParentHWND);
+        clearFlag(WithinSetParent);
+
+        // WS_CHILD/WS_POPUP must be manually set/cleared in addition
+        // to dialog frames, etc (see  SetParent() ) if the top level state changes.
+        // Force toplevel state as QWindow::isTopLevel cannot be relied upon here.
+        if (wasTopLevel != isTopLevel) {
+            setDropSiteEnabled(false);
+            setWindowFlags_sys(window()->flags(), unsigned(isTopLevel ? WindowCreationData::ForceTopLevel : WindowCreationData::ForceChild));
+            updateDropSite(isTopLevel);
+        }
+    }
+}
+
+void QWindowsWindow::handleHidden()
+{
+    fireExpose(QRegion());
+}
+
+void QWindowsWindow::handleCompositionSettingsChanged()
+{
+    const QWindow *w = window();
+    if ((w->surfaceType() == QWindow::OpenGLSurface
+         || w->surfaceType() == QWindow::VulkanSurface
+         || w->surfaceType() == QWindow::Direct3DSurface)
+        && w->format().hasAlpha())
+    {
+        applyBlurBehindWindow(handle());
+    }
+}
+
+qreal QWindowsWindow::dpiRelativeScale(const UINT dpi) const
+{
+    return QHighDpiScaling::roundScaleFactor(qreal(dpi) / QWindowsScreen::baseDpi) /
+           QHighDpiScaling::roundScaleFactor(qreal(savedDpi()) / QWindowsScreen::baseDpi);
+}
+
+void QWindowsWindow::handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT *result)
+{
+    // We want to keep QWindow's device independent size constant across the
+    // DPI change. To accomplish this, scale QPlatformWindow's native size
+    // by the change of DPI (e.g. 120 -> 144 = 1.2), also taking any scale
+    // factor rounding into account. The win32 window size includes the margins;
+    // add the margins for the new DPI to the window size.
+    const UINT dpi = UINT(wParam);
+    const qreal scale = dpiRelativeScale(dpi);
+    const QMargins margins = fullFrameMargins();
+    if (!(m_data.flags & Qt::FramelessWindowHint)) {
+        // We need to update the custom margins to match the current DPI, because
+        // we don't want our users manually hook into this message just to set a
+        // new margin, but here we can't call setCustomMargins() directly, that
+        // function will change the window geometry which conflicts with what we
+        // are currently doing.
+        m_data.customMargins *= scale;
+    }
+
+    const QSize windowSize = (geometry().size() * scale).grownBy((margins * scale) + customMargins());
+    SIZE *size = reinterpret_cast<SIZE *>(lParam);
+    size->cx = windowSize.width();
+    size->cy = windowSize.height();
+    *result = true; // Inform Windows that we've set a size
+}
+
+void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam)
+{
+    const UINT dpi = HIWORD(wParam);
+    const qreal scale = dpiRelativeScale(dpi);
+    setSavedDpi(dpi);
+
+    QWindowsThemeCache::clearThemeCache(hwnd);
+
+    // Send screen change first, so that the new screen is set during any following resize
+    checkForScreenChanged(QWindowsWindow::FromDpiChange);
+
+    if (!IsZoomed(hwnd))
+        m_data.restoreGeometry.setSize(m_data.restoreGeometry.size() * scale);
+
+    // We get WM_DPICHANGED in one of two situations:
+    //
+    // 1. The DPI change is a "spontaneous" DPI change as a result of e.g.
+    // the user dragging the window to a new screen. In this case Windows
+    // first sends WM_GETDPISCALEDSIZE, where we set the new window size,
+    // followed by this event where we apply the suggested window geometry
+    // to the native window. This will make sure the window tracks the mouse
+    // cursor during screen change, and also that the window size is scaled
+    // according to the DPI change.
+    //
+    // 2. The DPI change is a result of a setGeometry() call. In this case
+    // Qt has already scaled the window size for the new DPI. Further, Windows
+    // does not call WM_GETDPISCALEDSIZE, and also applies its own scaling
+    // to the already scaled window size. Since there is no need to set the
+    // window geometry again, and the provided geometry is incorrect, we omit
+    // making the SetWindowPos() call.
+    if (!m_inSetgeometry) {
+        updateFullFrameMargins();
+        const auto prcNewWindow = reinterpret_cast<RECT *>(lParam);
+        SetWindowPos(hwnd, nullptr, prcNewWindow->left, prcNewWindow->top,
+                     prcNewWindow->right - prcNewWindow->left,
+                     prcNewWindow->bottom - prcNewWindow->top, SWP_NOZORDER | SWP_NOACTIVATE);
+        // If the window does not have a frame, WM_MOVE and WM_SIZE won't be
+        // called which prevents the content from being scaled appropriately
+        // after a DPI change.
+        if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd))
+            handleGeometryChange();
+    }
+
+    // Re-apply mask now that we have a new DPI, which have resulted in
+    // a new scale factor.
+    setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
+}
+
+void QWindowsWindow::handleDpiChangedAfterParent(HWND hwnd)
+{
+    const UINT dpi = GetDpiForWindow(hwnd);
+    const qreal scale = dpiRelativeScale(dpi);
+    setSavedDpi(dpi);
+
+    checkForScreenChanged(QWindowsWindow::FromDpiChange);
+
+    // Child windows do not get WM_GETDPISCALEDSIZE messages to inform
+    // Windows about the new size, so we need to manually scale them.
+    QRect currentGeometry = geometry();
+    QRect scaledGeometry = QRect(currentGeometry.topLeft() * scale, currentGeometry.size() * scale);
+    setGeometry(scaledGeometry);
+}
+
+static QRect normalFrameGeometry(HWND hwnd)
+{
+    WINDOWPLACEMENT wp;
+    wp.length = sizeof(WINDOWPLACEMENT);
+    if (GetWindowPlacement(hwnd, &wp)) {
+        const QRect result = qrectFromRECT(wp.rcNormalPosition);
+        return result.translated(windowPlacementOffset(hwnd, result.topLeft()));
+    }
+    return QRect();
+}
+
+QRect QWindowsWindow::normalGeometry() const
+{
+    // Check for fake 'fullscreen' mode.
+    const bool fakeFullScreen =
+        m_savedFrameGeometry.isValid() && (window()->windowStates() & Qt::WindowFullScreen);
+    const QRect frame = fakeFullScreen ? m_savedFrameGeometry : normalFrameGeometry(m_data.hwnd);
+    const QMargins margins = fakeFullScreen
+        ? QWindowsGeometryHint::frame(window(), handle(), m_savedStyle, 0)
+        : fullFrameMargins();
+    return frame.isValid() ? frame.marginsRemoved(margins) : frame;
+}
+
+static QString msgUnableToSetGeometry(const QWindowsWindow *platformWindow,
+                                      const QRect &requestedRect,
+                                      const QRect &obtainedRect,
+                                      const QMargins &fullMargins,
+                                      const QMargins &customMargins)
+{
+    QString result;
+    QDebug debug(&result);
+    debug.nospace();
+    debug.noquote();
+    const auto window = platformWindow->window();
+    debug << "Unable to set geometry ";
+    formatBriefRectangle(debug, requestedRect);
+    debug << " (frame: ";
+    formatBriefRectangle(debug, requestedRect + fullMargins);
+    debug << ") on " << window->metaObject()->className() << "/\""
+          << window->objectName() << "\" on \"" << window->screen()->name()
+          << "\". Resulting geometry: ";
+    formatBriefRectangle(debug, obtainedRect);
+    debug << " (frame: ";
+    formatBriefRectangle(debug, obtainedRect + fullMargins);
+    debug << ") margins: ";
+    formatBriefMargins(debug, fullMargins);
+    if (!customMargins.isNull()) {
+       debug << " custom margin: ";
+       formatBriefMargins(debug, customMargins);
+    }
+    const auto minimumSize = window->minimumSize();
+    const bool hasMinimumSize = !minimumSize.isEmpty();
+    if (hasMinimumSize)
+        debug << " minimum size: " << minimumSize.width() << 'x' << minimumSize.height();
+    const auto maximumSize = window->maximumSize();
+    const bool hasMaximumSize = maximumSize.width() != QWINDOWSIZE_MAX || maximumSize.height() != QWINDOWSIZE_MAX;
+    if (hasMaximumSize)
+        debug << " maximum size: " << maximumSize.width() << 'x' << maximumSize.height();
+    if (hasMinimumSize || hasMaximumSize) {
+        MINMAXINFO minmaxInfo;
+        memset(&minmaxInfo, 0, sizeof(minmaxInfo));
+        platformWindow->getSizeHints(&minmaxInfo);
+        debug << ' ' << minmaxInfo;
+    }
+    debug << ')';
+    return result;
+}
+
+void QWindowsWindow::setGeometry(const QRect &rectIn)
+{
+    QBoolBlocker b(m_inSetgeometry);
+
+    QRect rect = rectIn;
+    // This means it is a call from QWindow::setFramePosition() and
+    // the coordinates include the frame (size is still the contents rectangle).
+    if (QWindowsGeometryHint::positionIncludesFrame(window())) {
+        const QMargins margins = frameMargins();
+        rect.moveTopLeft(rect.topLeft() + QPoint(margins.left(), margins.top()));
+    }
+
+    if (m_windowState & Qt::WindowMinimized)
+        m_data.geometry = rect; // Otherwise set by handleGeometryChange() triggered by event.
+    else
+        setWindowState(Qt::WindowNoState);// Update window state to WindowNoState unless minimized
+
+    if (m_data.hwnd) {
+        // A ResizeEvent with resulting geometry will be sent. If we cannot
+        // achieve that size (for example, window title minimal constraint),
+        // notify and warn.
+        setFlag(WithinSetGeometry);
+        setGeometry_sys(rect);
+        clearFlag(WithinSetGeometry);
+        if (m_data.geometry != rect && (isVisible() || QLibraryInfo::isDebugBuild())) {
+            const auto warning =
+                msgUnableToSetGeometry(this, rectIn, m_data.geometry,
+                                       fullFrameMargins(), customMargins());
+            qWarning("%s: %s", __FUNCTION__, qPrintable(warning));
+        }
+    } else {
+        QPlatformWindow::setGeometry(rect);
+    }
+}
+
+void QWindowsWindow::handleMoved()
+{
+    // Minimize/Set parent can send nonsensical move events.
+    if (!IsIconic(m_data.hwnd) && !testFlag(WithinSetParent))
+        handleGeometryChange();
+}
+
+void QWindowsWindow::handleResized(int wParam, LPARAM lParam)
+{
+    /* Prevents borderless windows from covering the taskbar when maximized. */
+    if ((m_data.flags.testFlag(Qt::FramelessWindowHint)
+         || (m_data.flags.testFlag(Qt::CustomizeWindowHint) && !m_data.flags.testFlag(Qt::WindowTitleHint)))
+        && IsZoomed(m_data.hwnd)) {
+        const int resizedWidth = LOWORD(lParam);
+        const int resizedHeight = HIWORD(lParam);
+
+        const HMONITOR monitor = MonitorFromWindow(m_data.hwnd, MONITOR_DEFAULTTOPRIMARY);
+        MONITORINFO monitorInfo = {};
+        monitorInfo.cbSize = sizeof(MONITORINFO);
+        GetMonitorInfoW(monitor, &monitorInfo);
+
+        int correctLeft = monitorInfo.rcMonitor.left;
+        int correctTop = monitorInfo.rcMonitor.top;
+        int correctWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
+        int correctHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
+
+        if (!m_data.flags.testFlag(Qt::FramelessWindowHint)) {
+            const int borderWidth = invisibleMargins(m_data.hwnd).left();
+            correctLeft -= borderWidth;
+            correctTop -= borderWidth;
+            correctWidth += borderWidth * 2;
+            correctHeight += borderWidth * 2;
+        }
+
+        if (resizedWidth != correctWidth || resizedHeight != correctHeight) {
+            qCDebug(lcQpaWindow) << __FUNCTION__ << "correcting: " << resizedWidth << "x"
+                                 << resizedHeight << " -> " << correctWidth << "x" << correctHeight;
+            SetWindowPos(m_data.hwnd, nullptr, correctLeft, correctTop, correctWidth, correctHeight,
+                         SWP_NOZORDER | SWP_NOACTIVATE);
+        }
+    }
+
+    switch (wParam) {
+    case SIZE_MAXHIDE: // Some other window affected.
+    case SIZE_MAXSHOW:
+        return;
+    case SIZE_MINIMIZED: // QTBUG-53577, prevent state change events during programmatic state change
+        if (!testFlag(WithinSetStyle) && !testFlag(WithinSetGeometry))
+            handleWindowStateChange(m_windowState | Qt::WindowMinimized);
+        return;
+    case SIZE_MAXIMIZED:
+        handleGeometryChange();
+        if (!testFlag(WithinSetStyle) && !testFlag(WithinSetGeometry))
+            handleWindowStateChange(Qt::WindowMaximized | (isFullScreen_sys() ? Qt::WindowFullScreen
+                                                                              : Qt::WindowNoState));
+        break;
+    case SIZE_RESTORED:
+        handleGeometryChange();
+        if (!testFlag(WithinSetStyle) && !testFlag(WithinSetGeometry)) {
+            if (isFullScreen_sys())
+                handleWindowStateChange(
+                    Qt::WindowFullScreen
+                    | (testFlag(MaximizeToFullScreen) ? Qt::WindowMaximized : Qt::WindowNoState));
+            else if (m_windowState != Qt::WindowNoState && !testFlag(MaximizeToFullScreen))
+                handleWindowStateChange(Qt::WindowNoState);
+        }
+        break;
+    }
+}
+
+static inline bool equalDpi(const QDpi &d1, const QDpi &d2)
+{
+    return qFuzzyCompare(d1.first, d2.first) && qFuzzyCompare(d1.second, d2.second);
+}
+
+void QWindowsWindow::checkForScreenChanged(ScreenChangeMode mode)
+{
+    if ((parent() && !parent()->isForeignWindow()) || QWindowsScreenManager::isSingleScreen())
+        return;
+
+    QPlatformScreen *currentScreen = screen();
+    auto topLevel = isTopLevel_sys() ? m_data.hwnd : GetAncestor(m_data.hwnd, GA_ROOT);
+    const QWindowsScreen *newScreen =
+        QWindowsContext::instance()->screenManager().screenForHwnd(topLevel);
+
+    if (newScreen == nullptr || newScreen == currentScreen)
+        return;
+    // For screens with different DPI: postpone until WM_DPICHANGE
+    // Check on currentScreen as it can be 0 when resuming a session (QTBUG-80436).
+    const bool changingDpi = !equalDpi(QDpi(savedDpi(), savedDpi()), newScreen->logicalDpi());
+    if (mode == FromGeometryChange && currentScreen != nullptr && changingDpi)
+        return;
+
+    qCDebug(lcQpaWindow).noquote().nospace() << __FUNCTION__
+        << ' ' << window() << " \"" << (currentScreen ? currentScreen->name() : QString())
+        << "\"->\"" << newScreen->name() << '"';
+    updateFullFrameMargins();
+    QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(window(), newScreen->screen());
+}
+
+void QWindowsWindow::handleGeometryChange()
+{
+    const QRect previousGeometry = m_data.geometry;
+    m_data.geometry = geometry_sys();
+    updateFullFrameMargins();
+    QWindowSystemInterface::handleGeometryChange(window(), m_data.geometry);
+    // QTBUG-32121: OpenGL/normal windows (with exception of ANGLE
+    // which we no longer support in Qt 6) do not receive expose
+    // events when shrinking, synthesize.
+    if (isExposed()
+        && m_data.geometry.size() != previousGeometry.size() // Exclude plain move
+        // One dimension grew -> Windows will send expose, no need to synthesize.
+        && !(m_data.geometry.width() > previousGeometry.width() || m_data.geometry.height() > previousGeometry.height())) {
+        fireFullExpose(true);
+    }
+
+    const bool wasSync = testFlag(SynchronousGeometryChangeEvent);
+    checkForScreenChanged();
+
+    if (testFlag(SynchronousGeometryChangeEvent))
+        QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
+
+    if (!testFlag(ResizeMoveActive))
+        updateRestoreGeometry();
+
+    if (!wasSync)
+        clearFlag(SynchronousGeometryChangeEvent);
+    qCDebug(lcQpaEvents) << __FUNCTION__ << this << window() << m_data.geometry;
+}
+
+void QWindowsBaseWindow::setGeometry_sys(const QRect &rect) const
+{
+    const QMargins margins = fullFrameMargins();
+    const QRect frameGeometry = rect + margins;
+
+    qCDebug(lcQpaWindow) << '>' << __FUNCTION__ << window()
+        << "\n from " << geometry_sys() << " frame: "
+        << margins << " to " <<rect
+        << " new frame: " << frameGeometry;
+
+    bool result = false;
+    const HWND hwnd = handle();
+    WINDOWPLACEMENT windowPlacement;
+    windowPlacement.length = sizeof(WINDOWPLACEMENT);
+    GetWindowPlacement(hwnd, &windowPlacement);
+    // If the window is hidden and in maximized state or minimized, instead of moving the
+    // window, set the normal position of the window.
+    if ((windowPlacement.showCmd == SW_MAXIMIZE && !IsWindowVisible(hwnd))
+        || windowPlacement.showCmd == SW_SHOWMINIMIZED) {
+        windowPlacement.rcNormalPosition =
+            RECTfromQRect(frameGeometry.translated(-windowPlacementOffset(hwnd, frameGeometry.topLeft())));
+        windowPlacement.showCmd = windowPlacement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWMINIMIZED : SW_HIDE;
+        result = SetWindowPlacement(hwnd, &windowPlacement);
+    } else {
+        int x = frameGeometry.x();
+        if (!window()->isTopLevel()) {
+            const HWND parentHandle = GetParent(hwnd);
+            if (isRtlLayout(parentHandle)) {
+                RECT rect;
+                GetClientRect(parentHandle, &rect);
+                x = rect.right - frameGeometry.width() - x;
+            }
+        }
+        result = MoveWindow(hwnd, x, frameGeometry.y(),
+                            frameGeometry.width(), frameGeometry.height(), true);
+    }
+    qCDebug(lcQpaWindow) << '<' << __FUNCTION__ << window()
+        << "\n resulting " << result << geometry_sys();
+}
+
+/*!
+    Allocates a HDC for the window or returns the temporary one
+    obtained from WinAPI BeginPaint within a WM_PAINT event.
+
+    \sa releaseDC()
+*/
+
+HDC QWindowsWindow::getDC()
+{
+    if (!m_hdc) {
+        m_hdc = GetDC(handle());
+        if (QGuiApplication::layoutDirection() == Qt::RightToLeft)
+            SetLayout(m_hdc, 0); // Clear RTL layout
+    }
+    return m_hdc;
+}
+
+/*!
+    Releases the HDC for the window or does nothing in
+    case it was obtained from WinAPI BeginPaint within a WM_PAINT event.
+
+    \sa getDC()
+*/
+
+void QWindowsWindow::releaseDC()
+{
+    if (m_hdc) {
+        ReleaseDC(handle(), m_hdc);
+        m_hdc = nullptr;
+    }
+}
+
+static inline bool isSoftwareGl()
+{
+#if QT_CONFIG(dynamicgl)
+    return QOpenGLStaticContext::opengl32.moduleIsNotOpengl32()
+        && QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL;
+#else
+    return false;
+#endif // dynamicgl
+}
+
+bool QWindowsWindow::handleWmPaint(HWND hwnd, UINT message,
+                                         WPARAM, LPARAM, LRESULT *result)
+{
+    if (message == WM_ERASEBKGND) { // Backing store - ignored.
+        *result = 1;
+        return true;
+    }
+    // QTBUG-75455: Suppress WM_PAINT sent to invisible windows when setting WS_EX_LAYERED
+    if (!window()->isVisible() && (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_LAYERED) != 0)
+        return false;
+    // Ignore invalid update bounding rectangles
+    if (!GetUpdateRect(m_data.hwnd, 0, FALSE))
+        return false;
+    PAINTSTRUCT ps;
+
+    // GL software rendering (QTBUG-58178) with some AMD cards
+    // (QTBUG-60527) need InvalidateRect() to suppress artifacts while resizing.
+    if (testFlag(OpenGLSurface) && isSoftwareGl())
+        InvalidateRect(hwnd, nullptr, false);
+
+    BeginPaint(hwnd, &ps);
+
+    // If the a window is obscured by another window (such as a child window)
+    // we still need to send isExposed=true, for compatibility.
+    // Our tests depend on it.
+    fireExpose(QRegion(qrectFromRECT(ps.rcPaint)), true);
+    if (!QWindowsContext::instance()->asyncExpose())
+        QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
+
+    EndPaint(hwnd, &ps);
+    return true;
+}
+
+void QWindowsWindow::setWindowTitle(const QString &title)
+{
+    setWindowTitle_sys(QWindowsWindow::formatWindowTitle(title));
+}
+
+void QWindowsWindow::setWindowFlags(Qt::WindowFlags flags)
+{
+    qCDebug(lcQpaWindow) << '>' << __FUNCTION__ << this << window() << "\n    from: "
+        << m_data.flags << "\n    to: " << flags;
+    const QRect oldGeometry = geometry();
+    if (m_data.flags != flags) {
+        m_data.flags = flags;
+        if (m_data.hwnd) {
+            m_data = setWindowFlags_sys(flags);
+            updateDropSite(window()->isTopLevel());
+        }
+    }
+    // When switching to a frameless window, geometry
+    // may change without a WM_MOVE. Report change manually.
+    // Do not send synchronously as not to clobber the widget
+    // geometry in a sequence of setting flags and geometry.
+    const QRect newGeometry = geometry_sys();
+    if (oldGeometry != newGeometry)
+        handleGeometryChange();
+
+    qCDebug(lcQpaWindow) << '<' << __FUNCTION__ << "\n    returns: "
+        << m_data.flags << " geometry " << oldGeometry << "->" << newGeometry;
+}
+
+QWindowsWindowData QWindowsWindow::setWindowFlags_sys(Qt::WindowFlags wt,
+                                                              unsigned flags) const
+{
+    WindowCreationData creationData;
+    creationData.fromWindow(window(), wt, flags);
+    creationData.applyWindowFlags(m_data.hwnd);
+    creationData.initialize(window(), m_data.hwnd, true, m_opacity);
+
+    QWindowsWindowData result = m_data;
+    result.flags = creationData.flags;
+    result.embedded = creationData.embedded;
+    result.hasFrame = (creationData.style & (WS_DLGFRAME | WS_THICKFRAME))
+            && !(creationData.flags & Qt::FramelessWindowHint);
+    return result;
+}
+
+void QWindowsWindow::handleWindowStateChange(Qt::WindowStates state)
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << this << window()
+                 << "\n    from " << m_windowState << " to " << state;
+    m_windowState = state;
+    QWindowSystemInterface::handleWindowStateChanged(window(), state);
+    if (state & Qt::WindowMinimized) {
+        handleHidden();
+        QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); // Tell QQuickWindow to stop rendering now.
+    } else {
+        if (state & Qt::WindowMaximized) {
+            WINDOWPLACEMENT windowPlacement{};
+            windowPlacement.length = sizeof(WINDOWPLACEMENT);
+            GetWindowPlacement(m_data.hwnd, &windowPlacement);
+            const RECT geometry = RECTfromQRect(m_data.restoreGeometry);
+            windowPlacement.rcNormalPosition = geometry;
+            SetWindowPlacement(m_data.hwnd, &windowPlacement);
+        }
+        // QTBUG-17548: We send expose events when receiving WM_Paint, but for
+        // layered windows and transient children, we won't receive any WM_Paint.
+        QWindow *w = window();
+        bool exposeEventsSent = false;
+        if (isLayered()) {
+            fireFullExpose();
+            exposeEventsSent = true;
+        }
+        const QWindowList allWindows = QGuiApplication::allWindows();
+        for (QWindow *child : allWindows) {
+            if (child != w && child->isVisible() && child->transientParent() == w) {
+                QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(child);
+                if (platformWindow && platformWindow->isLayered()) {
+                    platformWindow->fireFullExpose();
+                    exposeEventsSent = true;
+                }
+            }
+        }
+        if (exposeEventsSent && !QWindowsContext::instance()->asyncExpose())
+            QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
+    }
+}
+
+void QWindowsWindow::updateRestoreGeometry()
+{
+    m_data.restoreGeometry = normalFrameGeometry(m_data.hwnd);
+}
+
+void QWindowsWindow::setWindowState(Qt::WindowStates state)
+{
+    if (m_data.hwnd) {
+        setWindowState_sys(state);
+        m_windowState = state;
+    }
+}
+
+bool QWindowsWindow::isFullScreen_sys() const
+{
+    const QWindow *w = window();
+    if (!w->isTopLevel())
+        return false;
+    QRect geometry = geometry_sys();
+    if (testFlag(HasBorderInFullScreen))
+        geometry += QMargins(1, 1, 1, 1);
+    QPlatformScreen *screen = screenForGeometry(geometry);
+    return screen && geometry == screen->geometry();
+}
+
+/*!
+    \brief Change the window state.
+
+    \note Window frames change when maximized;
+    the top margin shrinks somewhat but that cannot be obtained using
+    AdjustWindowRectEx().
+
+    \note Some calls to SetWindowLong require a subsequent call
+    to ShowWindow.
+*/
+
+void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState)
+{
+    const Qt::WindowStates oldState = m_windowState;
+    if (oldState == newState)
+        return;
+    qCDebug(lcQpaWindow) << '>' << __FUNCTION__ << this << window()
+        << " from " << oldState << " to " << newState;
+
+    const bool visible = isVisible();
+    auto stateChange = oldState ^ newState;
+
+    if (stateChange & Qt::WindowFullScreen) {
+        if (newState & Qt::WindowFullScreen) {
+            UINT newStyle = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP;
+            // Save geometry and style to be restored when fullscreen
+            // is turned off again, since on Windows, it is not a real
+            // Window state but emulated by changing geometry and style.
+            if (!m_savedStyle) {
+                m_savedStyle = style();
+                if ((oldState & Qt::WindowMinimized) || (oldState & Qt::WindowMaximized)) {
+                    const QRect nf = normalFrameGeometry(m_data.hwnd);
+                    if (nf.isValid())
+                        m_savedFrameGeometry = nf;
+                } else {
+                    m_savedFrameGeometry = frameGeometry_sys();
+                }
+            }
+            if (newState & Qt::WindowMaximized)
+                setFlag(MaximizeToFullScreen);
+            if (m_savedStyle & WS_SYSMENU)
+                newStyle |= WS_SYSMENU;
+            if (visible)
+                newStyle |= WS_VISIBLE;
+            if (testFlag(HasBorderInFullScreen))
+                newStyle |= WS_BORDER;
+            setStyle(newStyle);
+            const HMONITOR monitor = MonitorFromWindow(m_data.hwnd, MONITOR_DEFAULTTONEAREST);
+            MONITORINFO monitorInfo = {};
+            monitorInfo.cbSize = sizeof(MONITORINFO);
+            GetMonitorInfoW(monitor, &monitorInfo);
+            const QRect screenGeometry(monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.top,
+                                       monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left,
+                                       monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top);
+            if (newState & Qt::WindowMinimized) {
+                setMinimizedGeometry(m_data.hwnd, screenGeometry);
+                if (stateChange & Qt::WindowMaximized)
+                    setRestoreMaximizedFlag(m_data.hwnd, newState & Qt::WindowMaximized);
+            } else {
+                const UINT swpf = SWP_FRAMECHANGED | SWP_NOACTIVATE;
+                const bool wasSync = testFlag(SynchronousGeometryChangeEvent);
+                setFlag(SynchronousGeometryChangeEvent);
+                SetWindowPos(m_data.hwnd, HWND_TOP, screenGeometry.left(), screenGeometry.top(), screenGeometry.width(), screenGeometry.height(), swpf);
+                if (!wasSync)
+                    clearFlag(SynchronousGeometryChangeEvent);
+                clearFlag(MaximizeToFullScreen);
+                QWindowSystemInterface::handleGeometryChange(window(), screenGeometry);
+                QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
+            }
+        } else {
+            // Restore saved state.
+            unsigned newStyle = m_savedStyle ? m_savedStyle : style();
+            if (visible)
+                newStyle |= WS_VISIBLE;
+            setStyle(newStyle);
+
+            const QScreen *screen = window()->screen();
+            if (!screen)
+                screen = QGuiApplication::primaryScreen();
+            // That area of the virtual desktop might not be covered by a screen anymore.
+            if (const auto platformScreen = screen->handle()) {
+                if (!platformScreen->geometry().intersects(m_savedFrameGeometry))
+                    m_savedFrameGeometry.moveTo(platformScreen->geometry().topLeft());
+            }
+
+            if (newState & Qt::WindowMinimized) {
+                setMinimizedGeometry(m_data.hwnd, m_savedFrameGeometry);
+                if (stateChange & Qt::WindowMaximized)
+                    setRestoreMaximizedFlag(m_data.hwnd, newState & Qt::WindowMaximized);
+            } else {
+                UINT swpf = SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE;
+                if (!m_savedFrameGeometry.isValid())
+                    swpf |= SWP_NOSIZE | SWP_NOMOVE;
+                const bool wasSync = testFlag(SynchronousGeometryChangeEvent);
+                setFlag(SynchronousGeometryChangeEvent);
+                // After maximized/fullscreen; the window can be in a maximized state. Clear
+                // it before applying the normal geometry.
+                if (windowVisibility_sys(m_data.hwnd) == QWindow::Maximized)
+                    ShowWindow(m_data.hwnd, SW_SHOWNOACTIVATE);
+                SetWindowPos(m_data.hwnd, nullptr, m_savedFrameGeometry.x(), m_savedFrameGeometry.y(),
+                             m_savedFrameGeometry.width(), m_savedFrameGeometry.height(), swpf);
+                if (!wasSync)
+                    clearFlag(SynchronousGeometryChangeEvent);
+                // preserve maximized state
+                if (visible) {
+                    setFlag(WithinMaximize);
+                    ShowWindow(m_data.hwnd,
+                               (newState & Qt::WindowMaximized) ? SW_MAXIMIZE : SW_SHOWNA);
+                    clearFlag(WithinMaximize);
+                }
+            }
+            m_savedStyle = 0;
+            m_savedFrameGeometry = QRect();
+        }
+    } else if ((oldState & Qt::WindowMaximized) != (newState & Qt::WindowMaximized)) {
+        if (visible && !(newState & Qt::WindowMinimized)) {
+            setFlag(WithinMaximize);
+            if (newState & Qt::WindowFullScreen)
+                setFlag(MaximizeToFullScreen);
+            ShowWindow(m_data.hwnd,
+                       (newState & Qt::WindowMaximized) ? SW_MAXIMIZE : SW_SHOWNOACTIVATE);
+            clearFlag(WithinMaximize);
+            clearFlag(MaximizeToFullScreen);
+        } else if (visible && (oldState & newState & Qt::WindowMinimized)) {
+            // change of the maximized state while keeping minimized
+            setRestoreMaximizedFlag(m_data.hwnd, newState & Qt::WindowMaximized);
+        }
+    }
+
+    if (stateChange & Qt::WindowMinimized) {
+        if (visible) {
+            ShowWindow(m_data.hwnd,
+                       (newState & Qt::WindowMinimized) ? SW_MINIMIZE :
+                       (newState & Qt::WindowMaximized) ? SW_MAXIMIZE : SW_SHOWNORMAL);
+            if ((newState & Qt::WindowMinimized) && (stateChange & Qt::WindowMaximized))
+                setRestoreMaximizedFlag(m_data.hwnd, newState & Qt::WindowMaximized);
+        }
+    }
+    qCDebug(lcQpaWindow) << '<' << __FUNCTION__ << this << window() << newState;
+}
+
+void QWindowsWindow::setStyle(unsigned s) const
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << this << window() << debugWinStyle(s);
+    setFlag(WithinSetStyle);
+    SetWindowLongPtr(m_data.hwnd, GWL_STYLE, s);
+    clearFlag(WithinSetStyle);
+}
+
+void QWindowsWindow::setExStyle(unsigned s) const
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << this << window() << debugWinExStyle(s);
+    SetWindowLongPtr(m_data.hwnd, GWL_EXSTYLE, s);
+}
+
+bool QWindowsWindow::windowEvent(QEvent *event)
+{
+    switch (event->type()) {
+    case QEvent::ApplicationPaletteChange:
+        setDarkBorder(QWindowsContext::isDarkMode());
+        break;
+    case QEvent::WindowBlocked: // Blocked by another modal window.
+        setEnabled(false);
+        setFlag(BlockedByModal);
+        if (hasMouseCapture())
+            ReleaseCapture();
+        break;
+    case QEvent::WindowUnblocked:
+        setEnabled(true);
+        clearFlag(BlockedByModal);
+        break;
+    default:
+        break;
+    }
+
+    return QPlatformWindow::windowEvent(event);
+}
+
+void QWindowsWindow::propagateSizeHints()
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << this << window();
+}
+
+bool QWindowsWindow::handleGeometryChangingMessage(MSG *message, const QWindow *qWindow, const QMargins &margins)
+{
+    auto *windowPos = reinterpret_cast<WINDOWPOS *>(message->lParam);
+    const QRect suggestedFrameGeometry(windowPos->x, windowPos->y,
+                                       windowPos->cx, windowPos->cy);
+    const QRect suggestedGeometry = suggestedFrameGeometry - margins;
+
+    // Tell Windows to discard the entire contents of the client area, as re-using
+    // parts of the client area would lead to jitter during resize.
+    // Check the suggestedGeometry against the current one to only discard during
+    // resize, and not a plain move. We also look for SWP_NOSIZE since that, too,
+    // implies an identical size, and comparing QRects wouldn't work with null cx/cy
+    if (!(windowPos->flags & SWP_NOSIZE) && suggestedGeometry.size() != qWindow->geometry().size())
+        windowPos->flags |= SWP_NOCOPYBITS;
+
+    if ((windowPos->flags & SWP_NOZORDER) == 0) {
+        if (QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(qWindow)) {
+            QWindow *parentWindow = qWindow->parent();
+            HWND parentHWND = GetAncestor(windowPos->hwnd, GA_PARENT);
+            HWND desktopHWND = GetDesktopWindow();
+            platformWindow->m_data.embedded = !parentWindow && parentHWND && (parentHWND != desktopHWND);
+        }
+        if (qWindow->flags().testFlag(Qt::WindowStaysOnBottomHint))
+            windowPos->hwndInsertAfter = HWND_BOTTOM;
+    }
+    if (!qWindow->isTopLevel()) // Implement hasHeightForWidth().
+        return false;
+    if (windowPos->flags & SWP_NOSIZE)
+        return false;
+    const QRectF correctedGeometryF = QPlatformWindow::closestAcceptableGeometry(qWindow, suggestedGeometry);
+    if (!correctedGeometryF.isValid())
+        return false;
+    const QRect correctedFrameGeometry = correctedGeometryF.toRect() + margins;
+    if (correctedFrameGeometry == suggestedFrameGeometry)
+        return false;
+    windowPos->x = correctedFrameGeometry.left();
+    windowPos->y = correctedFrameGeometry.top();
+    windowPos->cx = correctedFrameGeometry.width();
+    windowPos->cy = correctedFrameGeometry.height();
+    return true;
+}
+
+bool QWindowsWindow::handleGeometryChanging(MSG *message) const
+{
+    const QMargins margins = window()->isTopLevel() ? fullFrameMargins() : QMargins();
+    return QWindowsWindow::handleGeometryChangingMessage(message, window(), margins);
+}
+
+void QWindowsWindow::setFullFrameMargins(const QMargins &newMargins)
+{
+    if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd))
+        return;
+    if (m_data.fullFrameMargins != newMargins) {
+        qCDebug(lcQpaWindow) << __FUNCTION__ << window() <<  m_data.fullFrameMargins  << "->" << newMargins;
+        m_data.fullFrameMargins = newMargins;
+    }
+}
+
+void QWindowsWindow::updateFullFrameMargins()
+{
+    // QTBUG-82580: If a native menu is present, force a WM_NCCALCSIZE.
+    if (GetMenu(m_data.hwnd))
+        QWindowsContext::forceNcCalcSize(m_data.hwnd);
+    else
+        calculateFullFrameMargins();
+}
+
+void QWindowsWindow::calculateFullFrameMargins()
+{
+    if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd))
+        return;
+
+    // QTBUG-113736: systemMargins depends on AdjustWindowRectExForDpi. This doesn't take into
+    // account possible external modifications to the titlebar, as with ExtendsContentIntoTitleBar()
+    // from the Windows App SDK. We can fix this by comparing the WindowRect (which includes the
+    // frame) to the ClientRect. If a 'typical' frame is detected, i.e. only the titlebar has been
+    // modified, we can safely adjust the frame by deducting the bottom margin to the total Y
+    // difference between the two rects, to get the actual size of the titlebar and prevent
+    // unwanted client area slicing.
+
+    RECT windowRect{};
+    RECT clientRect{};
+    GetWindowRect(handle(), &windowRect);
+    GetClientRect(handle(), &clientRect);
+
+    // QTBUG-117704 It is also possible that the user has manually removed the frame (for example
+    // by handling WM_NCCALCSIZE). If that is the case, i.e., the client area and the window area
+    // have identical sizes, we don't want to override the user-defined margins.
+
+    if (qrectFromRECT(windowRect).size() == qrectFromRECT(clientRect).size())
+        return;
+
+    // Normally obtained from WM_NCCALCSIZE. This calculation only works
+    // when no native menu is present.
+    const auto systemMargins = testFlag(DisableNonClientScaling)
+        ? QWindowsGeometryHint::frameOnPrimaryScreen(window(), m_data.hwnd)
+        : frameMargins_sys();
+
+    const int yDiff = (windowRect.bottom - windowRect.top) - (clientRect.bottom - clientRect.top);
+    const bool typicalFrame = (systemMargins.left() == systemMargins.right())
+            && (systemMargins.right() == systemMargins.bottom());
+
+    const QMargins adjustedMargins = typicalFrame ?
+          QMargins(systemMargins.left(), (yDiff - systemMargins.bottom()),
+                   systemMargins.right(), systemMargins.bottom())
+            : systemMargins + customMargins();
+
+    setFullFrameMargins(adjustedMargins);
+}
+
+QMargins QWindowsWindow::frameMargins() const
+{
+    QMargins result = fullFrameMargins();
+    if (isTopLevel() && m_data.hasFrame)
+        result -= invisibleMargins(m_data.hwnd);
+    return result;
+}
+
+QMargins QWindowsWindow::fullFrameMargins() const
+{
+    if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd))
+        return {};
+    return m_data.fullFrameMargins;
+}
+
+void QWindowsWindow::setOpacity(qreal level)
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << level;
+    if (!qFuzzyCompare(m_opacity, level)) {
+        m_opacity = level;
+        if (m_data.hwnd)
+            setWindowOpacity(m_data.hwnd, m_data.flags,
+                             window()->format().hasAlpha(), testFlag(OpenGLSurface) || testFlag(VulkanSurface) || testFlag(Direct3DSurface),
+                             level);
+    }
+}
+
+static inline HRGN createRectRegion(const QRect &r)
+{
+    return CreateRectRgn(r.left(), r.top(), r.x() + r.width(), r.y() + r.height());
+}
+
+static inline void addRectToWinRegion(const QRect &rect, HRGN *winRegion)
+{
+    if (const HRGN rectRegion = createRectRegion(rect)) {
+        HRGN result = CreateRectRgn(0, 0, 0, 0);
+        if (CombineRgn(result, *winRegion, rectRegion, RGN_OR)) {
+            DeleteObject(*winRegion);
+            *winRegion = result;
+        }
+        DeleteObject(rectRegion);
+    }
+}
+
+static HRGN qRegionToWinRegion(const QRegion &region)
+{
+    auto it = region.begin();
+    const auto end = region.end();
+    if (it == end)
+        return nullptr;
+    HRGN hRegion = createRectRegion(*it);
+    while (++it != end)
+        addRectToWinRegion(*it, &hRegion);
+    return hRegion;
+}
+
+void QWindowsWindow::setMask(const QRegion &region)
+{
+    if (region.isEmpty()) {
+         SetWindowRgn(m_data.hwnd, nullptr, true);
+         return;
+    }
+    const HRGN winRegion = qRegionToWinRegion(region);
+
+    // Mask is in client area coordinates, so offset it in case we have a frame
+    if (window()->isTopLevel()) {
+        const QMargins margins = fullFrameMargins();
+        OffsetRgn(winRegion, margins.left(), margins.top());
+    }
+
+    // SetWindowRgn takes ownership.
+    if (!SetWindowRgn(m_data.hwnd, winRegion, true))
+        DeleteObject(winRegion);
+}
+
+void QWindowsWindow::requestActivateWindow()
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << this << window();
+
+    if (!m_data.hwnd)
+        return;
+
+    const auto activationBehavior = QWindowsIntegration::instance()->windowActivationBehavior();
+    if (QGuiApplication::applicationState() == Qt::ApplicationActive
+        || activationBehavior != QWindowsApplication::AlwaysActivateWindow) {
+        SetForegroundWindow(m_data.hwnd);
+        SetFocus(m_data.hwnd);
+        return;
+    }
+
+    // Force activate this window. The following code will bring the window to the
+    // foreground and activate it. If the window is hidden, it will show up. If
+    // the window is minimized, it will restore to the previous position.
+
+    // But first we need some sanity checks.
+    if (m_data.flags & Qt::WindowStaysOnBottomHint) {
+        qCWarning(lcQpaWindow) <<
+            "Windows with Qt::WindowStaysOnBottomHint can't be brought to the foreground.";
+        return;
+    }
+    if (m_data.flags & Qt::WindowStaysOnTopHint) {
+        qCWarning(lcQpaWindow) <<
+            "Windows with Qt::WindowStaysOnTopHint will always be on the foreground.";
+        return;
+    }
+    if (window()->type() == Qt::ToolTip) {
+        qCWarning(lcQpaWindow) << "ToolTip windows should not be activated.";
+        return;
+    }
+
+    // We need to show the window first, otherwise we won't be able to bring it to front.
+    if (!IsWindowVisible(m_data.hwnd))
+        ShowWindow(m_data.hwnd, SW_SHOW);
+
+    if (IsIconic(m_data.hwnd)) {
+        ShowWindow(m_data.hwnd, SW_RESTORE);
+        // When the window is restored, it will always become the foreground window.
+        // So return early here, we don't need the following code to bring it to front.
+        return;
+    }
+
+    // OK, our window is not minimized, so now we will try to bring it to front manually.
+    const HWND oldForegroundWindow = GetForegroundWindow();
+    if (!oldForegroundWindow) // It may be NULL, according to MS docs.
+        return;
+
+    // First try to send a message to the current foreground window to check whether
+    // it is currently hanging or not.
+    if (SendMessageTimeoutW(oldForegroundWindow, WM_NULL, 0, 0,
+            SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG, 1000, nullptr) == 0) {
+        qCWarning(lcQpaWindow) << "The foreground window hangs, can't activate current window.";
+        return;
+    }
+
+    const DWORD windowThreadProcessId = GetWindowThreadProcessId(oldForegroundWindow, nullptr);
+    const DWORD currentThreadId = GetCurrentThreadId();
+
+    AttachThreadInput(windowThreadProcessId, currentThreadId, TRUE);
+    const auto cleanup = qScopeGuard([windowThreadProcessId, currentThreadId](){
+        AttachThreadInput(windowThreadProcessId, currentThreadId, FALSE);
+    });
+
+    BringWindowToTop(m_data.hwnd);
+
+    // Activate the window too. This will force us to the virtual desktop this
+    // window is on, if it's on another virtual desktop.
+    SetActiveWindow(m_data.hwnd);
+}
+
+bool QWindowsWindow::setKeyboardGrabEnabled(bool grab)
+{
+    if (!m_data.hwnd) {
+        qWarning("%s: No handle", __FUNCTION__);
+        return false;
+    }
+    qCDebug(lcQpaWindow) << __FUNCTION__ << this << window() << grab;
+
+    QWindowsContext *context = QWindowsContext::instance();
+    if (grab) {
+        context->setKeyGrabber(window());
+    } else {
+        if (context->keyGrabber() == window())
+            context->setKeyGrabber(nullptr);
+    }
+    return true;
+}
+
+bool QWindowsWindow::setMouseGrabEnabled(bool grab)
+{
+    qCDebug(lcQpaWindow) << __FUNCTION__ << window() << grab;
+    if (!m_data.hwnd) {
+        qWarning("%s: No handle", __FUNCTION__);
+        return false;
+    }
+    if (!isVisible() && grab) {
+        qWarning("%s: Not setting mouse grab for invisible window %s/'%s'",
+                 __FUNCTION__, window()->metaObject()->className(),
+                 qPrintable(window()->objectName()));
+        return false;
+    }
+    // release grab or an explicit grab overriding autocapture: Clear flag.
+    clearFlag(QWindowsWindow::AutoMouseCapture);
+    if (hasMouseCapture() != grab) {
+        if (grab) {
+            SetCapture(m_data.hwnd);
+        } else {
+            ReleaseCapture();
+        }
+    }
+    return grab;
+}
+
+static inline DWORD edgesToWinOrientation(Qt::Edges edges)
+{
+    if (edges == Qt::LeftEdge)
+        return 0xf001; // SC_SIZELEFT;
+    else if (edges == (Qt::RightEdge))
+        return 0xf002; // SC_SIZERIGHT
+    else if (edges == (Qt::TopEdge))
+        return 0xf003; // SC_SIZETOP
+    else if (edges == (Qt::TopEdge | Qt::LeftEdge))
+        return 0xf004; // SC_SIZETOPLEFT
+    else if (edges == (Qt::TopEdge | Qt::RightEdge))
+        return 0xf005; // SC_SIZETOPRIGHT
+    else if (edges == (Qt::BottomEdge))
+        return 0xf006; // SC_SIZEBOTTOM
+    else if (edges == (Qt::BottomEdge | Qt::LeftEdge))
+        return 0xf007; // SC_SIZEBOTTOMLEFT
+    else if (edges == (Qt::BottomEdge | Qt::RightEdge))
+        return 0xf008; // SC_SIZEBOTTOMRIGHT
+
+    return 0xf000; // SC_SIZE
+}
+
+bool QWindowsWindow::startSystemResize(Qt::Edges edges)
+{
+    if (Q_UNLIKELY(window()->flags().testFlag(Qt::MSWindowsFixedSizeDialogHint)))
+        return false;
+
+    ReleaseCapture();
+    PostMessage(m_data.hwnd, WM_SYSCOMMAND, edgesToWinOrientation(edges), 0);
+    setFlag(SizeGripOperation);
+    return true;
+}
+
+bool QWindowsWindow::startSystemMove()
+{
+    ReleaseCapture();
+    PostMessage(m_data.hwnd, WM_SYSCOMMAND, 0xF012 /*SC_DRAGMOVE*/, 0);
+    return true;
+}
+
+void QWindowsWindow::setFrameStrutEventsEnabled(bool enabled)
+{
+    if (enabled) {
+        setFlag(FrameStrutEventsEnabled);
+    } else {
+        clearFlag(FrameStrutEventsEnabled);
+    }
+}
+
+void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const
+{
+    QWindowsGeometryHint::applyToMinMaxInfo(window(), fullFrameMargins(), mmi);
+    qCDebug(lcQpaWindow) << __FUNCTION__ << window() << *mmi;
+}
+
+bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *result) const
+{
+    // QTBUG-32663, suppress resize cursor for fixed size windows.
+    const QWindow *w = window();
+    if (!w->isTopLevel() // Task 105852, minimized windows need to respond to user input.
+        || (m_windowState != Qt::WindowNoState)
+        || !isActive()
+        || (m_data.flags & Qt::FramelessWindowHint)) {
+        return false;
+    }
+    const QSize minimumSize = w->minimumSize();
+    if (minimumSize.isEmpty())
+        return false;
+    const QSize maximumSize = w->maximumSize();
+    const bool fixedWidth = minimumSize.width() == maximumSize.width();
+    const bool fixedHeight = minimumSize.height() == maximumSize.height();
+    if (!fixedWidth && !fixedHeight)
+        return false;
+    const QPoint localPos = w->mapFromGlobal(QHighDpi::fromNativePixels(globalPos, w));
+    const QSize size = w->size();
+    if (fixedHeight) {
+        if (localPos.y() >= size.height()) {
+            *result = HTBORDER; // Unspecified border, no resize cursor.
+            return true;
+        }
+        if (localPos.y() < 0) {
+            const int topResizeBarPos = invisibleMargins(m_data.hwnd).left() - frameMargins().top();
+            if (localPos.y() < topResizeBarPos) {
+                *result = HTCAPTION; // Extend caption over top resize bar, let's user move the window.
+                return true;
+            }
+        }
+    }
+    if (fixedWidth && (localPos.x() < 0 || localPos.x() >= size.width())) {
+        *result = HTBORDER; // Unspecified border, no resize cursor.
+        return true;
+    }
+    return false;
+}
+
+#ifndef QT_NO_CURSOR
+// Return the default cursor (Arrow) from QWindowsCursor's cache.
+static inline CursorHandlePtr defaultCursor(const QWindow *w)
+{
+    if (QScreen *screen = w->screen())
+        if (const QPlatformScreen *platformScreen = screen->handle())
+            if (QPlatformCursor *cursor = platformScreen->cursor())
+                return static_cast<QWindowsCursor *>(cursor)->standardWindowCursor(Qt::ArrowCursor);
+    return CursorHandlePtr(new CursorHandle(QWindowsCursor::createCursorFromShape(Qt::ArrowCursor)));
+}
+
+// Check whether to apply a new cursor. Either the window in question is
+// currently under mouse, or it is the parent of the window under mouse and
+// there is no other window with an explicitly set cursor in-between.
+static inline bool applyNewCursor(const QWindow *w)
+{
+    const QWindow *underMouse = QWindowsContext::instance()->windowUnderMouse();
+    if (underMouse == w)
+        return true;
+    for (const QWindow *p = underMouse; p ; p = p->parent()) {
+        if (p == w)
+            return true;
+        const QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(p);
+        if (platformWindow && !platformWindow->cursor()->isNull())
+            return false;
+    }
+    return false;
+}
+#endif // !QT_NO_CURSOR
+
+/*!
+    \brief Applies to cursor property set on the window to the global cursor.
+
+    \sa QWindowsCursor
+*/
+
+void QWindowsWindow::applyCursor()
+{
+    if (QWindowsCursor::hasOverrideCursor()) {
+        if (isTopLevel())
+            QWindowsCursor::enforceOverrideCursor();
+        return;
+    }
+#ifndef QT_NO_CURSOR
+    if (m_cursor->isNull()) { // Recurse up to parent with non-null cursor. Set default for toplevel.
+        if (const QWindow *p = window()->parent()) {
+            if (QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(p))
+                platformWindow->applyCursor();
+        } else {
+            SetCursor(defaultCursor(window())->handle());
+        }
+    } else {
+        SetCursor(m_cursor->handle());
+    }
+#endif
+}
+
+void QWindowsWindow::setCursor(const CursorHandlePtr &c)
+{
+#ifndef QT_NO_CURSOR
+    bool changed = c->handle() != m_cursor->handle();
+    // QTBUG-98856: Cursors can get out of sync after restoring override
+    // cursors on native windows. Force an update.
+    if (testFlag(RestoreOverrideCursor)) {
+        clearFlag(RestoreOverrideCursor);
+        changed = true;
+    }
+    if (changed) {
+        const bool apply = applyNewCursor(window());
+        qCDebug(lcQpaWindow) << window() << __FUNCTION__
+            << c->handle() << " doApply=" << apply;
+        m_cursor = c;
+        if (apply)
+            applyCursor();
+    }
+#endif
+}
+
+void QWindowsWindow::setAlertState(bool enabled)
+{
+    if (isAlertState() == enabled)
+        return;
+    if (enabled) {
+        alertWindow(0);
+        setFlag(AlertState);
+    } else {
+        stopAlertWindow();
+        clearFlag(AlertState);
+    }
+}
+
+void QWindowsWindow::alertWindow(int durationMs)
+{
+    UINT timeOutMs = GetCaretBlinkTime();
+    if (!timeOutMs || timeOutMs == INFINITE)
+        timeOutMs = 250;
+
+    FLASHWINFO info;
+    info.cbSize = sizeof(info);
+    info.hwnd = m_data.hwnd;
+    info.dwFlags = FLASHW_TRAY;
+    info.dwTimeout = timeOutMs;
+    info.uCount = durationMs == 0 ? 10 : UINT(durationMs) / timeOutMs;
+    FlashWindowEx(&info);
+}
+
+void QWindowsWindow::stopAlertWindow()
+{
+    FLASHWINFO info;
+    info.cbSize = sizeof(info);
+    info.hwnd = m_data.hwnd;
+    info.dwFlags = FLASHW_STOP;
+    info.dwTimeout = 0;
+    info.uCount = 0;
+    FlashWindowEx(&info);
+}
+
+bool QWindowsWindow::isEnabled() const
+{
+    return (style() & WS_DISABLED) == 0;
+}
+
+void QWindowsWindow::setEnabled(bool enabled)
+{
+    const unsigned oldStyle = style();
+    unsigned newStyle = oldStyle;
+    if (enabled) {
+        newStyle &= ~WS_DISABLED;
+    } else {
+        newStyle |= WS_DISABLED;
+    }
+    if (newStyle != oldStyle)
+        setStyle(newStyle);
+}
+
+static HICON createHIcon(const QIcon &icon, int xSize, int ySize)
+{
+    if (!icon.isNull()) {
+        // QTBUG-90363, request DPR=1 for the title bar.
+        const QPixmap pm = icon.pixmap(icon.actualSize(QSize(xSize, ySize)), 1);
+        if (!pm.isNull())
+            return qt_pixmapToWinHICON(pm);
+    }
+    return nullptr;
+}
+
+void QWindowsWindow::setWindowIcon(const QIcon &icon)
+{
+    if (m_data.hwnd) {
+        destroyIcon();
+
+        m_iconSmall = createHIcon(icon, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
+        m_iconBig = createHIcon(icon, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
+
+        if (m_iconBig) {
+            SendMessage(m_data.hwnd, WM_SETICON, 0 /* ICON_SMALL */, LPARAM(m_iconSmall));
+            SendMessage(m_data.hwnd, WM_SETICON, 1 /* ICON_BIG */, LPARAM(m_iconBig));
+        } else {
+            SendMessage(m_data.hwnd, WM_SETICON, 0 /* ICON_SMALL */, LPARAM(m_iconSmall));
+            SendMessage(m_data.hwnd, WM_SETICON, 1 /* ICON_BIG */, LPARAM(m_iconSmall));
+        }
+    }
+}
+
+bool QWindowsWindow::isTopLevel() const
+{
+    return window()->isTopLevel() && !m_data.embedded;
+}
+
+enum : WORD {
+    DwmwaUseImmersiveDarkMode = 20,
+    DwmwaUseImmersiveDarkModeBefore20h1 = 19
+};
+
+static bool queryDarkBorder(HWND hwnd)
+{
+    BOOL result = FALSE;
+    const bool ok =
+        SUCCEEDED(DwmGetWindowAttribute(hwnd, DwmwaUseImmersiveDarkMode, &result, sizeof(result)))
+        || SUCCEEDED(DwmGetWindowAttribute(hwnd, DwmwaUseImmersiveDarkModeBefore20h1, &result, sizeof(result)));
+    if (!ok)
+        qCWarning(lcQpaWindow, "%s: Unable to retrieve dark window border setting.", __FUNCTION__);
+    return result == TRUE;
+}
+
+bool QWindowsWindow::setDarkBorderToWindow(HWND hwnd, bool d)
+{
+    const BOOL darkBorder = d ? TRUE : FALSE;
+    const bool ok =
+        SUCCEEDED(DwmSetWindowAttribute(hwnd, DwmwaUseImmersiveDarkMode, &darkBorder, sizeof(darkBorder)))
+        || SUCCEEDED(DwmSetWindowAttribute(hwnd, DwmwaUseImmersiveDarkModeBefore20h1, &darkBorder, sizeof(darkBorder)));
+    if (!ok)
+        qCWarning(lcQpaWindow, "%s: Unable to set %s window border.", __FUNCTION__, d ? "dark" : "light");
+    return ok;
+}
+
+void QWindowsWindow::setDarkBorder(bool d)
+{
+    // respect explicit opt-out and incompatible palettes or styles
+    d = d && shouldApplyDarkFrame(window());
+    if (queryDarkBorder(m_data.hwnd) == d)
+        return;
+
+    setDarkBorderToWindow(m_data.hwnd, d);
+}
+
+QWindowsMenuBar *QWindowsWindow::menuBar() const
+{
+    return m_menuBar.data();
+}
+
+void QWindowsWindow::setMenuBar(QWindowsMenuBar *mb)
+{
+    m_menuBar = mb;
+}
+
+QMargins QWindowsWindow::customMargins() const
+{
+    if (m_data.flags & Qt::FramelessWindowHint)
+        return {};
+    return m_data.customMargins;
+}
+
+/*!
+    \brief Sets custom margins to be added to the default margins determined by
+    the windows style in the handling of the WM_NCCALCSIZE message.
+
+    This is currently used to give the Aero-style QWizard a smaller top margin.
+    The property can be set using QPlatformNativeInterface::setWindowProperty() or,
+    before platform window creation, by setting a dynamic property
+    on the QWindow (see QWindowsIntegration::createPlatformWindow()).
+*/
+
+void QWindowsWindow::setCustomMargins(const QMargins &newCustomMargins)
+{
+    if (m_data.flags & Qt::FramelessWindowHint) {
+        qCWarning(lcQpaWindow) << "You should not set custom margins for a frameless window.";
+        return;
+    }
+    if (newCustomMargins != m_data.customMargins) {
+        const QMargins oldCustomMargins = m_data.customMargins;
+        m_data.customMargins = newCustomMargins;
+         // Re-trigger WM_NCALCSIZE with wParam=1 by passing SWP_FRAMECHANGED
+        const QRect currentFrameGeometry = frameGeometry_sys();
+        const QPoint topLeft = currentFrameGeometry.topLeft();
+        QRect newFrame = currentFrameGeometry.marginsRemoved(oldCustomMargins) + m_data.customMargins;
+        newFrame.moveTo(topLeft);
+        qCDebug(lcQpaWindow) << __FUNCTION__ << oldCustomMargins << "->" << newCustomMargins
+            << currentFrameGeometry << "->" << newFrame;
+        SetWindowPos(m_data.hwnd, nullptr, newFrame.x(), newFrame.y(), newFrame.width(), newFrame.height(), SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE);
+    }
+}
+
+void *QWindowsWindow::surface(void *nativeConfig, int *err)
+{
+#if QT_CONFIG(vulkan)
+    Q_UNUSED(nativeConfig);
+    Q_UNUSED(err);
+    if (window()->surfaceType() == QSurface::VulkanSurface) {
+        if (!m_vkSurface) {
+            QVulkanInstance *inst = window()->vulkanInstance();
+            if (inst)
+                m_vkSurface = static_cast<QWindowsVulkanInstance *>(inst->handle())->createSurface(handle());
+            else
+                qWarning("Attempted to create Vulkan surface without an instance; was QWindow::setVulkanInstance() called?");
+        }
+        // Different semantics for VkSurfaces: the return value is the address,
+        // not the value, given that this is a 64-bit handle even on x86.
+        return &m_vkSurface;
+    }
+#elif defined(QT_NO_OPENGL)
+    Q_UNUSED(err);
+    Q_UNUSED(nativeConfig);
+    return nullptr;
+#endif
+#ifndef QT_NO_OPENGL
+    if (!m_surface) {
+        if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext())
+            m_surface = staticOpenGLContext->createWindowSurface(m_data.hwnd, nativeConfig, err);
+    }
+
+    return m_surface;
+#else
+    return nullptr;
+#endif
+}
+
+void QWindowsWindow::invalidateSurface()
+{
+#if QT_CONFIG(vulkan)
+    if (m_vkSurface) {
+        QVulkanInstance *inst = window()->vulkanInstance();
+        if (inst)
+            static_cast<QWindowsVulkanInstance *>(inst->handle())->destroySurface(m_vkSurface);
+        m_vkSurface = VK_NULL_HANDLE;
+    }
+#endif
+#ifndef QT_NO_OPENGL
+    if (m_surface) {
+        if (QWindowsStaticOpenGLContext *staticOpenGLContext = QWindowsIntegration::staticOpenGLContext())
+            staticOpenGLContext->destroyWindowSurface(m_surface);
+        m_surface = nullptr;
+    }
+#endif // QT_NO_OPENGL
+}
+
+void QWindowsWindow::registerTouchWindow()
+{
+    if ((QWindowsContext::instance()->systemInfo() & QWindowsContext::SI_SupportsTouch) == 0)
+        return;
+
+    // Initially register or re-register to change the flags
+    const auto touchTypes = QWindowsIntegration::instance()->touchWindowTouchType();
+    if (testFlag(TouchRegistered)) {
+        const auto currentTouchTypes = touchWindowTouchTypes_sys();
+        if (currentTouchTypes.has_value() && currentTouchTypes.value() == touchTypes)
+            return;
+    }
+
+    ULONG touchFlags = 0;
+    if (touchTypes.testFlag(TouchWindowTouchType::FineTouch))
+        touchFlags |= TWF_FINETOUCH;
+    if (touchTypes.testFlag(TouchWindowTouchType::WantPalmTouch))
+        touchFlags |= TWF_WANTPALM;
+    if (RegisterTouchWindow(m_data.hwnd, touchFlags))
+        setFlag(TouchRegistered);
+    else
+        qErrnoWarning("RegisterTouchWindow() failed for window '%s'.", qPrintable(window()->objectName()));
+}
+
+void QWindowsWindow::setHasBorderInFullScreenStatic(QWindow *window, bool border)
+{
+    if (QPlatformWindow *handle = window->handle())
+        static_cast<QWindowsWindow *>(handle)->setHasBorderInFullScreen(border);
+    else
+        window->setProperty(hasBorderInFullScreenProperty, QVariant(border));
+}
+
+void QWindowsWindow::setHasBorderInFullScreenDefault(bool border)
+{
+    m_borderInFullScreenDefault = border;
+}
+
+bool QWindowsWindow::hasBorderInFullScreen() const
+{
+    return testFlag(HasBorderInFullScreen);
+}
+
+void QWindowsWindow::setHasBorderInFullScreen(bool border)
+{
+    if (hasBorderInFullScreen() == border)
+        return;
+    if (border)
+        setFlag(HasBorderInFullScreen);
+    else
+        clearFlag(HasBorderInFullScreen);
+    // Directly apply the flag in case we are fullscreen.
+    if (m_windowState == Qt::WindowFullScreen) {
+        LONG_PTR style = GetWindowLongPtr(handle(), GWL_STYLE);
+        if (border)
+            style |= WS_BORDER;
+        else
+            style &= ~WS_BORDER;
+        SetWindowLongPtr(handle(), GWL_STYLE, style);
+    }
+}
+
+QString QWindowsWindow::formatWindowTitle(const QString &title)
+{
+    return QPlatformWindow::formatWindowTitle(title, QStringLiteral(" - "));
+}
+
+QT_END_NAMESPACE
diff --git a/qtbase/src/widgets/styles/qwindowsstyle.cpp b/qtbase/src/widgets/styles/qwindowsstyle.cpp
index ae82f784ca..a617ef14c1 100644
--- a/qtbase/src/widgets/styles/qwindowsstyle.cpp
+++ b/qtbase/src/widgets/styles/qwindowsstyle.cpp
@@ -262,33 +262,29 @@ void QWindowsStyle::polish(QPalette &pal)
 int QWindowsStylePrivate::pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *, const QWidget *widget)
 {
 #if defined(Q_OS_WIN)
-    // The pixel metrics are in device indepentent pixels;
-    // hardcode DPI to 1x 96 DPI.
-    const int dpi = 96;
-
     switch (pm) {
     case QStyle::PM_DockWidgetFrameWidth:
-        return GetSystemMetricsForDpi(SM_CXFRAME, dpi);
+        return GetSystemMetrics(SM_CXFRAME);
 
     case QStyle::PM_TitleBarHeight: {
         const int resizeBorderThickness =
-            GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
+                GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
         if (widget && (widget->windowType() == Qt::Tool))
-            return GetSystemMetricsForDpi(SM_CYSMCAPTION, dpi) + resizeBorderThickness;
-        return GetSystemMetricsForDpi(SM_CYCAPTION, dpi) + resizeBorderThickness;
+            return GetSystemMetrics(SM_CYSMCAPTION) + resizeBorderThickness;
+        return GetSystemMetrics(SM_CYCAPTION) + resizeBorderThickness;
     }
 
     case QStyle::PM_ScrollBarExtent:
-        {
-            NONCLIENTMETRICS ncm;
-            ncm.cbSize = sizeof(NONCLIENTMETRICS);
-            if (SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0, dpi))
-                return qMax(ncm.iScrollHeight, ncm.iScrollWidth);
-        }
-        break;
+    {
+        NONCLIENTMETRICS ncm;
+        ncm.cbSize = FIELD_OFFSET(NONCLIENTMETRICS, lfMessageFont) + sizeof(LOGFONT);
+        if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0))
+            return qMax(ncm.iScrollHeight, ncm.iScrollWidth);
+    }
+    break;
 
     case  QStyle::PM_MdiSubWindowFrameWidth:
-        return GetSystemMetricsForDpi(SM_CYFRAME, dpi);
+        return GetSystemMetrics(SM_CYFRAME);
 
     default:
         break;
