diff --git a/qtbase/src/corelib/io/qstandardpaths_win.cpp b/qtbase/src/corelib/io/qstandardpaths_win.cpp new file mode 100644 index 00000000..13b8fe22 --- /dev/null +++ b/qtbase/src/corelib/io/qstandardpaths_win.cpp @@ -0,0 +1,242 @@ +// 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 "qstandardpaths.h" + +#include +#include + +#ifndef QT_BOOTSTRAPPED +#include +#endif + +#include +#include +#include +#include + +#ifndef QT_NO_STANDARDPATHS + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QString convertCharArray(const wchar_t *path) +{ + return QDir::fromNativeSeparators(QString::fromWCharArray(path)); +} + +static inline bool isGenericConfigLocation(QStandardPaths::StandardLocation type) +{ + return type == QStandardPaths::GenericConfigLocation || type == QStandardPaths::GenericDataLocation; +} + +static inline bool isConfigLocation(QStandardPaths::StandardLocation type) +{ + return type == QStandardPaths::ConfigLocation || type == QStandardPaths::AppConfigLocation + || type == QStandardPaths::AppDataLocation || type == QStandardPaths::AppLocalDataLocation + || isGenericConfigLocation(type); +} + +static void appendOrganizationAndApp(QString &path) // Courtesy qstandardpaths_unix.cpp +{ +#ifndef QT_BOOTSTRAPPED + const QString &org = QCoreApplication::organizationName(); + if (!org.isEmpty()) + path += u'/' + org; + const QString &appName = QCoreApplication::applicationName(); + if (!appName.isEmpty()) + path += u'/' + appName; +#else // !QT_BOOTSTRAPPED + Q_UNUSED(path); +#endif +} + +static inline void appendTestMode(QString &path) +{ + if (QStandardPaths::isTestModeEnabled()) + path += "/qttest"_L1; +} + +static bool isProcessLowIntegrity() +{ + // same as GetCurrentProcessToken() + const auto process_token = HANDLE(quintptr(-4)); + + QVarLengthArray token_info_buf(256); + auto* token_info = reinterpret_cast(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)) { + // grow buffer and retry GetTokenInformation + token_info_buf.resize(token_info_length); + token_info = reinterpret_cast(token_info_buf.data()); + if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_info, token_info_length, &token_info_length)) + return false; // assume "normal" process + } + + // 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); + return (integrity_level < SECURITY_MANDATORY_MEDIUM_RID); +} + +// Map QStandardPaths::StandardLocation to KNOWNFOLDERID of SHGetKnownFolderPath() +static GUID writableSpecialFolderId(QStandardPaths::StandardLocation type) +{ + // folders for medium & high integrity processes + static const GUID folderIds[] = { + FOLDERID_Desktop, // DesktopLocation + FOLDERID_Documents, // DocumentsLocation + FOLDERID_Fonts, // FontsLocation + FOLDERID_Programs, // ApplicationsLocation + FOLDERID_Music, // MusicLocation + FOLDERID_Videos, // MoviesLocation + FOLDERID_Pictures, // PicturesLocation + GUID(), GUID(), // TempLocation/HomeLocation + FOLDERID_LocalAppData, // AppLocalDataLocation ("Local" path) + GUID(), // CacheLocation + FOLDERID_LocalAppData, // GenericDataLocation ("Local" path) + GUID(), // RuntimeLocation + FOLDERID_LocalAppData, // ConfigLocation ("Local" path) + FOLDERID_Downloads, // DownloadLocation + GUID(), // GenericCacheLocation + FOLDERID_LocalAppData, // GenericConfigLocation ("Local" path) + FOLDERID_RoamingAppData,// AppDataLocation ("Roaming" path) + FOLDERID_LocalAppData, // AppConfigLocation ("Local" path) + FOLDERID_Public, // PublicShareLocation + FOLDERID_Templates, // TemplatesLocation + }; + static_assert(sizeof(folderIds) / sizeof(folderIds[0]) == size_t(QStandardPaths::TemplatesLocation + 1)); + + // folders for low integrity processes + static const GUID folderIds_li[] = { + FOLDERID_Desktop, // DesktopLocation + FOLDERID_Documents, // DocumentsLocation + FOLDERID_Fonts, // FontsLocation + FOLDERID_Programs, // ApplicationsLocation + FOLDERID_Music, // MusicLocation + FOLDERID_Videos, // MoviesLocation + FOLDERID_Pictures, // PicturesLocation + GUID(), GUID(), // TempLocation/HomeLocation + FOLDERID_LocalAppDataLow,// AppLocalDataLocation ("Local" path) + GUID(), // CacheLocation + FOLDERID_LocalAppDataLow,// GenericDataLocation ("Local" path) + GUID(), // RuntimeLocation + FOLDERID_LocalAppDataLow,// ConfigLocation ("Local" path) + FOLDERID_Downloads, // DownloadLocation + GUID(), // GenericCacheLocation + FOLDERID_LocalAppDataLow,// GenericConfigLocation ("Local" path) + FOLDERID_RoamingAppData, // AppDataLocation ("Roaming" path) + FOLDERID_LocalAppDataLow,// AppConfigLocation ("Local" path) + FOLDERID_Public, // PublicShareLocation + FOLDERID_Templates, // TemplatesLocation + }; + static_assert(sizeof(folderIds_li) == sizeof(folderIds)); + + static bool low_integrity_process = isProcessLowIntegrity(); + if (size_t(type) < sizeof(folderIds) / sizeof(folderIds[0])) + return low_integrity_process ? folderIds_li[type] : folderIds[type]; + return GUID(); +} + +// Convenience for SHGetKnownFolderPath(). +static QString sHGetKnownFolderPath(const GUID &clsid) +{ + QString result; + LPWSTR path; + if (Q_LIKELY(SUCCEEDED(SHGetKnownFolderPath(clsid, KF_FLAG_DONT_VERIFY, 0, &path)))) { + result = convertCharArray(path); + CoTaskMemFree(path); + } + return result; +} + +QString QStandardPaths::writableLocation(StandardLocation type) +{ + QString result; + switch (type) { + case CacheLocation: + // Although Microsoft has a Cache key it is a pointer to IE's cache, not a cache + // location for everyone. Most applications seem to be using a + // cache directory located in their AppData directory + result = sHGetKnownFolderPath(writableSpecialFolderId(AppLocalDataLocation)); + if (!result.isEmpty()) { + appendTestMode(result); + appendOrganizationAndApp(result); + result += "/cache"_L1; + } + break; + + case GenericCacheLocation: + result = sHGetKnownFolderPath(writableSpecialFolderId(GenericDataLocation)); + if (!result.isEmpty()) { + appendTestMode(result); + result += "/cache"_L1; + } + break; + + case RuntimeLocation: + case HomeLocation: + result = QDir::homePath(); + break; + + case TempLocation: + result = QDir::tempPath(); + break; + + default: + result = sHGetKnownFolderPath(writableSpecialFolderId(type)); + if (!result.isEmpty() && isConfigLocation(type)) { + appendTestMode(result); + if (!isGenericConfigLocation(type)) + appendOrganizationAndApp(result); + } + break; + } + return result; +} + +#ifndef QT_BOOTSTRAPPED +extern QString qAppFileName(); +#endif + +QStringList QStandardPaths::standardLocations(StandardLocation type) +{ + QStringList dirs; + const QString localDir = writableLocation(type); + if (!localDir.isEmpty()) + dirs.append(localDir); + + // type-specific handling goes here + if (isConfigLocation(type)) { + QString programData = sHGetKnownFolderPath(FOLDERID_ProgramData); + if (!programData.isEmpty()) { + if (!isGenericConfigLocation(type)) + appendOrganizationAndApp(programData); + dirs.append(programData); + } +#ifndef QT_BOOTSTRAPPED + // Note: QCoreApplication::applicationDirPath(), while static, requires + // an application instance. But we might need to resolve the standard + // locations earlier than that, so we fall back to qAppFileName(). + QString applicationDirPath = qApp ? QCoreApplication::applicationDirPath() + : QFileInfo(qAppFileName()).path(); + dirs.append(applicationDirPath); + const QString dataDir = applicationDirPath + "/data"_L1; + dirs.append(dataDir); + + if (!isGenericConfigLocation(type)) { + QString appDataDir = dataDir; + appendOrganizationAndApp(appDataDir); + if (appDataDir != dataDir) + dirs.append(appDataDir); + } +#endif // !QT_BOOTSTRAPPED + } // isConfigLocation() + + return dirs; +} + +QT_END_NAMESPACE + +#endif // QT_NO_STANDARDPATHS diff --git a/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp b/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp new file mode 100644 index 00000000..f7fd2a7b --- /dev/null +++ b/qtbase/src/corelib/kernel/qeventdispatcher_win.cpp @@ -0,0 +1,908 @@ +// 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 +#include "qoperatingsystemversion.h" +#include "qpair.h" +#include "qset.h" +#include "qsocketnotifier.h" +#include "qvarlengtharray.h" + +#include "qelapsedtimer.h" +#include "qcoreapplication_p.h" +#include + +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(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(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(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(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(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(int 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 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); + int 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 %d 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 + int 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(); + int 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::registeredTimers(QObject *object) const +{ +#ifndef QT_NO_DEBUG + if (!object) { + qWarning("QEventDispatcherWin32:registeredTimers: invalid argument"); + return QList(); + } +#endif + + Q_D(const QEventDispatcherWin32); + QList 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(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(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 diff --git a/qtbase/src/corelib/kernel/qfunctions_win.cpp b/qtbase/src/corelib/kernel/qfunctions_win.cpp new file mode 100644 index 00000000..d5ce3e58 --- /dev/null +++ b/qtbase/src/corelib/kernel/qfunctions_win.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2022 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 "qfunctions_win_p.h" + +#include + +#include +#include + +#if __has_include() +# include +# define HAS_APPMODEL +#endif + +QT_BEGIN_NAMESPACE + +QComHelper::QComHelper(COINIT concurrencyModel) +{ + // Avoid overhead of initializing and using obsolete technology + concurrencyModel = COINIT(concurrencyModel | COINIT_DISABLE_OLE1DDE); + + m_initResult = CoInitializeEx(nullptr, concurrencyModel); + + if (FAILED(m_initResult)) + qErrnoWarning(m_initResult, "Failed to initialize COM library"); +} + +QComHelper::~QComHelper() +{ + if (SUCCEEDED(m_initResult)) + CoUninitialize(); +} + +/*! + \internal + Checks if the application has a \e{package identity} + + Having a \e{package identity} is required to use many modern + Windows APIs. + + https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/modernize-packaged-apps +*/ +bool qt_win_hasPackageIdentity() +{ +#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; +#else + return false; +#endif +} + +QT_END_NAMESPACE diff --git a/qtbase/src/corelib/thread/qfutex_p.h b/qtbase/src/corelib/thread/qfutex_p.h new file mode 100644 index 00000000..48f03f5e --- /dev/null +++ b/qtbase/src/corelib/thread/qfutex_p.h @@ -0,0 +1,150 @@ +// 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 +#include + +QT_BEGIN_NAMESPACE + +namespace QtDummyFutex { + constexpr inline bool futexAvailable() { return false; } + template + inline bool futexWait(Atomic &, typename Atomic::Type, int = 0) + { Q_UNREACHABLE_RETURN(false); } + template inline void futexWakeOne(Atomic &) + { Q_UNREACHABLE(); } + template inline void futexWakeAll(Atomic &) + { Q_UNREACHABLE(); } +} + +QT_END_NAMESPACE + +#if defined(Q_OS_LINUX) && !defined(QT_LINUXBASE) +// use Linux mutexes everywhere except for LSB builds +# include +# include +# include +# include +# include +# include +# define QT_ALWAYS_USE_FUTEX + +// if not defined in linux/futex.h +# define FUTEX_PRIVATE_FLAG 128 // added in v2.6.22 + +// RISC-V does not supply __NR_futex +# ifndef __NR_futex +# define __NR_futex __NR_futex_time64 +# endif + +QT_BEGIN_NAMESPACE +namespace QtLinuxFutex { + constexpr inline bool futexAvailable() { return true; } + inline int _q_futex(int *addr, int op, int val, quintptr val2 = 0, + int *addr2 = nullptr, int val3 = 0) noexcept + { + QtTsan::futexRelease(addr, addr2); + + // we use __NR_futex because some libcs (like Android's bionic) don't + // provide SYS_futex etc. + int result = syscall(__NR_futex, addr, op | FUTEX_PRIVATE_FLAG, val, val2, addr2, val3); + + QtTsan::futexAcquire(addr, addr2); + + return result; + } + template int *addr(T *ptr) + { + int *int_addr = reinterpret_cast(ptr); +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + if (sizeof(T) > sizeof(int)) + int_addr++; //We want a pointer to the least significant half +#endif + return int_addr; + } + + template + inline void futexWait(Atomic &futex, typename Atomic::Type expectedValue) + { + _q_futex(addr(&futex), FUTEX_WAIT, qintptr(expectedValue)); + } + template + inline bool futexWait(Atomic &futex, typename Atomic::Type expectedValue, qint64 nstimeout) + { + struct timespec ts; + ts.tv_sec = nstimeout / 1000 / 1000 / 1000; + ts.tv_nsec = nstimeout % (1000 * 1000 * 1000); + int r = _q_futex(addr(&futex), FUTEX_WAIT, qintptr(expectedValue), quintptr(&ts)); + return r == 0 || errno != ETIMEDOUT; + } + template inline void futexWakeOne(Atomic &futex) + { + _q_futex(addr(&futex), FUTEX_WAKE, 1); + } + template inline void futexWakeAll(Atomic &futex) + { + _q_futex(addr(&futex), FUTEX_WAKE, INT_MAX); + } + template inline + void futexWakeOp(Atomic &futex1, int wake1, int wake2, Atomic &futex2, quint32 op) + { + _q_futex(addr(&futex1), FUTEX_WAKE_OP, wake1, wake2, addr(&futex2), op); + } +} +namespace QtFutex = QtLinuxFutex; +QT_END_NAMESPACE + +#elif defined(Q_OS_WIN) +# include + +QT_BEGIN_NAMESPACE +namespace QtWindowsFutex { +#define QT_ALWAYS_USE_FUTEX +constexpr inline bool futexAvailable() { return true; } + +template +inline void futexWait(Atomic &futex, typename Atomic::Type expectedValue) +{ + QtTsan::futexRelease(&futex); + WaitOnAddress(&futex, &expectedValue, sizeof(expectedValue), INFINITE); + QtTsan::futexAcquire(&futex); +} +template +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 inline void futexWakeAll(Atomic &futex) +{ + WakeByAddressAll(&futex); +} +template inline void futexWakeOne(Atomic &futex) +{ + WakeByAddressSingle(&futex); +} +} +namespace QtFutex = QtWindowsFutex; +QT_END_NAMESPACE +#else + +QT_BEGIN_NAMESPACE +namespace QtFutex = QtDummyFutex; +QT_END_NAMESPACE +#endif + +#endif // QFUTEX_P_H diff --git a/qtbase/src/corelib/thread/qmutex.cpp b/qtbase/src/corelib/thread/qmutex.cpp new file mode 100644 index 00000000..b794d79e --- /dev/null +++ b/qtbase/src/corelib/thread/qmutex.cpp @@ -0,0 +1,922 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2016 Intel Corporation. +// Copyright (C) 2012 Olivier Goffart +// 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 +#include "qatomic.h" +#include "qelapsedtimer.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(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 bool QMutex::try_lock_for(std::chrono::duration 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 bool QMutex::try_lock_until(std::chrono::time_point 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 bool QRecursiveMutex::try_lock_for(std::chrono::duration 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 bool QRecursiveMutex::try_lock_until(std::chrono::time_point 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 QMutexLocker::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 QMutexLocker::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 QMutexLocker &QMutexLocker::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 void QMutexLocker::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 QMutexLocker::~QMutexLocker() noexcept + + Destroys the QMutexLocker and unlocks the mutex that was locked + in the constructor. + + \sa QMutex::unlock() +*/ + +/*! + \fn template bool QMutexLocker::isLocked() const noexcept + \since 6.4 + + Returns true if this QMutexLocker is currently locking its associated + mutex, or false otherwise. +*/ + +/*! + \fn template void QMutexLocker::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 void QMutexLocker::relock() noexcept + + Relocks an unlocked mutex locker. + + \sa unlock() +*/ + +/*! + \fn template QMutex *QMutexLocker::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 +{ + qint64 remainingTime = deadlineTimer.remainingTimeNSecs(); + if (remainingTime == 0) + return false; + + if (futexAvailable()) { + if (Q_UNLIKELY(remainingTime < 0)) { // 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; + + Q_FOREVER { + if (!futexWait(d_ptr, dummyFutexValue(), remainingTime)) + 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; + + // calculate the remaining time + remainingTime = deadlineTimer.remainingTimeNSecs(); + if (remainingTime <= 0) + 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 (remainingTime == 0) + 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(copy); + if (remainingTime == 0 && !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 { + Q_ASSERT(remainingTime >= 0); + // 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(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 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 new file mode 100644 index 00000000..aabb66fa --- /dev/null +++ b/qtbase/src/corelib/thread/qmutex_p.h @@ -0,0 +1,94 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2016 Intel Corporation. +// Copyright (C) 2012 Olivier Goffart +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QMUTEX_P_H +#define QMUTEX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience of +// qmutex.cpp and qmutex_unix.cpp. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +#include "qplatformdefs.h" // _POSIX_VERSION + +#if defined(Q_OS_DARWIN) +# include +#elif defined(Q_OS_UNIX) +# include +#endif + +struct timespec; + +QT_BEGIN_NAMESPACE + +// We manipulate the pointer to this class in inline, atomic code, +// so syncqt mustn't mark them as private, so ELFVERSION:ignore-next +class QMutexPrivate +{ +public: + ~QMutexPrivate(); + QMutexPrivate(); + + bool wait(QDeadlineTimer timeout = QDeadlineTimer::Forever); + void wakeUp() noexcept; + + // Control the lifetime of the privates + QAtomicInt refCount; + int id; + + bool ref() + { + Q_ASSERT(refCount.loadRelaxed() >= 0); + int c; + do { + c = refCount.loadRelaxed(); + if (c == 0) + return false; + } while (!refCount.testAndSetRelaxed(c, c + 1)); + Q_ASSERT(refCount.loadRelaxed() >= 0); + return true; + } + void deref() + { + Q_ASSERT(refCount.loadRelaxed() >= 0); + if (!refCount.deref()) + release(); + Q_ASSERT(refCount.loadRelaxed() >= 0); + } + void release(); + static QMutexPrivate *allocate(); + + QAtomicInt waiters; // Number of threads waiting on this mutex. (may be offset by -BigNumber) + QAtomicInt possiblyUnlocked; /* Boolean indicating that a timed wait timed out. + When it is true, a reference is held. + It is there to avoid a race that happens if unlock happens right + when the mutex is unlocked. + */ + enum { BigNumber = 0x100000 }; //Must be bigger than the possible number of waiters (number of threads) + void derefWaiters(int value) noexcept; + + //platform specific stuff +#if defined(Q_OS_DARWIN) + semaphore_t mach_semaphore; +#elif defined(Q_OS_UNIX) + sem_t semaphore; +#endif +}; + +QT_END_NAMESPACE + +#endif // QMUTEX_P_H diff --git a/qtbase/src/gui/rhi/qrhid3d11.cpp b/qtbase/src/gui/rhi/qrhid3d11.cpp new file mode 100644 index 00000000..8c4eac9b --- /dev/null +++ b/qtbase/src/gui/rhi/qrhid3d11.cpp @@ -0,0 +1,5270 @@ +// 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 +#include +#include +#include +#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, ¶ms); + \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(importParams->dev); + ID3D11DeviceContext *ctx = reinterpret_cast(importParams->context); + if (SUCCEEDED(ctx->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast(&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 +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(&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(&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(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; + driverInfoStruct.deviceName = name.toUtf8(); + driverInfoStruct.deviceId = desc.DeviceId; + driverInfoStruct.vendorId = desc.VendorId; + 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 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; + } + + // 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 { + qWarning("D3D11 smoke test failed (failed to create vertex shader)"); + ctx->Release(); + return false; + } + + const bool supports11_1 = SUCCEEDED(ctx->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast(&context))); + ctx->Release(); + if (!supports11_1) { + qWarning("ID3D11DeviceContext1 not supported"); + 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) { + qWarning("Constant buffer offsetting is not supported by the driver"); + return false; + } + } else { + qWarning("Failed to query D3D11_FEATURE_D3D11_OPTIONS"); + return false; + } + } else { + Q_ASSERT(dev && context); + featureLevel = dev->GetFeatureLevel(); + IDXGIDevice *dxgiDev = nullptr; + if (SUCCEEDED(dev->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast(&dxgiDev)))) { + IDXGIAdapter *adapter = nullptr; + if (SUCCEEDED(dxgiDev->GetAdapter(&adapter))) { + DXGI_ADAPTER_DESC desc; + adapter->GetDesc(&desc); + adapterLuid = desc.AdapterLuid; + driverInfoStruct.deviceName = QString::fromUtf16(reinterpret_cast(desc.Description)).toUtf8(); + driverInfoStruct.deviceId = desc.DeviceId; + driverInfoStruct.vendorId = desc.VendorId; + adapter->Release(); + } + dxgiDev->Release(); + } + qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev); + } + + if (FAILED(context->QueryInterface(__uuidof(ID3DUserDefinedAnnotation), reinterpret_cast(&annotations)))) + annotations = nullptr; + + if (flags.testFlag(QRhi::EnableTimestamps)) { + ofr.timestamps.prepare(2, this); + // timestamp queries are optional so we can go on even if they failed + } + + 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(); + + ofr.timestamps.destroy(); + + 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(&debug)))) { + debug->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL); + debug->Release(); + } +} + +QList QRhiD3D11::supportedSampleCounts() const +{ + return { 1, 2, 4, 8 }; +} + +DXGI_SAMPLE_DESC QRhiD3D11::effectiveSampleCount(int sampleCount) const +{ + DXGI_SAMPLE_DESC desc; + desc.Count = 1; + desc.Quality = 0; + + // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1. + int s = qBound(1, sampleCount, 64); + + if (!supportedSampleCounts().contains(s)) { + qWarning("Attempted to set unsupported sample count %d", sampleCount); + return desc; + } + + 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; + 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(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(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); + // no timestampSwapChain, in order to avoid timestamp mess + 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; +} + +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[currentFrameSlot]) { + double elapsedSec = 0; + if (swapChainD->timestamps.tryQueryTimestamps(currentFrameSlot, context, &elapsedSec)) + swapChainD->cb.lastGpuTime = elapsedSec; + } + + 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; + + ID3D11Query *tsDisjoint = swapChainD->timestamps.disjointQuery[currentFrameSlot]; + const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot; + ID3D11Query *tsStart = swapChainD->timestamps.query[tsIdx]; + ID3D11Query *tsEnd = swapChainD->timestamps.query[tsIdx + 1]; + const bool recordTimestamps = tsDisjoint && tsStart && tsEnd && !swapChainD->timestamps.active[currentFrameSlot]; + + // send all commands to the context + if (recordTimestamps) + executeCommandBuffer(&swapChainD->cb, swapChainD); + else + 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 resolve as well + if (recordTimestamps) { + context->End(tsEnd); + context->End(tsDisjoint); + swapChainD->timestamps.active[currentFrameSlot] = true; + } + + 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 (ofr.timestamps.active[ofr.timestampIdx]) { + double elapsedSec = 0; + if (ofr.timestamps.tryQueryTimestamps(ofr.timestampIdx, context, &elapsedSec)) + ofr.cbWrapper.lastGpuTime = elapsedSec; + } + + return QRhi::FrameOpSuccess; +} + +QRhi::FrameOpResult QRhiD3D11::endOffscreenFrame(QRhi::EndFrameFlags flags) +{ + Q_UNUSED(flags); + ofr.active = false; + + ID3D11Query *tsDisjoint = ofr.timestamps.disjointQuery[ofr.timestampIdx]; + ID3D11Query *tsStart = ofr.timestamps.query[ofr.timestampIdx * 2]; + ID3D11Query *tsEnd = ofr.timestamps.query[ofr.timestampIdx * 2 + 1]; + const bool recordTimestamps = tsDisjoint && tsStart && tsEnd && !ofr.timestamps.active[ofr.timestampIdx]; + if (recordTimestamps) { + context->Begin(tsDisjoint); + context->End(tsStart); // record timestamp; no Begin() for D3D11_QUERY_TIMESTAMP + } + + executeCommandBuffer(&ofr.cbWrapper); + context->Flush(); + + finishActiveReadbacks(); + + if (recordTimestamps) { + context->End(tsEnd); + context->End(tsDisjoint); + ofr.timestamps.active[ofr.timestampIdx] = true; + ofr.timestampIdx = (ofr.timestampIdx + 1) % 2; + } + + 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_R24_UNORM_X8_TYPELESS; + case QRhiTexture::D24S8: + return DXGI_FORMAT_D24_UNORM_S8_UINT; + 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); // no timestampSwapChain, in order to avoid timestamp mess + 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, 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(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(); +} + +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; + } +} + +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(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 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 buffers; + QVarLengthArray textures; + QVarLengthArray samplers; + QVarLengthArray 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 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 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 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 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 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 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 nativeBindingVert = mapBinding(b->binding, RBM_VERTEX, nativeResourceBindingMaps); + const QPair nativeBindingHull = mapBinding(b->binding, RBM_HULL, nativeResourceBindingMaps); + const QPair nativeBindingDomain = mapBinding(b->binding, RBM_DOMAIN, nativeResourceBindingMaps); + const QPair nativeBindingGeom = mapBinding(b->binding, RBM_GEOMETRY, nativeResourceBindingMaps); + const QPair nativeBindingFrag = mapBinding(b->binding, RBM_FRAGMENT, nativeResourceBindingMaps); + const QPair 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 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 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 *originalBindings, + const QRhiBatchedBindings *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 nullbufs(count); + for (int i = 0; i < count; ++i) + nullbufs[i] = nullptr; + QVarLengthArray nullstrides(count); + for (int i = 0; i < count; ++i) + nullstrides[i] = 0; + QVarLengthArray 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 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 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, QD3D11SwapChain *timestampSwapChain) +{ + quint32 stencilRef = 0; + float blendConstants[] = { 1, 1, 1, 1 }; + enum ActiveShaderMask { + VSMaskBit = 0x01, + HSMaskBit = 0x02, + DSMaskBit = 0x04, + GSMaskBit = 0x08, + PSMaskBit = 0x10 + }; + int currentShaderMask = 0xFF; + + if (timestampSwapChain) { + const int currentFrameSlot = timestampSwapChain->currentFrameSlot; + ID3D11Query *tsDisjoint = timestampSwapChain->timestamps.disjointQuery[currentFrameSlot]; + const int tsIdx = QD3D11SwapChain::BUFFER_COUNT * currentFrameSlot; + ID3D11Query *tsStart = timestampSwapChain->timestamps.query[tsIdx]; + if (tsDisjoint && tsStart && !timestampSwapChain->timestamps.active[currentFrameSlot]) { + // 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. + context->Begin(tsDisjoint); + QD3D11RenderTargetData *rtD = rtData(×tampSwapChain->rt); + context->OMSetRenderTargets(UINT(rtD->colorAttCount), rtD->colorAttCount ? rtD->rtv : nullptr, rtD->dsv); + context->End(tsStart); // just record a timestamp, no Begin needed + } + } + + for (auto it = cbD->commands.cbegin(), end = cbD->commands.cend(); it != end; ++it) { + const QD3D11CommandBuffer::Command &cmd(*it); + switch (cmd.cmd) { + 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( + 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(QString::fromLatin1(cmd.args.debugMark.s).utf16())); + break; + case QD3D11CommandBuffer::Command::DebugMarkEnd: + annotations->EndEvent(); + break; + case QD3D11CommandBuffer::Command::DebugMarkMsg: + annotations->SetMarker(reinterpret_cast(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(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->effectiveSampleCount(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_R24_UNORM_X8_TYPELESS; + 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->effectiveSampleCount(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(src.object); + else if (m_flags.testFlags(OneDimensional)) + tex1D = reinterpret_cast(src.object); + else + tex = reinterpret_cast(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 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(m_desc, &d.currentResIdList); + + rhiD->registerResource(this); + return true; +} + +QSize QD3D11TextureRenderTarget::pixelSize() const +{ + if (!QRhiRenderTargetAttachmentTracker::isUpToDate(m_desc, d.currentResIdList)) + const_cast(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 +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(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->effectiveSampleCount(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(cacheIt->s); + vs.shader->AddRef(); + vsByteCode = cacheIt->bytecode; + vs.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap; + break; + case QRhiShaderStage::TessellationControl: + hs.shader = static_cast(cacheIt->s); + hs.shader->AddRef(); + hs.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap; + break; + case QRhiShaderStage::TessellationEvaluation: + ds.shader = static_cast(cacheIt->s); + ds.shader->AddRef(); + ds.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap; + break; + case QRhiShaderStage::Geometry: + gs.shader = static_cast(cacheIt->s); + gs.shader->AddRef(); + gs.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap; + break; + case QRhiShaderStage::Fragment: + fs.shader = static_cast(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 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 as the + // semantic, except for matrices that are unrolled into consecutive + // vec2/3/4s attributes and need TEXCOORD_ 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(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 QD3D11Timestamps::prepare(int pairCount, QRhiD3D11 *rhiD) +{ + // Creates the query objects if not yet done, but otherwise calling this + // function is expected to be a no-op. + + Q_ASSERT(pairCount <= MAX_TIMESTAMP_PAIRS); + D3D11_QUERY_DESC queryDesc = {}; + for (int i = 0; i < pairCount; ++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 = pairCount * 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; + } + } + } + } + this->pairCount = pairCount; + return true; +} + +void QD3D11Timestamps::destroy() +{ + for (int i = 0; i < MAX_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 = MAX_TIMESTAMP_PAIRS * i + j; + if (query[idx]) { + query[idx]->Release(); + query[idx] = nullptr; + } + } + } +} + +bool QD3D11Timestamps::tryQueryTimestamps(int idx, ID3D11DeviceContext *context, double *elapsedSec) +{ + bool result = false; + if (!active[idx]) + return result; + + ID3D11Query *tsDisjoint = disjointQuery[idx]; + const int tsIdx = pairCount * idx; + ID3D11Query *tsStart = query[tsIdx]; + ID3D11Query *tsEnd = query[tsIdx + 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, ×tamps[1], sizeof(quint64), D3D11_ASYNC_GETDATA_DONOTFLUSH) == S_OK; + // this above is often not ready, not even in frame_where_recorded+2, + // not clear why. so make the whole thing async and do not touch the + // queries until they are finally all available in frame this+2 or + // this+4 or ... + ok &= context->GetData(tsStart, ×tamps[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[idx] = false; + } // else leave active set, will retry in a subsequent beginFrame or similar + + return result; +} + +QD3D11SwapChain::QD3D11SwapChain(QRhiImplementation *rhi) + : QRhiSwapChain(rhi), + rt(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 (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); +} + +QRhiCommandBuffer *QD3D11SwapChain::currentFrameCommandBuffer() +{ + return &cb; +} + +QRhiRenderTarget *QD3D11SwapChain::currentFrameRenderTarget() +{ + return &rt; +} + +QSize QD3D11SwapChain::surfacePixelSize() +{ + Q_ASSERT(m_window); + return m_window->size() * m_window->devicePixelRatio(); +} + +static bool output6ForWindow(QWindow *w, IDXGIAdapter1 *adapter, IDXGIOutput6 **result) +{ + bool ok = false; + QRect wr = w->geometry(); + wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio()); + const QPoint center = wr.center(); + IDXGIOutput *currentOutput = nullptr; + IDXGIOutput *output = nullptr; + for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) { + DXGI_OUTPUT_DESC desc; + output->GetDesc(&desc); + const RECT r = desc.DesktopCoordinates; + const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1)); + if (dr.contains(center)) { + currentOutput = output; + break; + } else { + output->Release(); + } + } + if (currentOutput) { + ok = SUCCEEDED(currentOutput->QueryInterface(__uuidof(IDXGIOutput6), reinterpret_cast(result))); + currentOutput->Release(); + } + return ok; +} + +static bool outputDesc1ForWindow(QWindow *w, IDXGIAdapter1 *adapter, DXGI_OUTPUT_DESC1 *result) +{ + bool ok = false; + IDXGIOutput6 *out6 = nullptr; + if (output6ForWindow(w, adapter, &out6)) { + ok = SUCCEEDED(out6->GetDesc1(result)); + out6->Release(); + } + return ok; +} + +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 (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(); + if (m_window) { + QRHI_RES_RHI(QRhiD3D11); + DXGI_OUTPUT_DESC1 hdrOutputDesc; + if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) { + info.isHardCodedDefaults = false; + info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits; + info.limits.luminanceInNits.minLuminance = hdrOutputDesc.MinLuminance; + info.limits.luminanceInNits.maxLuminance = hdrOutputDesc.MaxLuminance; + } + } + 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; + + // 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(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->effectiveSampleCount(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 (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; + + 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(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(&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", + qPrintable(QSystemError::windowsComString(hr))); + 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(&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; + } + + // 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 (rhiD->rhiFlags.testFlag(QRhi::EnableTimestamps)) { + timestamps.prepare(BUFFER_COUNT, 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 new file mode 100644 index 00000000..f3884f68 --- /dev/null +++ b/qtbase/src/gui/rhi/qrhid3d11_p.h @@ -0,0 +1,853 @@ +// 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 +#include + +#include +#include +#include + +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 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 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 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; + + struct StageUniformBufferBatches { + bool present = false; + QRhiBatchedBindings ubufs; + QRhiBatchedBindings ubuforigbindings; + QRhiBatchedBindings ubufoffsets; + QRhiBatchedBindings 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 samplers; + QRhiBatchedBindings shaderresources; + void finish() { + present = samplers.finish(); + shaderresources.finish(); + } + void clear() { + samplers.clear(); + shaderresources.clear(); + } + }; + + struct StageUavBatches { + bool present = false; + QRhiBatchedBindings 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 { + 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 { + 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 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 dataRetainPool; + QVarLengthArray bufferDataRetainPool; + QVarLengthArray 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(dataRetainPool.last().constData()); + } + const uchar *retainBufferData(const QRhiBufferData &data) { + bufferDataRetainPool.append(data); + return reinterpret_cast(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)); + } +}; + +static const int QD3D11_SWAPCHAIN_BUFFER_COUNT = 2; + +struct QD3D11Timestamps +{ + static const int MAX_TIMESTAMP_PAIRS = QD3D11_SWAPCHAIN_BUFFER_COUNT; + bool active[MAX_TIMESTAMP_PAIRS] = {}; + ID3D11Query *disjointQuery[MAX_TIMESTAMP_PAIRS] = {}; + ID3D11Query *query[MAX_TIMESTAMP_PAIRS * 2] = {}; + int pairCount = 0; + + bool prepare(int pairCount, 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; + + 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; + QD3D11CommandBuffer cb; + DXGI_FORMAT colorFormat; + DXGI_FORMAT srgbAdjustedColorFormat; + IDXGISwapChain *swapChain = nullptr; + UINT swapChainFlags = 0; + ID3D11Texture2D *backBufferTex; + ID3D11RenderTargetView *backBufferRtv; + static const int BUFFER_COUNT = QD3D11_SWAPCHAIN_BUFFER_COUNT; + 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; + QD3D11Timestamps timestamps; +}; + +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 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, QD3D11SwapChain *timestampSwapChain = nullptr); + DXGI_SAMPLE_DESC effectiveSampleCount(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; + QD3D11Timestamps timestamps; + int timestampIdx = 0; + } ofr; + + struct TextureReadback { + QRhiReadbackDescription desc; + QRhiReadbackResult *result; + ID3D11Texture2D *stagingTex; + quint32 byteSize; + quint32 bpl; + QSize pixelSize; + QRhiTexture::Format format; + }; + QVarLengthArray activeTextureReadbacks; + struct BufferReadback { + QRhiReadbackResult *result; + quint32 byteSize; + ID3D11Buffer *stagingBuf; + }; + QVarLengthArray 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 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 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 new file mode 100644 index 00000000..38e4fbd2 --- /dev/null +++ b/qtbase/src/gui/rhi/qrhid3d12.cpp @@ -0,0 +1,6166 @@ +// 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 "qshader.h" +#include +#include +#include +#include +#include +#include "qrhid3dhelpers_p.h" +#include "cs_mipmap_p.h" + +#if __has_include() +#include +#define QRHI_D3D12_HAS_OLD_PIX +#endif + +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, ¶ms); + \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 ID3D12GraphicsCommandList 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) { + dev = reinterpret_cast(importParams->dev); + importedDevice = true; + } + if (importParams->commandQueue) { + cmdQueue = reinterpret_cast(importParams->commandQueue); + importedCommandQueue = true; + } + minimumFeatureLevel = D3D_FEATURE_LEVEL(importParams->minimumFeatureLevel); + adapterLuid.LowPart = importParams->adapterLuidLow; + adapterLuid.HighPart = importParams->adapterLuidHigh; + } +} + +template +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(&dxgiFactory)); + if (FAILED(hr)) { + 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(&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(&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(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; + driverInfoStruct.deviceName = name.toUtf8(); + driverInfoStruct.deviceId = desc.DeviceId; + driverInfoStruct.vendorId = desc.VendorId; + 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(ID3D12Device), + reinterpret_cast(&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); + adapter->Release(); + if (desc.AdapterLuid.LowPart == adapterLuid.LowPart + && desc.AdapterLuid.HighPart == adapterLuid.HighPart) + { + driverInfoStruct.deviceName = QString::fromUtf16(reinterpret_cast(desc.Description)).toUtf8(); + driverInfoStruct.deviceId = desc.DeviceId; + driverInfoStruct.vendorId = desc.VendorId; + break; + } + } + qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev); + } + + if (debugLayer) { + ID3D12InfoQueue *infoQueue; + if (SUCCEEDED(dev->QueryInterface(__uuidof(ID3D12InfoQueue), reinterpret_cast(&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(&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(&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(&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; + } + } + + 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; + } + + 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; + } + } + + 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) { + 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 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 false; // ### + 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 + } + 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); + } +} + +void QRhiD3D12::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; + } + } + } + visitorData.cbufs[s].append({ bufD->handles[currentFrameSlot], offset }); +} + +void QRhiD3D12::visitTexture(QD3D12Stage s, + const QRhiShaderResourceBinding::TextureAndSampler &d, + int) +{ + QD3D12Texture *texD = QRHI_RES(QD3D12Texture, d.tex); + visitorData.srvs[s].append(texD->srv); +} + +void QRhiD3D12::visitSampler(QD3D12Stage s, + const QRhiShaderResourceBinding::TextureAndSampler &d, + int) +{ + QD3D12Sampler *samplerD = QRHI_RES(QD3D12Sampler, d.sampler); + visitorData.samplers[s].append(samplerD->lookupOrCreateShaderVisibleDescriptor()); +} + +void QRhiD3D12::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 QRhiD3D12::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->sortedBindings.size(); i != ie; ++i) { + const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->sortedBindings[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); + + visitorData = {}; + + using namespace std::placeholders; + visitor.uniformBuffer = std::bind(&QRhiD3D12::visitUniformBuffer, this, _1, _2, _3, _4, dynamicOffsetCount, dynamicOffsets); + visitor.texture = std::bind(&QRhiD3D12::visitTexture, this, _1, _2, _3); + visitor.sampler = std::bind(&QRhiD3D12::visitSampler, this, _1, _2, _3); + visitor.storageBuffer = std::bind(&QRhiD3D12::visitStorageBuffer, this, _1, _2, _3, _4); + visitor.storageImage = std::bind(&QRhiD3D12::visitStorageImage, this, _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 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(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(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(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(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(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) +{ + Q_UNUSED(cb); + return 0; +} + +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 }; + + // 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 + + 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); + + ID3D12GraphicsCommandList *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; + } + 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); + + 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]; + ID3D12GraphicsCommandList *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); + + 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 = ¤tSwapChain->cbWrapper; + } + if (!cbD) + return QRhi::FrameOpError; + + Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass); + + ID3D12GraphicsCommandList *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(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 srcSubresource = calcSubresource(0, UINT(colorAtt.layer()), 1); + const UINT dstSubresource = calcSubresource(UINT(colorAtt.resolveLevel()), + UINT(colorAtt.resolveLayer()), + 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(&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 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(&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(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 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 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 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->sortedBindings.count(); bindingIdx != bindingCount; ++bindingIdx) { + const QRhiShaderResourceBinding &b(srb->sortedBindings[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; + + // 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(&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(&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 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::effectiveSampleCount(int sampleCount, DXGI_FORMAT format) const +{ + DXGI_SAMPLE_DESC desc; + desc.Count = 1; + desc.Quality = 0; + + // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1. + int s = qBound(1, sampleCount, 64); + + if (!supportedSampleCounts().contains(s)) { + qWarning("Attempted to set unsupported sample count %d", sampleCount); + return desc; + } + + if (s > 1) { + D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msaaInfo = {}; + msaaInfo.Format = format; + msaaInfo.SampleCount = 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(ID3D12GraphicsCommandList **cmdList) +{ + ID3D12CommandAllocator *cmdAlloc = cmdAllocators[currentFrameSlot]; + if (!*cmdList) { + HRESULT hr = dev->CreateCommandList(0, + D3D12_COMMAND_LIST_TYPE_DIRECT, + cmdAlloc, + nullptr, + __uuidof(ID3D12GraphicsCommandList), + reinterpret_cast(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 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(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(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(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(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 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(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, 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(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(&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(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(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(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->effectiveSampleCount(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(&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->effectiveSampleCount(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(&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(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->effectiveSampleCount(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(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)) { + 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(&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(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; + } + 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 = 1; + } 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 = 1; + } 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 = 1; + } else { + rtvDesc.ViewDimension = D3D12_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 = D3D12_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 = 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(m_desc, &d.currentResIdList); + + rhiD->registerResource(this); + return true; +} + +QSize QD3D12TextureRenderTarget::pixelSize() const +{ + if (!QRhiRenderTargetAttachmentTracker::isUpToDate(m_desc, d.currentResIdList)) + const_cast(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() +{ + sortedBindings.clear(); + + QRHI_RES_RHI(QRhiD3D12); + if (rhiD) + rhiD->unregisterResource(this); +} + +bool QD3D12ShaderResourceBindings::create() +{ + if (!sortedBindings.isEmpty()) + destroy(); + + QRHI_RES_RHI(QRhiD3D12); + 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); + + 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; + } + } + + // 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) +{ + 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); + + 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; + 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 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(&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 now we mirror exactly what's done in the D3D11 backend, meaning we use +// the old shader compiler (so like fxc, not dxc) to generate shader model 5.0 +// output. Some day this should be moved to the new compiler and DXIL. + +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'; +} + +static QByteArray compileHlslShaderSource(const QShader &shader, + QShader::Variant shaderVariant, + UINT 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; + } + + static const pD3DCompile d3dCompile = QRhiD3D::resolveD3DCompile(); + if (!d3dCompile) { + 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(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; +} + +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; + UINT compileFlags = 0; + if (m_flags.testFlag(CompileShadersWithDebugInfo)) + compileFlags |= D3DCOMPILE_DEBUG; + const QByteArray bytecode = compileHlslShaderSource(shaderStage.shader(), + shaderStage.shaderVariant(), + compileFlags, + &error, + &shaderKey); + if (bytecode.isEmpty()) { + qWarning("HLSL compute 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->effectiveSampleCount(m_sampleCount, DXGI_FORMAT(rpD->colorFormat[0])); + + D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; + psoDesc.pRootSignature = rootSig; + for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) { + const int d3dStage = qd3d12_stage(shaderStage.type()); + switch (d3dStage) { + case VS: + psoDesc.VS.pShaderBytecode = shaderBytecode[d3dStage].constData(); + psoDesc.VS.BytecodeLength = shaderBytecode[d3dStage].size(); + break; + case HS: + psoDesc.HS.pShaderBytecode = shaderBytecode[d3dStage].constData(); + psoDesc.HS.BytecodeLength = shaderBytecode[d3dStage].size(); + break; + case DS: + psoDesc.DS.pShaderBytecode = shaderBytecode[d3dStage].constData(); + psoDesc.DS.BytecodeLength = shaderBytecode[d3dStage].size(); + break; + case GS: + psoDesc.GS.pShaderBytecode = shaderBytecode[d3dStage].constData(); + psoDesc.GS.BytecodeLength = shaderBytecode[d3dStage].size(); + break; + case PS: + psoDesc.PS.pShaderBytecode = shaderBytecode[d3dStage].constData(); + psoDesc.PS.BytecodeLength = shaderBytecode[d3dStage].size(); + break; + default: + Q_UNREACHABLE(); + break; + } + } + + psoDesc.BlendState.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); + psoDesc.BlendState.RenderTarget[i] = blend; + } + if (m_targetBlends.isEmpty()) { + D3D12_RENDER_TARGET_BLEND_DESC blend = {}; + blend.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; + psoDesc.BlendState.RenderTarget[0] = blend; + } + + psoDesc.SampleMask = 0xFFFFFFFF; + + psoDesc.RasterizerState.FillMode = toD3DFillMode(m_polygonMode); + psoDesc.RasterizerState.CullMode = toD3DCullMode(m_cullMode); + psoDesc.RasterizerState.FrontCounterClockwise = m_frontFace == CCW; + psoDesc.RasterizerState.DepthBias = m_depthBias; + psoDesc.RasterizerState.SlopeScaledDepthBias = m_slopeScaledDepthBias; + psoDesc.RasterizerState.DepthClipEnable = TRUE; + psoDesc.RasterizerState.MultisampleEnable = sampleDesc.Count > 1; + + psoDesc.DepthStencilState.DepthEnable = m_depthTest; + psoDesc.DepthStencilState.DepthWriteMask = m_depthWrite ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO; + psoDesc.DepthStencilState.DepthFunc = toD3DCompareOp(m_depthOp); + psoDesc.DepthStencilState.StencilEnable = m_stencilTest; + if (m_stencilTest) { + psoDesc.DepthStencilState.StencilReadMask = UINT8(m_stencilReadMask); + psoDesc.DepthStencilState.StencilWriteMask = UINT8(m_stencilWriteMask); + psoDesc.DepthStencilState.FrontFace.StencilFailOp = toD3DStencilOp(m_stencilFront.failOp); + psoDesc.DepthStencilState.FrontFace.StencilDepthFailOp = toD3DStencilOp(m_stencilFront.depthFailOp); + psoDesc.DepthStencilState.FrontFace.StencilPassOp = toD3DStencilOp(m_stencilFront.passOp); + psoDesc.DepthStencilState.FrontFace.StencilFunc = toD3DCompareOp(m_stencilFront.compareOp); + psoDesc.DepthStencilState.BackFace.StencilFailOp = toD3DStencilOp(m_stencilBack.failOp); + psoDesc.DepthStencilState.BackFace.StencilDepthFailOp = toD3DStencilOp(m_stencilBack.depthFailOp); + psoDesc.DepthStencilState.BackFace.StencilPassOp = toD3DStencilOp(m_stencilBack.passOp); + psoDesc.DepthStencilState.BackFace.StencilFunc = toD3DCompareOp(m_stencilBack.compareOp); + } + + QVarLengthArray 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 as the + // semantic, except for matrices that are unrolled into consecutive + // vec2/3/4s attributes and need TEXCOORD_ 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); + } + } + if (!inputDescs.isEmpty()) { + psoDesc.InputLayout.pInputElementDescs = inputDescs.constData(); + psoDesc.InputLayout.NumElements = inputDescs.count(); + } + + psoDesc.PrimitiveTopologyType = toD3DTopologyType(m_topology); + topology = toD3DTopology(m_topology, m_patchControlPointCount); + + psoDesc.NumRenderTargets = rpD->colorAttachmentCount; + for (int i = 0; i < rpD->colorAttachmentCount; ++i) + psoDesc.RTVFormats[i] = DXGI_FORMAT(rpD->colorFormat[i]); + psoDesc.DSVFormat = rpD->hasDepthStencil ? DXGI_FORMAT(rpD->dsFormat) : DXGI_FORMAT_UNKNOWN; + psoDesc.SampleDesc = sampleDesc; + + ID3D12PipelineState *pso = nullptr; + HRESULT hr = rhiD->dev->CreateGraphicsPipelineState(&psoDesc, + __uuidof(ID3D12PipelineState), + reinterpret_cast(&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; + UINT compileFlags = 0; + if (m_flags.testFlag(CompileShadersWithDebugInfo)) + compileFlags |= D3DCOMPILE_DEBUG; + 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; + } + + D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {}; + psoDesc.pRootSignature = rootSig; + psoDesc.CS.pShaderBytecode = shaderBytecode.constData(); + psoDesc.CS.BytecodeLength = shaderBytecode.size(); + ID3D12PipelineState *pso = nullptr; + HRESULT hr = rhiD->dev->CreateComputePipelineState(&psoDesc, + __uuidof(ID3D12PipelineState), + reinterpret_cast(&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 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), + 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 (!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; +} + +QSize QD3D12SwapChain::surfacePixelSize() +{ + Q_ASSERT(m_window); + return m_window->size() * m_window->devicePixelRatio(); +} + +static bool output6ForWindow(QWindow *w, IDXGIAdapter1 *adapter, IDXGIOutput6 **result) +{ + bool ok = false; + QRect wr = w->geometry(); + wr = QRect(wr.topLeft() * w->devicePixelRatio(), wr.size() * w->devicePixelRatio()); + const QPoint center = wr.center(); + IDXGIOutput *currentOutput = nullptr; + IDXGIOutput *output = nullptr; + for (UINT i = 0; adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND; ++i) { + DXGI_OUTPUT_DESC desc; + output->GetDesc(&desc); + const RECT r = desc.DesktopCoordinates; + const QRect dr(QPoint(r.left, r.top), QPoint(r.right - 1, r.bottom - 1)); + if (dr.contains(center)) { + currentOutput = output; + break; + } else { + output->Release(); + } + } + if (currentOutput) { + ok = SUCCEEDED(currentOutput->QueryInterface(__uuidof(IDXGIOutput6), reinterpret_cast(result))); + currentOutput->Release(); + } + return ok; +} + +static bool outputDesc1ForWindow(QWindow *w, IDXGIAdapter1 *adapter, DXGI_OUTPUT_DESC1 *result) +{ + bool ok = false; + IDXGIOutput6 *out6 = nullptr; + if (output6ForWindow(w, adapter, &out6)) { + ok = SUCCEEDED(out6->GetDesc1(result)); + out6->Release(); + } + return ok; +} + +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 (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(); + if (m_window) { + QRHI_RES_RHI(QRhiD3D12); + DXGI_OUTPUT_DESC1 hdrOutputDesc; + if (outputDesc1ForWindow(m_window, rhiD->activeAdapter, &hdrOutputDesc)) { + info.isHardCodedDefaults = false; + info.limitsType = QRhiSwapChainHdrInfo::LuminanceInNits; + info.limits.luminanceInNits.minLuminance = hdrOutputDesc.MinLuminance; + info.limits.luminanceInNits.maxLuminance = hdrOutputDesc.MaxLuminance; + } + } + 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 (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->effectiveSampleCount(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(window->winId()); + HRESULT hr; + QRHI_RES_RHI(QRhiD3D12); + + 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 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; + 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; + + 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(&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", qPrintable(QSystemError::windowsComString(hr))); + 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(&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(&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 (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(&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; + + if (needsRegistration) { + rhiD->swapchains.insert(this); + rhiD->registerResource(this); + } + + return true; +} + +QT_END_NAMESPACE diff --git a/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp b/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp new file mode 100644 index 00000000..f9b36b48 --- /dev/null +++ b/qtbase/src/gui/text/windows/qwindowsfontdatabasebase.cpp @@ -0,0 +1,921 @@ +// 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 +#include + +#if QT_CONFIG(directwrite) +# if QT_CONFIG(directwrite3) +# include +# else +# include +# endif +# include +# 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(tagName.constData())); + const size_t fontDataSize = m_fontData.size(); + if (Q_UNLIKELY(fontDataSize < sizeof(OffsetSubTable))) + return nullptr; + + OffsetSubTable *offsetSubTable = reinterpret_cast(m_fontData.data()); + TableDirectory *tableDirectory = reinterpret_cast(offsetSubTable + 1); + + const size_t tableCount = qFromBigEndian(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(directoryEntry); + if (nameTableDirectoryEntry == nullptr) + nameTableDirectoryEntry = static_cast(tableDirectoryEntry("name")); + + if (nameTableDirectoryEntry != nullptr) { + quint32 offset = qFromBigEndian(nameTableDirectoryEntry->offset); + if (Q_UNLIKELY(quint32(m_fontData.size()) < offset + sizeof(NameTable))) + return QString(); + + NameTable *nameTable = reinterpret_cast(m_fontData.data() + offset); + NameRecord *nameRecord = reinterpret_cast(nameTable + 1); + + quint16 nameTableCount = qFromBigEndian(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(nameRecord->nameID) == 1 + && qFromBigEndian(nameRecord->platformID) == 3 // Windows + && qFromBigEndian(nameRecord->languageID) == 0x0409) { // US English + quint16 stringOffset = qFromBigEndian(nameTable->stringOffset); + quint16 nameOffset = qFromBigEndian(nameRecord->offset); + quint16 nameLength = qFromBigEndian(nameRecord->length); + + if (Q_UNLIKELY(quint32(m_fontData.size()) < offset + stringOffset + nameOffset + nameLength)) + return QString(); + + const void *ptr = reinterpret_cast(nameTable) + + stringOffset + + nameOffset; + + const quint16 *s = reinterpret_cast(ptr); + const quint16 *e = s + nameLength / sizeof(quint16); + while (s != e) + name += QChar( qFromBigEndian(*s++)); + break; + } + } + } + + return name; +} + +void QWindowsFontDatabaseBase::EmbeddedFont::updateFromOS2Table(QFontEngine *fontEngine) +{ + TableDirectory *os2TableEntry = static_cast(tableDirectoryEntry("OS/2")); + if (os2TableEntry != nullptr) { + const OS2Table *os2Table = + reinterpret_cast(m_fontData.constData() + + qFromBigEndian(os2TableEntry->offset)); + + bool italic = qFromBigEndian(os2Table->selection) & (1 << 0); + bool oblique = qFromBigEndian(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(os2Table->weightClass); + } +} + +QString QWindowsFontDatabaseBase::EmbeddedFont::changeFamilyName(const QString &newFamilyName) +{ + TableDirectory *nameTableDirectoryEntry = static_cast(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(newNameTable.data()); + nameTable->count = qbswap(requiredRecordCount); + nameTable->stringOffset = qbswap(sizeOfHeader); + + NameRecord *nameRecord = reinterpret_cast(nameTable + 1); + for (int i = 0; i < requiredRecordCount; ++i, nameRecord++) { + nameRecord->nameID = qbswap(nameIds[i]); + nameRecord->encodingID = qbswap(1); + nameRecord->languageID = qbswap(0x0409); + nameRecord->platformID = qbswap(3); + nameRecord->length = qbswap(newFamilyNameSize); + + // Special case for sub-family + if (nameIds[i] == 4) { + nameRecord->offset = qbswap(newFamilyNameSize); + nameRecord->length = qbswap(regularStringSize); + } + } + + // nameRecord now points to string data + quint16 *stringStorage = reinterpret_cast(nameRecord); + for (QChar ch : newFamilyName) + *stringStorage++ = qbswap(quint16(ch.unicode())); + + for (QChar ch : regularString) + *stringStorage++ = qbswap(quint16(ch.unicode())); + } + + quint32 *p = reinterpret_cast(newNameTable.data()); + quint32 *tableEnd = reinterpret_cast(newNameTable.data() + fullSize); + + quint32 checkSum = 0; + while (p < tableEnd) + checkSum += qFromBigEndian(*(p++)); + + nameTableDirectoryEntry->checkSum = qbswap(checkSum); + nameTableDirectoryEntry->offset = qbswap(m_fontData.size()); + nameTableDirectoryEntry->length = qbswap(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 void *key, const QByteArray &fontData) + { + Q_ASSERT(!m_fontDatas.contains(key)); + m_fontDatas.insert(key, 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; + + private: + ULONG m_referenceCount; + QHash 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(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; + } + + class CustomFontFileLoader + { + public: + CustomFontFileLoader(IDWriteFactory *factory) + { + m_directWriteFactory = factory; + + if (m_directWriteFactory) { + m_directWriteFactory->AddRef(); + + m_directWriteFontFileLoader = new DirectWriteFontFileLoader(); + m_directWriteFactory->RegisterFontFileLoader(m_directWriteFontFileLoader); + } + } + + ~CustomFontFileLoader() + { + if (m_directWriteFactory != nullptr && m_directWriteFontFileLoader != nullptr) + m_directWriteFactory->UnregisterFontFileLoader(m_directWriteFontFileLoader); + + if (m_directWriteFactory != nullptr) + m_directWriteFactory->Release(); + } + + void addKey(const void *key, const QByteArray &fontData) + { + if (m_directWriteFontFileLoader != nullptr) + m_directWriteFontFileLoader->addKey(key, fontData); + } + + void removeKey(const void *key) + { + if (m_directWriteFontFileLoader != nullptr) + m_directWriteFontFileLoader->removeKey(key); + } + + IDWriteFontFileLoader *loader() const + { + return m_directWriteFontFileLoader; + } + + private: + IDWriteFactory *m_directWriteFactory = nullptr; + DirectWriteFontFileLoader *m_directWriteFontFileLoader = nullptr; + }; +} // Anonymous namespace + +#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 QWindowsFontEngineDataPtr; +typedef QThreadStorage FontEngineThreadLocalData; +Q_GLOBAL_STATIC(FontEngineThreadLocalData, fontEngineThreadLocalData) + +QSharedPointer QWindowsFontDatabaseBase::data() +{ + FontEngineThreadLocalData *data = fontEngineThreadLocalData(); + if (!data->hasLocalData()) + data->setLocalData(QSharedPointer::create()); + + if (!init(data->localData())) + qCWarning(lcQpaFonts) << "Cannot initialize common font database data"; + + return data->localData(); +} + +bool QWindowsFontDatabaseBase::init(QSharedPointer 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(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(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(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; +} + +#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d) +IDWriteFontFace *QWindowsFontDatabaseBase::createDirectWriteFace(const QByteArray &fontData) const +{ + QSharedPointer fontEngineData = data(); + if (fontEngineData->directWriteFactory == nullptr) { + qCWarning(lcQpaFonts) << "DirectWrite factory not created in QWindowsFontDatabaseBase::createDirectWriteFace()"; + return nullptr; + } + + CustomFontFileLoader fontFileLoader(fontEngineData->directWriteFactory); + fontFileLoader.addKey(this, fontData); + + IDWriteFontFile *fontFile = nullptr; + const void *key = this; + + HRESULT hres = fontEngineData->directWriteFactory->CreateCustomFontFileReference(&key, + sizeof(void *), + fontFileLoader.loader(), + &fontFile); + if (FAILED(hres)) { + qErrnoWarning(hres, "%s: CreateCustomFontFileReference failed", __FUNCTION__); + return nullptr; + } + + 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 nullptr; + } + + // ### Currently no support for .ttc, but we could easily return a list here. + 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 nullptr; + } + + fontFile->Release(); + return directWriteFontFace; +} +#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 fontEngineData = data(); + if (fontEngineData->directWriteFactory == nullptr) + return nullptr; + + IDWriteFontFace *directWriteFontFace = createDirectWriteFace(fontData); + 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 new file mode 100644 index 00000000..72d5ae5c --- /dev/null +++ b/qtbase/src/network/kernel/qdnslookup_win.cpp @@ -0,0 +1,176 @@ +// Copyright (C) 2012 Jeremy Lainé +// Copyright (C) 2023 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include +#include "qdnslookup_p.h" + +#include +#include +#include + +#include +#include +#include + +#ifndef DNS_ADDR_MAX_SOCKADDR_LENGTH +// MinGW headers are missing almost all of this +typedef struct Qt_DnsAddr { + CHAR MaxSa[32]; + DWORD DnsAddrUserDword[8]; +} DNS_ADDR, *PDNS_ADDR; +typedef struct Qt_DnsAddrArray { + DWORD MaxCount; + DWORD AddrCount; + DWORD Tag; + WORD Family; + WORD WordReserved; + DWORD Flags; + DWORD MatchFlag; + DWORD Reserved1; + DWORD Reserved2; + DNS_ADDR AddrArray[]; +} DNS_ADDR_ARRAY, *PDNS_ADDR_ARRAY; +# ifndef DNS_QUERY_RESULTS_VERSION1 +typedef struct Qt_DNS_QUERY_RESULT { + ULONG Version; + DNS_STATUS QueryStatus; + ULONG64 QueryOptions; + PDNS_RECORD pQueryRecords; + PVOID Reserved; +} DNS_QUERY_RESULT, *PDNS_QUERY_RESULT; +typedef VOID WINAPI DNS_QUERY_COMPLETION_ROUTINE(PVOID pQueryContext,PDNS_QUERY_RESULT pQueryResults); +typedef DNS_QUERY_COMPLETION_ROUTINE *PDNS_QUERY_COMPLETION_ROUTINE; +# endif +typedef struct Qt_DNS_QUERY_REQUEST { + ULONG Version; + PCWSTR QueryName; + WORD QueryType; + ULONG64 QueryOptions; + PDNS_ADDR_ARRAY pDnsServerList; + ULONG InterfaceIndex; + PDNS_QUERY_COMPLETION_ROUTINE pQueryCompletionCallback; + PVOID pQueryContext; +} DNS_QUERY_REQUEST, *PDNS_QUERY_REQUEST; + +typedef void *PDNS_QUERY_CANCEL; // not really, but we don't need it +extern "C" { +DNS_STATUS WINAPI DnsQueryEx(PDNS_QUERY_REQUEST pQueryRequest, + PDNS_QUERY_RESULT pQueryResults, + PDNS_QUERY_CANCEL pCancelHandle); +} +#endif + +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(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 = 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); + + QStringView lastEncodedName; + QString cachedDecodedName; + auto extractAndCacheHost = [&](QStringView name) -> const QString & { + lastEncodedName = name; + cachedDecodedName = decodeLabel(name); + return cachedDecodedName; + }; + 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) { + // warning: always assign name to the record before calling extractXxxHost() again + const QString &name = extractMaybeCachedHost(ptr->pName); + if (ptr->wType == QDnsLookup::A) { + QDnsHostAddressRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = QHostAddress(ntohl(ptr->Data.A.IpAddress)); + reply->hostAddressRecords.append(record); + } else if (ptr->wType == QDnsLookup::AAAA) { + Q_IPV6ADDR addr; + memcpy(&addr, &ptr->Data.AAAA.Ip6Address, sizeof(Q_IPV6ADDR)); + + QDnsHostAddressRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = QHostAddress(addr); + reply->hostAddressRecords.append(record); + } else if (ptr->wType == QDnsLookup::CNAME) { + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = extractAndCacheHost(ptr->Data.Cname.pNameHost); + reply->canonicalNameRecords.append(record); + } else if (ptr->wType == QDnsLookup::MX) { + QDnsMailExchangeRecord record; + record.d->name = name; + record.d->exchange = decodeLabel(QStringView(ptr->Data.Mx.pNameExchange)); + record.d->preference = ptr->Data.Mx.wPreference; + record.d->timeToLive = ptr->dwTtl; + reply->mailExchangeRecords.append(record); + } else if (ptr->wType == QDnsLookup::NS) { + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = decodeLabel(QStringView(ptr->Data.Ns.pNameHost)); + reply->nameServerRecords.append(record); + } else if (ptr->wType == QDnsLookup::PTR) { + QDnsDomainNameRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + record.d->value = decodeLabel(QStringView(ptr->Data.Ptr.pNameHost)); + reply->pointerRecords.append(record); + } else if (ptr->wType == QDnsLookup::SRV) { + QDnsServiceRecord record; + record.d->name = name; + record.d->target = decodeLabel(QStringView(ptr->Data.Srv.pNameTarget)); + record.d->port = ptr->Data.Srv.wPort; + record.d->priority = ptr->Data.Srv.wPriority; + record.d->timeToLive = ptr->dwTtl; + record.d->weight = ptr->Data.Srv.wWeight; + reply->serviceRecords.append(record); + } else if (ptr->wType == QDnsLookup::TXT) { + QDnsTextRecord record; + record.d->name = name; + record.d->timeToLive = ptr->dwTtl; + for (unsigned int i = 0; i < ptr->Data.Txt.dwStringCount; ++i) { + record.d->values << QStringView(ptr->Data.Txt.pStringArray[i]).toLatin1(); + } + reply->textRecords.append(record); + } + } + + DnsRecordListFree(results.pQueryRecords, DnsFreeRecordList); +} + +QT_END_NAMESPACE diff --git a/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp b/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp new file mode 100644 index 00000000..026e81cb --- /dev/null +++ b/qtbase/src/plugins/platforms/windows/qwin10helpers.cpp @@ -0,0 +1,100 @@ +// 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 "qwin10helpers.h" + +#include +#include +#include + +#if defined(Q_CC_MINGW) || defined(Q_CC_CLANG) +# define HAS_UI_VIEW_SETTINGS_INTEROP +// Present from MSVC2015 + SDK 10 onwards +#elif (!defined(Q_CC_MSVC) || _MSC_VER >= 1900) && WINVER >= 0x0A00 +# define HAS_UI_VIEW_SETTINGS_INTEROP +# define HAS_UI_VIEW_SETTINGS +#endif + +#include + +#ifdef HAS_UI_VIEW_SETTINGS +# include +#endif + +#ifdef HAS_UI_VIEW_SETTINGS_INTEROP +# include +#endif + +#ifndef HAS_UI_VIEW_SETTINGS_INTEROP +MIDL_INTERFACE("3694dbf9-8f68-44be-8ff5-195c98ede8a6") +IUIViewSettingsInterop : public IInspectable +{ +public: + virtual HRESULT STDMETHODCALLTYPE GetForWindow( + __RPC__in HWND hwnd, + __RPC__in REFIID riid, + __RPC__deref_out_opt void **ppv) = 0; +}; +#endif // !HAS_UI_VIEW_SETTINGS_INTEROP + +#ifndef HAS_UI_VIEW_SETTINGS +namespace ABI { +namespace Windows { +namespace UI { +namespace ViewManagement { + +enum UserInteractionMode { Mouse, Touch }; + +MIDL_INTERFACE("C63657F6-8850-470D-88F8-455E16EA2C26") +IUIViewSettings : public IInspectable +{ +public: + virtual HRESULT STDMETHODCALLTYPE get_UserInteractionMode(UserInteractionMode *value) = 0; +}; + +} // namespace ViewManagement +} // namespace UI +} // namespace Windows +} // namespace ABI +#endif // HAS_UI_VIEW_SETTINGS + +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(&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(&viewSettings)); + if (SUCCEEDED(hr)) { + ABI::Windows::UI::ViewManagement::UserInteractionMode currentMode; + hr = viewSettings->get_UserInteractionMode(¤tMode); + if (SUCCEEDED(hr)) + result = currentMode == 1; // Touch, 1 + viewSettings->Release(); + } + uiViewSettingsInterop->Release(); + return result; +} + +QT_END_NAMESPACE diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp new file mode 100644 index 00000000..72a3ca15 --- /dev/null +++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp @@ -0,0 +1,1606 @@ +// Copyright (C) 2013 Samuel Gaist +// 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 +#if QT_CONFIG(accessibility) +# include "uiautomation/qwindowsuiaaccessibility.h" +#endif +#if QT_CONFIG(sessionmanager) +# include +# include "qwindowssessionmanager.h" +#endif +#include "qwindowsscreen.h" +#include "qwindowstheme.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +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 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(QObjectPrivate::get(qApp)); + auto *managerPrivate = static_cast(QObjectPrivate::get(guiPrivate->session_manager)); + return static_cast(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 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 m_creationContext; +#if QT_CONFIG(tabletevent) + QScopedPointer 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(lParam); + if (setting) { + auto data = reinterpret_cast(&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(); +} + +QList QWindowsContext::possibleKeys(const QKeyEvent *e) const +{ + return d->m_keyMapper.possibleKeys(e); +} + +QWindowsContext::HandleBaseWindowHash &QWindowsContext::windows() +{ + return d->m_windows; +} + +QSharedPointer QWindowsContext::setWindowCreationContext(const QSharedPointer &ctx) +{ + const QSharedPointer old = d->m_creationContext; + d->m_creationContext = ctx; + return old; +} + +QSharedPointer 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); +#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(GetModuleHandle(nullptr)); + WNDCLASS wcinfo; + const bool classExists = GetClassInfo(appInstance, reinterpret_cast(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(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(LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, sw, sh, 0)); + } else { + wc.hIcon = static_cast(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(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(GetModuleHandle(nullptr)); + + for (const QString &name : std::as_const(d->m_registeredWindowClassNames)) { + if (!UnregisterClass(reinterpret_cast(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(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(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(className.utf16()), + windowName, style, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + HWND_MESSAGE, nullptr, static_cast(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(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(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(lParam)); + return true; + case QtWindows::ResizeEvent: + d->m_creationContext->obtainedSize = QSize(GET_X_LPARAM(lParam), GET_Y_LPARAM(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::KeyboardLayoutChangeEvent: + 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(wParam))) + return true; + if (platformWindow == nullptr || platformWindow->menuBar() == nullptr) + return false; + return platformWindow->menuBar()->notifyAboutToShow(reinterpret_cast(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(wParam), lParam); + return true; + case QtWindows::QuerySizeHints: + platformWindow->getSizeHints(reinterpret_cast(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: { + // 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(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(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::handleWindowActivated(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, + QWindowsKeyMapper::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 = QWindowsKeyMapper::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(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 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 new file mode 100644 index 00000000..1a3b47be --- /dev/null +++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.h @@ -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 + +#include +#include +#include + +#define STRICT_TYPED_ITEMIDS +#include +#include + +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 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; + + 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 setWindowCreationContext(const QSharedPointer &ctx); + QSharedPointer 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; + QList possibleKeys(const QKeyEvent *e) 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 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/qwindowsdrag.cpp b/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp new file mode 100644 index 00000000..b797217c --- /dev/null +++ b/qtbase/src/plugins/platforms/windows/qwindowsdrag.cpp @@ -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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +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 QWindowsComBase +{ +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 ActionCursorMap; + + Mode m_mode; + QWindowsDrag *m_drag; + QPointer 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(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(m_window->winId()), pDataObj, reinterpret_cast(&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(&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; + + lastModifiers = QWindowsKeyMapper::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(&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(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; +bool QWindowsDrag::m_dragging = 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(&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 HWND hwnd = underMouse ? reinterpret_cast(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(virt_x * (65535.0 / virt_w)); + input.mi.dy = static_cast(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; + // Indicate message handlers we are in DoDragDrop() event loop. + QWindowsDrag::m_dragging = true; + const HRESULT r = startDoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect); + QWindowsDrag::m_dragging = false; + 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(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 new file mode 100644 index 00000000..3089e745 --- /dev/null +++ b/qtbase/src/plugins/platforms/windows/qwindowskeymapper.cpp @@ -0,0 +1,1400 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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::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 + +// We not only need the scancode itself but also the extended bit of key messages. Thus we need +// the additional bit when masking the scancode. +enum { scancodeBitmask = 0x1ff }; + +// 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< &keys) : m_keys(keys) {} + +private: + friend QDebug operator<<(QDebug d, const formatKeys &keys); + const QList &m_keys; +}; + +QDebug operator<<(QDebug d, const formatKeys &k) +{ + QDebugStateSaver saver(d); + d.nospace(); + d << '('; + for (int i =0, size = k.m_keys.size(); i < size; ++i) { + if (i) + d << ", "; + d << QKeySequence(k.m_keys.at(i)); + } + d << ')'; + return d; +} +#else // !QT_NO_DEBUG_STREAM +static int formatKeys(const QList &) { return 0; } +#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(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(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 = (msg.lParam >> 16) & scancodeBitmask; + 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(&buffer), 0); + ::ToAscii(vk_key, scancode, kbdBuffer, reinterpret_cast(&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 = (msg.lParam >> 16) & scancodeBitmask; + 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, vk_key, 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(QWindowsIntegration::instance()->inputContext()); + if (!(windowsInputContext && windowsInputContext->isComposing())) + vk_key = ImmGetVirtualKey(reinterpret_cast(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+ 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() +{ + 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 QWindowsKeyMapper::possibleKeys(const QKeyEvent *e) const +{ + QList 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).toCombined(); + return result; + } + result << int(baseKey) + int(keyMods); // The base key is _always_ valid, of course + + 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 int matchedKey = int(key) + int(missingMods); + const auto it = + std::find_if(result.begin(), result.end(), + [key] (int k) { return (k & ~Qt::KeyboardModifierMask) == 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 > Qt::KeyboardModifiers(*it & Qt::KeyboardModifierMask)) + *it = matchedKey; + } + } + qCDebug(lcQpaEvents) << __FUNCTION__ << e << "nativeVirtualKey=" + << Qt::showbase << Qt::hex << e->nativeVirtualKey() << Qt::dec << Qt::noshowbase + << e->modifiers() << kbItem << "\n returns" << formatKeys(result); + return result; +} + +QT_END_NAMESPACE diff --git a/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp new file mode 100644 index 00000000..77ed0f84 --- /dev/null +++ b/qtbase/src/plugins/platforms/windows/qwindowspointerhandler.cpp @@ -0,0 +1,864 @@ +// 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 + +#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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +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 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 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(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(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(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(vTouchInfo); + + if (et & QtWindows::NonClientEventFlag) + return false; // Let DefWindowProc() handle Non Client messages. + + if (count < 1) + return false; + + if (msg.message == WM_POINTERCAPTURECHANGED) { + QWindowSystemInterface::handleTouchCancelEvent(window, m_touchDevice.data(), + QWindowsKeyMapper::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 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 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(); + + QWindowSystemInterface::handleTouchEvent(window, m_touchDevice.data(), touchPoints, + QWindowsKeyMapper::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(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 Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::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 Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::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 new file mode 100644 index 00000000..15c5aac8 --- /dev/null +++ b/qtbase/src/plugins/platforms/windows/qwindowsscreen.cpp @@ -0,0 +1,842 @@ +// 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 "qwindowsscreen.h" +#include "qwindowscontext.h" +#include "qwindowswindow.h" +#include "qwindowsintegration.h" +#include "qwindowscursor.h" +#include "qwindowstheme.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static inline QDpi deviceDPI(HDC hdc) +{ + return QDpi(GetDeviceCaps(hdc, LOGPIXELSX), GetDeviceCaps(hdc, LOGPIXELSY)); +} + +static inline QDpi monitorDPI(HMONITOR hMonitor) +{ + UINT dpiX; + UINT dpiY; + if (SUCCEEDED(GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) + return QDpi(dpiX, dpiY); + return {0, 0}; +} + +static std::vector getPathInfo(const MONITORINFOEX &viewInfo) +{ + // We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO. + std::vector pathInfos; + std::vector modeInfos; + + // Fetch paths + LONG result; + UINT32 numPathArrayElements; + UINT32 numModeInfoArrayElements; + do { + // QueryDisplayConfig documentation doesn't say the number of needed elements is updated + // when the call fails with ERROR_INSUFFICIENT_BUFFER, so we need a separate call to + // look up the needed buffer sizes. + if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElements, + &numModeInfoArrayElements) != ERROR_SUCCESS) { + return {}; + } + pathInfos.resize(numPathArrayElements); + modeInfos.resize(numModeInfoArrayElements); + result = QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElements, pathInfos.data(), + &numModeInfoArrayElements, modeInfos.data(), nullptr); + } while (result == ERROR_INSUFFICIENT_BUFFER); + + if (result != ERROR_SUCCESS) + return {}; + + // Find paths matching monitor name + auto discardThese = + std::remove_if(pathInfos.begin(), pathInfos.end(), [&](const auto &path) -> bool { + DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName; + deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + deviceName.header.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME); + deviceName.header.adapterId = path.sourceInfo.adapterId; + deviceName.header.id = path.sourceInfo.id; + if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) { + return wcscmp(viewInfo.szDevice, deviceName.viewGdiDeviceName) != 0; + } + return true; + }); + + pathInfos.erase(discardThese, pathInfos.end()); + + return pathInfos; +} + +#if 0 +// Needed later for HDR support +static float getMonitorSDRWhiteLevel(DISPLAYCONFIG_PATH_TARGET_INFO *targetInfo) +{ + const float defaultSdrWhiteLevel = 200.0; + if (!targetInfo) + return defaultSdrWhiteLevel; + + DISPLAYCONFIG_SDR_WHITE_LEVEL whiteLevel = {}; + whiteLevel.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL; + whiteLevel.header.size = sizeof(DISPLAYCONFIG_SDR_WHITE_LEVEL); + whiteLevel.header.adapterId = targetInfo->adapterId; + whiteLevel.header.id = targetInfo->id; + if (DisplayConfigGetDeviceInfo(&whiteLevel.header) != ERROR_SUCCESS) + return defaultSdrWhiteLevel; + return whiteLevel.SDRWhiteLevel * 80.0 / 1000.0; +} +#endif + +using WindowsScreenDataList = QList; + +struct RegistryHandleDeleter +{ + void operator()(HKEY handle) const noexcept + { + if (handle != nullptr && handle != INVALID_HANDLE_VALUE) + RegCloseKey(handle); + } +}; + +using RegistryHandlePtr = std::unique_ptr, RegistryHandleDeleter>; + +static void setMonitorDataFromSetupApi(QWindowsScreenData &data, + const std::vector &pathGroup) +{ + if (pathGroup.empty()) { + return; + } + + // The only property shared among monitors in a clone group is deviceName + { + DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {}; + deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; + deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME); + // The first element in the clone group is the main monitor. + deviceName.header.adapterId = pathGroup[0].targetInfo.adapterId; + deviceName.header.id = pathGroup[0].targetInfo.id; + if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) { + data.devicePath = QString::fromWCharArray(deviceName.monitorDevicePath); + } else { + qCWarning(lcQpaScreen) + << u"Unable to get device information for %1:"_s.arg(pathGroup[0].targetInfo.id) + << QSystemError::windowsString(); + } + } + + // The rest must be concatenated into the resulting property + QStringList names; + QStringList manufacturers; + QStringList models; + QStringList serialNumbers; + + for (const auto &path : pathGroup) { + DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {}; + deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; + deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME); + deviceName.header.adapterId = path.targetInfo.adapterId; + deviceName.header.id = path.targetInfo.id; + if (DisplayConfigGetDeviceInfo(&deviceName.header) != ERROR_SUCCESS) { + qCWarning(lcQpaScreen) + << u"Unable to get device information for %1:"_s.arg(path.targetInfo.id) + << QSystemError::windowsString(); + continue; + } + + // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-monitor + constexpr GUID GUID_DEVINTERFACE_MONITOR = { + 0xe6f07b5f, 0xee97, 0x4a90, { 0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7 } + }; + const HDEVINFO devInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_MONITOR, nullptr, nullptr, + DIGCF_DEVICEINTERFACE); + + SP_DEVICE_INTERFACE_DATA deviceInterfaceData{}; + deviceInterfaceData.cbSize = sizeof(deviceInterfaceData); + + if (!SetupDiOpenDeviceInterfaceW(devInfo, deviceName.monitorDevicePath, DIODI_NO_ADD, + &deviceInterfaceData)) { + qCWarning(lcQpaScreen) + << u"Unable to open monitor interface to %1:"_s.arg(data.deviceName) + << QSystemError::windowsString(); + continue; + } + + DWORD requiredSize{ 0 }; + if (SetupDiGetDeviceInterfaceDetailW(devInfo, &deviceInterfaceData, nullptr, 0, + &requiredSize, nullptr) + || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + continue; + } + + const std::unique_ptr storage(new std::byte[requiredSize]); + auto *devicePath = reinterpret_cast(storage.get()); + devicePath->cbSize = sizeof(std::remove_pointer_t); + SP_DEVINFO_DATA deviceInfoData{}; + deviceInfoData.cbSize = sizeof(deviceInfoData); + if (!SetupDiGetDeviceInterfaceDetailW(devInfo, &deviceInterfaceData, devicePath, + requiredSize, nullptr, &deviceInfoData)) { + qCDebug(lcQpaScreen) << u"Unable to get monitor metadata for %1:"_s.arg(data.deviceName) + << QSystemError::windowsString(); + continue; + } + + const RegistryHandlePtr edidRegistryKey{ SetupDiOpenDevRegKey( + devInfo, &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ) }; + + if (!edidRegistryKey || edidRegistryKey.get() == INVALID_HANDLE_VALUE) + continue; + + DWORD edidDataSize{ 0 }; + if (RegQueryValueExW(edidRegistryKey.get(), L"EDID", nullptr, nullptr, nullptr, + &edidDataSize) + != ERROR_SUCCESS) { + continue; + } + + QByteArray edidData; + edidData.resize(edidDataSize); + + if (RegQueryValueExW(edidRegistryKey.get(), L"EDID", nullptr, nullptr, + reinterpret_cast(edidData.data()), &edidDataSize) + != ERROR_SUCCESS) { + qCDebug(lcQpaScreen) << u"Unable to get EDID from the Registry for %1:"_s.arg( + data.deviceName) + << QSystemError::windowsString(); + continue; + } + + QEdidParser edid; + + if (!edid.parse(edidData)) { + qCDebug(lcQpaScreen) << "Invalid EDID blob for" << data.deviceName; + continue; + } + + // We skip edid.identifier because it is unreliable, and a better option + // is already available through DisplayConfigGetDeviceInfo (see below). + names << QString::fromWCharArray(deviceName.monitorFriendlyDeviceName); + manufacturers << edid.manufacturer; + models << edid.model; + serialNumbers << edid.serialNumber; + } + + data.name = names.join(u"|"_s); + data.manufacturer = manufacturers.join(u"|"_s); + data.model = models.join(u"|"_s); + data.serialNumber = serialNumbers.join(u"|"_s); +} + +static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data) +{ + MONITORINFOEX info; + memset(&info, 0, sizeof(MONITORINFOEX)); + info.cbSize = sizeof(MONITORINFOEX); + if (GetMonitorInfo(hMonitor, &info) == FALSE) + return false; + + data->hMonitor = hMonitor; + data->geometry = QRect(QPoint(info.rcMonitor.left, info.rcMonitor.top), QPoint(info.rcMonitor.right - 1, info.rcMonitor.bottom - 1)); + data->availableGeometry = QRect(QPoint(info.rcWork.left, info.rcWork.top), QPoint(info.rcWork.right - 1, info.rcWork.bottom - 1)); + data->deviceName = QString::fromWCharArray(info.szDevice); + const auto pathGroup = getPathInfo(info); + if (!pathGroup.empty()) { + setMonitorDataFromSetupApi(*data, pathGroup); + } + if (data->name.isEmpty()) + data->name = data->deviceName; + if (data->deviceName == u"WinDisc") { + data->flags |= QWindowsScreenData::LockScreen; + } else { + if (const HDC hdc = CreateDC(info.szDevice, nullptr, nullptr, nullptr)) { + const QDpi dpi = monitorDPI(hMonitor); + data->dpi = dpi.first > 0 ? dpi : deviceDPI(hdc); + data->depth = GetDeviceCaps(hdc, BITSPIXEL); + data->format = data->depth == 16 ? QImage::Format_RGB16 : QImage::Format_RGB32; + data->physicalSizeMM = QSizeF(GetDeviceCaps(hdc, HORZSIZE), GetDeviceCaps(hdc, VERTSIZE)); + const int refreshRate = GetDeviceCaps(hdc, VREFRESH); + if (refreshRate > 1) // 0,1 means hardware default. + data->refreshRateHz = refreshRate; + DeleteDC(hdc); + } else { + qWarning("%s: Unable to obtain handle for monitor '%s', defaulting to %g DPI.", + __FUNCTION__, qPrintable(data->deviceName), + data->dpi.first); + } // CreateDC() failed + } // not lock screen + + // ### We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO, + // if we are going to use DISPLAYCONFIG lookups more. + if (!pathGroup.empty()) { + // The first element in the clone group is the main monitor. + const auto &pathInfo = pathGroup[0]; + switch (pathInfo.targetInfo.rotation) { + case DISPLAYCONFIG_ROTATION_IDENTITY: + data->orientation = Qt::LandscapeOrientation; + break; + case DISPLAYCONFIG_ROTATION_ROTATE90: + data->orientation = Qt::PortraitOrientation; + break; + case DISPLAYCONFIG_ROTATION_ROTATE180: + data->orientation = Qt::InvertedLandscapeOrientation; + break; + case DISPLAYCONFIG_ROTATION_ROTATE270: + data->orientation = Qt::InvertedPortraitOrientation; + break; + case DISPLAYCONFIG_ROTATION_FORCE_UINT32: + Q_UNREACHABLE(); + break; + } + if (pathInfo.targetInfo.refreshRate.Numerator && pathInfo.targetInfo.refreshRate.Denominator) + data->refreshRateHz = static_cast(pathInfo.targetInfo.refreshRate.Numerator) + / pathInfo.targetInfo.refreshRate.Denominator; + } else { + data->orientation = data->geometry.height() > data->geometry.width() + ? Qt::PortraitOrientation + : Qt::LandscapeOrientation; + } + // EnumDisplayMonitors (as opposed to EnumDisplayDevices) enumerates only + // virtual desktop screens. + data->flags |= QWindowsScreenData::VirtualDesktop; + if (info.dwFlags & MONITORINFOF_PRIMARY) + data->flags |= QWindowsScreenData::PrimaryScreen; + return true; +} + +// from monitorData, taking WindowsScreenDataList as LPARAM +BOOL QT_WIN_CALLBACK monitorEnumCallback(HMONITOR hMonitor, HDC, LPRECT, LPARAM p) +{ + QWindowsScreenData data; + if (monitorData(hMonitor, &data)) { + auto *result = reinterpret_cast(p); + auto it = std::find_if(result->rbegin(), result->rend(), + [&data](QWindowsScreenData i){ return i.name == data.name; }); + if (it != result->rend()) { + int previousIndex = 1; + if (it->deviceIndex.has_value()) + previousIndex = it->deviceIndex.value(); + else + (*it).deviceIndex = 1; + data.deviceIndex = previousIndex + 1; + } + // QWindowSystemInterface::handleScreenAdded() documentation specifies that first + // added screen will be the primary screen, so order accordingly. + // Note that the side effect of this policy is that there is no way to change primary + // screen reported by Qt, unless we want to delete all existing screens and add them + // again whenever primary screen changes. + if (data.flags & QWindowsScreenData::PrimaryScreen) + result->prepend(data); + else + result->append(data); + } + return TRUE; +} + +static inline WindowsScreenDataList monitorData() +{ + WindowsScreenDataList result; + EnumDisplayMonitors(nullptr, nullptr, monitorEnumCallback, reinterpret_cast(&result)); + return result; +} + +#ifndef QT_NO_DEBUG_STREAM +static QDebug operator<<(QDebug dbg, const QWindowsScreenData &d) +{ + QDebugStateSaver saver(dbg); + dbg.nospace(); + dbg.noquote(); + dbg << "Screen \"" << d.name << "\" " << d.geometry.width() << 'x' << d.geometry.height() << '+' + << d.geometry.x() << '+' << d.geometry.y() << " avail: " << d.availableGeometry.width() + << 'x' << d.availableGeometry.height() << '+' << d.availableGeometry.x() << '+' + << d.availableGeometry.y() << " physical: " << d.physicalSizeMM.width() << 'x' + << d.physicalSizeMM.height() << " DPI: " << d.dpi.first << 'x' << d.dpi.second + << " Depth: " << d.depth << " Format: " << d.format << " hMonitor: " << d.hMonitor + << " device name: " << d.deviceName << " manufacturer: " << d.manufacturer + << " model: " << d.model << " serial number: " << d.serialNumber; + if (d.flags & QWindowsScreenData::PrimaryScreen) + dbg << " primary"; + if (d.flags & QWindowsScreenData::VirtualDesktop) + dbg << " virtual desktop"; + if (d.flags & QWindowsScreenData::LockScreen) + dbg << " lock screen"; + return dbg; +} +#endif // !QT_NO_DEBUG_STREAM + +/*! + \class QWindowsScreen + \brief Windows screen. + \sa QWindowsScreenManager + \internal +*/ + +QWindowsScreen::QWindowsScreen(const QWindowsScreenData &data) : + m_data(data) +#ifndef QT_NO_CURSOR + , m_cursor(new QWindowsCursor(this)) +#endif +{ +} + +QString QWindowsScreen::name() const +{ + return m_data.deviceIndex.has_value() + ? (u"%1 (%2)"_s).arg(m_data.name, QString::number(m_data.deviceIndex.value())) + : m_data.name; +} + +QPixmap QWindowsScreen::grabWindow(WId window, int xIn, int yIn, int width, int height) const +{ + QSize windowSize; + int x = xIn; + int y = yIn; + HWND hwnd = reinterpret_cast(window); + if (hwnd) { + RECT r; + GetClientRect(hwnd, &r); + windowSize = QSize(r.right - r.left, r.bottom - r.top); + } else { + // Grab current screen. The client rectangle of GetDesktopWindow() is the + // primary screen, but it is possible to grab other screens from it. + hwnd = GetDesktopWindow(); + const QRect screenGeometry = geometry(); + windowSize = screenGeometry.size(); + x += screenGeometry.x(); + y += screenGeometry.y(); + } + + if (width < 0) + width = windowSize.width() - xIn; + if (height < 0) + height = windowSize.height() - yIn; + + // Create and setup bitmap + HDC display_dc = GetDC(nullptr); + HDC bitmap_dc = CreateCompatibleDC(display_dc); + HBITMAP bitmap = CreateCompatibleBitmap(display_dc, width, height); + HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap); + + // copy data + HDC window_dc = GetDC(hwnd); + BitBlt(bitmap_dc, 0, 0, width, height, window_dc, x, y, SRCCOPY | CAPTUREBLT); + + // clean up all but bitmap + ReleaseDC(hwnd, window_dc); + SelectObject(bitmap_dc, null_bitmap); + DeleteDC(bitmap_dc); + + const QPixmap pixmap = qt_pixmapFromWinHBITMAP(bitmap); + + DeleteObject(bitmap); + ReleaseDC(nullptr, display_dc); + + return pixmap; +} + +/*! + \brief Find a top level window taking the flags of ChildWindowFromPointEx. +*/ + +QWindow *QWindowsScreen::topLevelAt(const QPoint &point) const +{ + QWindow *result = nullptr; + if (QWindow *child = QWindowsScreen::windowAt(point, CWP_SKIPINVISIBLE)) + result = QWindowsWindow::topLevelOf(child); + if (QWindowsContext::verbose > 1) + qCDebug(lcQpaScreen) <<__FUNCTION__ << point << result; + return result; +} + +QWindow *QWindowsScreen::windowAt(const QPoint &screenPoint, unsigned flags) +{ + QWindow* result = nullptr; + if (QPlatformWindow *bw = QWindowsContext::instance()-> + findPlatformWindowAt(GetDesktopWindow(), screenPoint, flags)) + result = bw->window(); + if (QWindowsContext::verbose > 1) + qCDebug(lcQpaScreen) <<__FUNCTION__ << screenPoint << " returns " << result; + return result; +} + +/*! + \brief Determine siblings in a virtual desktop system. + + Self is by definition a sibling, else collect all screens + within virtual desktop. +*/ + +QList QWindowsScreen::virtualSiblings() const +{ + QList result; + if (m_data.flags & QWindowsScreenData::VirtualDesktop) { + const QWindowsScreenManager::WindowsScreenList screens + = QWindowsContext::instance()->screenManager().screens(); + for (QWindowsScreen *screen : screens) { + if (screen->data().flags & QWindowsScreenData::VirtualDesktop) + result.push_back(screen); + } + } else { + result.push_back(const_cast(this)); + } + return result; +} + +/*! + \brief Notify QWindowSystemInterface about changes of a screen and synchronize data. +*/ + +void QWindowsScreen::handleChanges(const QWindowsScreenData &newData) +{ + m_data.physicalSizeMM = newData.physicalSizeMM; + + if (m_data.hMonitor != newData.hMonitor) { + qCDebug(lcQpaScreen) << "Monitor" << m_data.name + << "has had its hMonitor handle changed from" + << m_data.hMonitor << "to" << newData.hMonitor; + m_data.hMonitor = newData.hMonitor; + } + + // QGuiApplicationPrivate::processScreenGeometryChange() checks and emits + // DPI and orientation as well, so, assign new values and emit DPI first. + const bool geometryChanged = m_data.geometry != newData.geometry + || m_data.availableGeometry != newData.availableGeometry; + const bool dpiChanged = !qFuzzyCompare(m_data.dpi.first, newData.dpi.first) + || !qFuzzyCompare(m_data.dpi.second, newData.dpi.second); + const bool orientationChanged = m_data.orientation != newData.orientation; + const bool primaryChanged = (newData.flags & QWindowsScreenData::PrimaryScreen) + && !(m_data.flags & QWindowsScreenData::PrimaryScreen); + m_data.dpi = newData.dpi; + m_data.orientation = newData.orientation; + m_data.geometry = newData.geometry; + m_data.availableGeometry = newData.availableGeometry; + m_data.flags = (m_data.flags & ~QWindowsScreenData::PrimaryScreen) + | (newData.flags & QWindowsScreenData::PrimaryScreen); + + if (dpiChanged) { + QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), + newData.dpi.first, + newData.dpi.second); + } + if (orientationChanged) + QWindowSystemInterface::handleScreenOrientationChange(screen(), newData.orientation); + if (geometryChanged) { + QWindowSystemInterface::handleScreenGeometryChange(screen(), + newData.geometry, newData.availableGeometry); + } + if (primaryChanged) + QWindowSystemInterface::handlePrimaryScreenChanged(this); +} + +HMONITOR QWindowsScreen::handle() const +{ + return m_data.hMonitor; +} + +QRect QWindowsScreen::virtualGeometry(const QPlatformScreen *screen) // cf QScreen::virtualGeometry() +{ + QRect result; + const auto siblings = screen->virtualSiblings(); + for (const QPlatformScreen *sibling : siblings) + result |= sibling->geometry(); + return result; +} + +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; + } + 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; + } + } + return result; +} + +/*! + \brief Queries ClearType settings to check the pixel layout +*/ +QPlatformScreen::SubpixelAntialiasingType QWindowsScreen::subpixelAntialiasingTypeHint() const +{ + QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint(); + if (type == QPlatformScreen::Subpixel_None) { + QSettings settings(R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Avalon.Graphics\DISPLAY1)"_L1, + QSettings::NativeFormat); + int registryValue = settings.value("PixelStructure"_L1, -1).toInt(); + switch (registryValue) { + case 0: + type = QPlatformScreen::Subpixel_None; + break; + case 1: + type = QPlatformScreen::Subpixel_RGB; + break; + case 2: + type = QPlatformScreen::Subpixel_BGR; + break; + default: + type = QPlatformScreen::Subpixel_None; + break; + } + } + return type; +} + +/*! + \class QWindowsScreenManager + \brief Manages a list of QWindowsScreen. + + Listens for changes and notifies QWindowSystemInterface about changed/ + added/deleted screens. + + \sa QWindowsScreen + \internal +*/ + +extern "C" LRESULT QT_WIN_CALLBACK qDisplayChangeObserverWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message == WM_DISPLAYCHANGE) { + qCDebug(lcQpaScreen) << "Handling WM_DISPLAYCHANGE"; + if (QWindowsTheme *t = QWindowsTheme::instance()) + t->displayChanged(); + QWindowsWindow::displayChanged(); + if (auto *context = QWindowsContext::instance()) + context->screenManager().handleScreenChanges(); + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} + +QWindowsScreenManager::QWindowsScreenManager() = default; + +void QWindowsScreenManager::initialize() +{ + qCDebug(lcQpaScreen) << "Initializing screen manager"; + + auto className = QWindowsContext::instance()->registerWindowClass( + QWindowsContext::classNamePrefix() + QLatin1String("ScreenChangeObserverWindow"), + qDisplayChangeObserverWndProc); + + // HWND_MESSAGE windows do not get WM_DISPLAYCHANGE, so we need to create + // a real top level window that we never show. + m_displayChangeObserver = CreateWindowEx(0, reinterpret_cast(className.utf16()), + nullptr, WS_TILED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + nullptr, nullptr, GetModuleHandle(nullptr), nullptr); + Q_ASSERT(m_displayChangeObserver); + + qCDebug(lcQpaScreen) << "Created display change observer" << m_displayChangeObserver; + + handleScreenChanges(); +} + +QWindowsScreenManager::~QWindowsScreenManager() +{ + DestroyWindow(m_displayChangeObserver); +} + +bool QWindowsScreenManager::isSingleScreen() +{ + return QWindowsContext::instance()->screenManager().screens().size() < 2; +} + +static inline int indexOfMonitor(const QWindowsScreenManager::WindowsScreenList &screens, + const QString &deviceName) +{ + for (int i= 0; i < screens.size(); ++i) + if (screens.at(i)->data().deviceName == deviceName) + return i; + return -1; +} + +static inline int indexOfMonitor(const WindowsScreenDataList &screenData, + const QString &deviceName) +{ + for (int i = 0; i < screenData.size(); ++i) + if (screenData.at(i).deviceName == deviceName) + return i; + return -1; +} + +// Move a window to a new virtual screen, accounting for varying sizes. +static void moveToVirtualScreen(QWindow *w, const QScreen *newScreen) +{ + QRect geometry = w->geometry(); + const QRect oldScreenGeometry = w->screen()->geometry(); + const QRect newScreenGeometry = newScreen->geometry(); + QPoint relativePosition = geometry.topLeft() - oldScreenGeometry.topLeft(); + if (oldScreenGeometry.size() != newScreenGeometry.size()) { + const qreal factor = + qreal(QPoint(newScreenGeometry.width(), newScreenGeometry.height()).manhattanLength()) / + qreal(QPoint(oldScreenGeometry.width(), oldScreenGeometry.height()).manhattanLength()); + relativePosition = (QPointF(relativePosition) * factor).toPoint(); + } + geometry.moveTopLeft(relativePosition); + w->setGeometry(geometry); +} + +void QWindowsScreenManager::removeScreen(int index) +{ + qCDebug(lcQpaScreen) << "Removing Monitor:" << m_screens.at(index)->data(); + QScreen *screen = m_screens.at(index)->screen(); + QScreen *primaryScreen = QGuiApplication::primaryScreen(); + // QTBUG-38650: When a screen is disconnected, Windows will automatically + // move the Window to another screen. This will trigger a geometry change + // event, but unfortunately after the screen destruction signal. To prevent + // QtGui from automatically hiding the QWindow, pretend all Windows move to + // the primary screen first (which is likely the correct, final screen). + // QTBUG-39320: Windows does not automatically move WS_EX_TOOLWINDOW (dock) windows; + // move those manually. + if (screen != primaryScreen) { + unsigned movedWindowCount = 0; + const QWindowList tlws = QGuiApplication::topLevelWindows(); + for (QWindow *w : tlws) { + if (w->screen() == screen && w->handle() && w->type() != Qt::Desktop) { + if (w->isVisible() && w->windowState() != Qt::WindowMinimized + && (QWindowsWindow::baseWindowOf(w)->exStyle() & WS_EX_TOOLWINDOW)) { + moveToVirtualScreen(w, primaryScreen); + } else { + QWindowSystemInterface::handleWindowScreenChanged(w, primaryScreen); + } + ++movedWindowCount; + } + } + if (movedWindowCount) + QWindowSystemInterface::flushWindowSystemEvents(); + } + QWindowSystemInterface::handleScreenRemoved(m_screens.takeAt(index)); +} + +/*! + \brief Synchronizes the screen list, adds new screens, removes deleted + ones and propagates resolution changes to QWindowSystemInterface. +*/ + +bool QWindowsScreenManager::handleScreenChanges() +{ + // Look for changed monitors, add new ones + const WindowsScreenDataList newDataList = monitorData(); + const bool lockScreen = newDataList.size() == 1 && (newDataList.front().flags & QWindowsScreenData::LockScreen); + bool primaryScreenChanged = false; + for (const QWindowsScreenData &newData : newDataList) { + const int existingIndex = indexOfMonitor(m_screens, newData.deviceName); + if (existingIndex != -1) { + m_screens.at(existingIndex)->handleChanges(newData); + if (existingIndex == 0) + primaryScreenChanged = true; + } else { + auto *newScreen = new QWindowsScreen(newData); + m_screens.push_back(newScreen); + QWindowSystemInterface::handleScreenAdded(newScreen, + newData.flags & QWindowsScreenData::PrimaryScreen); + qCDebug(lcQpaScreen) << "New Monitor: " << newData; + } // exists + } // for new screens. + // Remove deleted ones but keep main monitors if we get only the + // temporary lock screen to avoid window recreation (QTBUG-33062). + if (!lockScreen) { + for (int i = m_screens.size() - 1; i >= 0; --i) { + if (indexOfMonitor(newDataList, m_screens.at(i)->data().deviceName) == -1) + removeScreen(i); + } // for existing screens + } // not lock screen + if (primaryScreenChanged) { + if (auto theme = QWindowsTheme::instance()) // QTBUG-85734/Wine + theme->refreshFonts(); + } + return true; +} + +void QWindowsScreenManager::clearScreens() +{ + // Delete screens in reverse order to avoid crash in case of multiple screens + while (!m_screens.isEmpty()) + QWindowSystemInterface::handleScreenRemoved(m_screens.takeLast()); +} + +const QWindowsScreen *QWindowsScreenManager::screenAtDp(const QPoint &p) const +{ + for (QWindowsScreen *scr : m_screens) { + if (scr->geometry().contains(p)) + return scr; + } + return nullptr; +} + +const QWindowsScreen *QWindowsScreenManager::screenForHwnd(HWND hwnd) const +{ + HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); + if (hMonitor == nullptr) + return nullptr; + const auto it = + std::find_if(m_screens.cbegin(), m_screens.cend(), + [hMonitor](const QWindowsScreen *s) + { + return s->data().hMonitor == hMonitor + && (s->data().flags & QWindowsScreenData::VirtualDesktop) != 0; + }); + return it != m_screens.cend() ? *it : nullptr; +} + +QT_END_NAMESPACE diff --git a/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp b/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp new file mode 100644 index 00000000..4fccec98 --- /dev/null +++ b/qtbase/src/plugins/platforms/windows/qwindowstheme.cpp @@ -0,0 +1,1144 @@ +// 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 + +#include "qwindowstheme.h" +#include "qwindowsmenu.h" +#include "qwindowsdialoghelpers.h" +#include "qwindowscontext.h" +#include "qwindowsintegration.h" +#if QT_CONFIG(systemtrayicon) +# include "qwindowssystemtrayicon.h" +#endif +#include "qwindowsscreen.h" +#include +#include +#ifndef Q_CC_MINGW +# include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if QT_CONFIG(cpp_winrt) +# include + +# include +#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}; +} + +static inline QColor getSysColor(int index) +{ + COLORREF cr = GetSysColor(index); + return QColor(GetRValue(cr), GetGValue(cr), GetBValue(cr)); +} + +#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 + +// 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(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(¶ms, 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; +} + +[[maybe_unused]] [[nodiscard]] static inline QColor qt_accentColor() +{ + 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(value)); + if (!abgr.isValid()) + return {}; + return QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha()); +} + +/* + 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); + +#if QT_CONFIG(cpp_winrt) + // 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); +#endif + + 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(); + 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() +{ + return { QStringLiteral("WindowsVista"), QStringLiteral("Windows") }; +} + +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 +{ + 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) { +#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); +#endif + m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]); + m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, accent); + m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, accentLight); + m_palettes[CheckBoxPalette]->setColor(QPalette::Inactive, QPalette::Base, accentDarkest); + 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(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) { + QPixmap pixmap; + SHSTOCKICONINFO iconInfo; + memset(&iconInfo, 0, sizeof(iconInfo)); + iconInfo.cbSize = sizeof(iconInfo); + stockFlags |= (pixmapSize.width() > 16 ? SHGFI_LARGEICON : SHGFI_SMALLICON); + if (SHGetStockIconInfo(stockId, SHGFI_ICON | stockFlags, &iconInfo) == S_OK) { + pixmap = qt_pixmapFromWinHICON(iconInfo.hIcon); + DestroyIcon(iconInfo.hIcon); + 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 +class FakePointer +{ +public: + + static_assert(sizeof(T) <= sizeof(void *), "FakePointers can only go that far."); + + static FakePointer *create(T thing) + { + return reinterpret_cast(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(&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 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 > 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::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::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)); +} + +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(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/qwindowswindow.cpp b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp new file mode 100644 index 00000000..e7fc6d06 --- /dev/null +++ b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp @@ -0,0 +1,3476 @@ +// 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 + +#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 +#include +#include +#include +#include +#include // QWINDOWSIZE_MAX +#include +#include +#include + +#include +#include + +#include + +#if QT_CONFIG(vulkan) +#include "qwindowsvulkaninstance.h" +#endif + +#include + +QT_BEGIN_NAMESPACE + +using QWindowCreationContextPtr = QSharedPointer; + +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: + return true; + case QSurface::RasterGLSurface: + return qt_window_private(const_cast(w))->compositing; + case QSurface::VulkanSurface: + return true; + 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(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(prop.value()); + } + + 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(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; + } +} + +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(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(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(title.utf16()); + const auto *classNameUtf16 = reinterpret_cast(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; +} + +/*! + \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() || w->flags().testFlag(Qt::FramelessWindowHint)) + 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) +{ + if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) + return {}; + 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() || w->flags().testFlag(Qt::FramelessWindowHint)) + 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() || w->flags().testFlag(Qt::FramelessWindowHint)) + 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) +{ + if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint)) + return {}; + 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() || w->flags().testFlag(Qt::FramelessWindowHint)) + 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(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(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(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_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(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(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(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); + 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(w, obtainedScreen->screen()); + if (state != Qt::WindowMaximized && state != Qt::WindowFullScreen + && creationContext->requestedGeometryIn != obtainedGeometry) { + QWindowSystemInterface::handleGeometryChange(w, obtainedGeometry); + } + } + QWindowsWindow::setSavedDpi(GetDpiForWindow(handle())); +} + +QSurfaceFormat QWindowsWindow::format() const +{ + return window()->requestedFormat(); +} + +void QWindowsWindow::fireExpose(const QRegion ®ion, 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(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(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 ¶meters, + 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(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(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(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); + // 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(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 (m_data.flags & Qt::FramelessWindowHint) + 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(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 " <