Compare commits

..
18 Commits
Author SHA1 Message Date
Cameron Gutman 781b26fd1c Completely disable connection caching on Qt 6.12+ 2026-10-22 23:01:46 -05:00
Cameron Gutman 74173b26eb Enforce a TCP/TLS HTTP timeout on Qt 6.3+
Timeouts for long requests like launch or resume timed the entire
HTTP operation. If the host was no longer reachable, we could
spend 2 minutes sitting there, even though we hadn't even made
successful contact with the host to give it our request yet.

Now we will time out a request if we can't send it in 5 seconds,
regardless of how long we want to wait for the server to respond.
2026-10-22 22:54:32 -05:00
Cameron Gutman b709bf5402 Handle musl headers without __NR_riscv_hwprobe defined 2026-10-22 22:14:04 -05:00
Cameron Gutman d79536b56d Only accept usable bounds if they differ from the full bounds
Some platforms (Wayland) just report the full display area, so we
incorrectly assume the entire display is usable for a window.

Fixes #2056
2026-10-22 20:53:30 -05:00
Cameron Gutman 74020b537b Don't start polling from the GUI for a CLI launch
We can end up missing early computerStateChanged signals.

Fixes #2039
2026-10-22 19:28:12 -05:00
Cameron Gutman dc92b6d2f3 Update moonlight-common-c 2026-10-20 00:35:04 -05:00
Cameron Gutman 9af0e9f539 Improve robustness and error handling of DXVsyncSource 2026-10-20 00:29:25 -05:00
Cameron Gutman e4db5c2c18 Check for pacing queue emptiness before dequeuing
This shouldn't actually be reachable when the queue
is empty, but we can check anyway to be safe.

Ref: #2054
2026-10-19 00:25:00 -05:00
Cameron Gutman 9b8886a6f5 Fix integer truncation warning on MSVC 2026-10-19 00:15:26 -05:00
Cameron Gutman f018a71a8a Override NSApplicationUpdateCycleEnabled to fix reduced mouse polling rate
Apps built with the macOS 26+ SDK default to an event loop synchronized to the display which caps input polling rates.

We have to use the plist for the opt-out, since NSApp is already set up by Qt before SDL can set this default.

Fixes #2036
2026-10-17 14:06:10 -05:00
Cameron Gutman b0af84ead1 Deploy the OpenSSL TLS backend for Qt
It supports TLS 1.3 and PQ-KEX back to Windows 10, unlike Schannel
2026-10-14 02:01:01 -05:00
Cameron Gutman 289782c6f3 Fix possible UB shift in bs_write_u() for large values of n 2026-10-14 01:51:15 -05:00
Cameron Gutman ee557e83c6 Include the trailing 16 bytes in the challengeResponseData check 2026-10-14 01:49:44 -05:00
Cameron Gutman 2bd4b9f721 Add missing checks for X509 parsing failures 2026-10-14 01:39:00 -05:00
Cameron Gutman fa6ae80940 Clamp additional values that can be used as array indices
These aren't exploitable in moonlight due to clamping done on the read side.
2026-10-14 01:09:54 -05:00
Cameron Gutman 71c3191e0c Remove warn_off for h264bitstream 2026-10-12 02:31:48 -05:00
Cameron Gutman a614924502 Don't hardcode the QSslKey algorithm 2026-10-12 02:31:21 -05:00
Cameron Gutman a57e947d71 Re-add the h264bitstream file headers mistakenly removed during vendoring 2026-10-07 20:19:06 -05:00
21 changed files with 360 additions and 117 deletions

No files matched your search

+2
View File
@@ -8,6 +8,8 @@
<string>Moonlight</string> <string>Moonlight</string>
<key>LSSupportsGameMode</key> <key>LSSupportsGameMode</key>
<true/> <true/>
<key>NSApplicationUpdateCycleEnabled</key>
<false/>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>Stream games and other applications from another PC</string> <string>Stream games and other applications from another PC</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
+1 -1
View File
@@ -186,7 +186,7 @@ IdentityManager::getSslKey()
BUF_MEM* mem; BUF_MEM* mem;
BIO_get_mem_ptr(bio, &mem); BIO_get_mem_ptr(bio, &mem);
m_CachedSslKey = QSslKey(QByteArray(mem->data, (int)mem->length), QSsl::Rsa); m_CachedSslKey = QSslKey(QByteArray(mem->data, (int)mem->length), getSslCertificate().publicKey().algorithm());
BIO_free(bio); BIO_free(bio);
EVP_PKEY_free(pk); EVP_PKEY_free(pk);
+38 -4
View File
@@ -507,23 +507,47 @@ NvHTTP::openConnection(QUrl baseUrl,
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false); request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
#endif #endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
// Use fine-grained idle timeouts to avoid calling QNetworkAccessManager::clearAccessCache(), // Use fine-grained idle timeouts to avoid calling QNetworkAccessManager::clearAccessCache(),
// which tears down the NAM's global thread each time. We must not keep persistent connections // which tears down the NAM's global thread each time. We must not keep persistent connections
// or GFE will puke. // or GFE will puke.
#if QT_VERSION >= QT_VERSION_CHECK(6, 12, 0)
request.setAttribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute, -1);
#elif QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
request.setAttribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute, 0); request.setAttribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute, 0);
#endif #endif
auto sslErrorsConnection = connect(m_Nam, &QNetworkAccessManager::sslErrors, this, &NvHTTP::handleSslErrors); auto sslErrorsConnection = connect(m_Nam, &QNetworkAccessManager::sslErrors, this, &NvHTTP::handleSslErrors);
QNetworkReply* reply = m_Nam->get(request); QNetworkReply* reply = m_Nam->get(request);
// Run the request with a timeout if requested
QEventLoop loop; QEventLoop loop;
// Connect the quit signals
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, &loop, &QEventLoop::quit); connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, &loop, &QEventLoop::quit);
// Enforce a transfer timeout if requested
if (timeoutMs) { if (timeoutMs) {
QTimer::singleShot(timeoutMs, &loop, &QEventLoop::quit); QTimer::singleShot(timeoutMs, &loop, &QEventLoop::quit);
} }
#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
// Time the request to distinguish a slow response from an unreachable host
auto* connectTimer = new QTimer(reply);
connectTimer->setSingleShot(true);
connect(connectTimer, &QTimer::timeout, reply, [reply, logLevel, url]() {
if (!reply->isFinished()) {
if (logLevel >= NvLogLevel::NVLL_ERROR) {
qWarning() << "Aborting timed out connection for" << url.toString();
}
reply->setProperty("timeout_reason", "connection_timeout");
reply->abort();
}
});
connect(reply, &QNetworkReply::requestSent, connectTimer, &QTimer::stop);
connectTimer->start(REQUEST_TIMEOUT_MS);
#endif
if (logLevel >= NvLogLevel::NVLL_VERBOSE) { if (logLevel >= NvLogLevel::NVLL_VERBOSE) {
qInfo() << "Executing request:" << url.toString(); qInfo() << "Executing request:" << url.toString();
} }
@@ -559,7 +583,11 @@ NvHTTP::openConnection(QUrl baseUrl,
throw exception; throw exception;
} }
else if (reply->error() == QNetworkReply::OperationCanceledError) { else if (reply->error() == QNetworkReply::OperationCanceledError) {
QtNetworkReplyException exception(QNetworkReply::TimeoutError, "Request timed out"); QString reason = reply->property("timeout_reason").toString();
QtNetworkReplyException exception(QNetworkReply::TimeoutError,
reason == "connection_timeout" ?
"Connection timed out" :
"Request timed out");
delete reply; delete reply;
throw exception; throw exception;
} }
@@ -570,5 +598,11 @@ NvHTTP::openConnection(QUrl baseUrl,
} }
} }
#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
// The requestSent signal must have stopped the timer for any successful request,
// otherwise we could falsely time out a slow response when we connected fine.
Q_ASSERT(!connectTimer->isActive());
#endif
return reply; return reply;
} }
+11 -2
View File
@@ -140,6 +140,9 @@ NvPairingManager::getSignatureFromPemCert(const QByteArray& certificate)
X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
BIO_free_all(bio); BIO_free_all(bio);
if (cert == nullptr) {
return QByteArray();
}
QByteArray signature = getSignatureFromCert(cert); QByteArray signature = getSignatureFromCert(cert);
X509_free(cert); X509_free(cert);
@@ -159,9 +162,15 @@ NvPairingManager::verifySignature(const QByteArray& data, const QByteArray& sign
X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
BIO_free_all(bio); BIO_free_all(bio);
if (cert == nullptr) {
return false;
}
EVP_PKEY* pubKey = X509_get_pubkey(cert); EVP_PKEY* pubKey = X509_get_pubkey(cert);
THROW_BAD_ALLOC_IF_NULL(pubKey); if (pubKey == nullptr) {
X509_free(cert);
return false;
}
EVP_MD_CTX* mdctx = EVP_MD_CTX_create(); EVP_MD_CTX* mdctx = EVP_MD_CTX_create();
THROW_BAD_ALLOC_IF_NULL(mdctx); THROW_BAD_ALLOC_IF_NULL(mdctx);
@@ -278,7 +287,7 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
} }
QByteArray challengeResponseData = decrypt(m_Http.getXmlStringFromHex(challengeXml, "challengeresponse"), aesKey); QByteArray challengeResponseData = decrypt(m_Http.getXmlStringFromHex(challengeXml, "challengeresponse"), aesKey);
if (challengeResponseData.size() < hashLength) { if (challengeResponseData.size() < hashLength + 16) {
qCritical() << "Invalid challengeresponse at stage #2"; qCritical() << "Invalid challengeresponse at stage #2";
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS); m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
return PairState::FAILED; return PairState::FAILED;
+36 -28
View File
@@ -51,7 +51,7 @@ ApplicationWindow {
} }
// Display any modal dialogs for configuration warnings // Display any modal dialogs for configuration warnings
if (runConfigChecks) { if (isGuiLaunch) {
if (SystemProperties.isWow64) { if (SystemProperties.isWow64) {
wow64Dialog.open() wow64Dialog.open()
} }
@@ -171,24 +171,28 @@ ApplicationWindow {
} }
onVisibleChanged: { onVisibleChanged: {
// When we become invisible while streaming is going on, // Polling is only managed from the window for a normal GUI launch;
// stop polling immediately. // CLI commands (stream/quit/pair) manage it via the ComputerSeeker.
if (!visible) { if (isGuiLaunch) {
inactivityTimer.stop() // When we become invisible while streaming is going on,
// stop polling immediately.
if (!visible) {
inactivityTimer.stop()
if (pollingActive) { if (pollingActive) {
ComputerManager.stopPollingAsync() ComputerManager.stopPollingAsync()
pollingActive = false pollingActive = false
}
} }
} else if (active) {
else if (active) { // When we become visible and active again, start polling
// When we become visible and active again, start polling inactivityTimer.stop()
inactivityTimer.stop()
// Restart polling if it was stopped // Restart polling if it was stopped
if (!pollingActive) { if (!pollingActive) {
ComputerManager.startPolling() ComputerManager.startPolling()
pollingActive = true pollingActive = true
}
} }
} }
@@ -197,20 +201,24 @@ ApplicationWindow {
} }
onActiveChanged: { onActiveChanged: {
if (active) { // Polling is only managed from the window for a normal GUI launch;
// Stop the inactivity timer // CLI commands (stream/quit/pair) manage it via the ComputerSeeker.
inactivityTimer.stop() if (isGuiLaunch) {
if (active) {
// Stop the inactivity timer
inactivityTimer.stop()
// Restart polling if it was stopped // Restart polling if it was stopped
if (!pollingActive) { if (!pollingActive) {
ComputerManager.startPolling() ComputerManager.startPolling()
pollingActive = true pollingActive = true
}
}
else {
// Start the inactivity timer to stop polling
// if focus does not return within a few minutes.
inactivityTimer.restart()
} }
}
else {
// Start the inactivity timer to stop polling
// if focus does not return within a few minutes.
inactivityTimer.restart()
} }
// Poll for gamepad input only when the window is in focus // Poll for gamepad input only when the window is in focus
+1 -1
View File
@@ -1034,7 +1034,7 @@ int main(int argc, char *argv[])
if (hasGUI) { if (hasGUI) {
engine.rootContext()->setContextProperty("initialView", initialView); engine.rootContext()->setContextProperty("initialView", initialView);
engine.rootContext()->setContextProperty("runConfigChecks", commandLineParserResult == GlobalCommandLineParser::NormalStartRequested); engine.rootContext()->setContextProperty("isGuiLaunch", commandLineParserResult == GlobalCommandLineParser::NormalStartRequested);
// Load the main.qml file // Load the main.qml file
engine.load(QUrl(QStringLiteral("qrc:/gui/main.qml"))); engine.load(QUrl(QStringLiteral("qrc:/gui/main.qml")));
+37 -23
View File
@@ -1363,34 +1363,48 @@ void Session::getWindowDimensions(int& x, int& y,
} }
} }
// Get the usable display bounds
SDL_Rect usableBounds; SDL_Rect usableBounds;
if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) == 0) { if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) != 0) {
// If the stream resolution fits within the usable display area, use it directly
if (m_StreamConfig.width <= usableBounds.w &&
m_StreamConfig.height <= usableBounds.h) {
width = m_StreamConfig.width;
height = m_StreamConfig.height;
} else {
// Otherwise, use 80% of usable bounds and preserve aspect ratio
SDL_Rect src, dst;
src.x = src.y = dst.x = dst.y = 0;
src.w = m_StreamConfig.width;
src.h = m_StreamConfig.height;
dst.w = ((int)(usableBounds.w * 0.80f)) & ~0x1; // even width
dst.h = ((int)(usableBounds.h * 0.80f)) & ~0x1; // even height
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
width = dst.w;
height = dst.h;
}
}
else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_GetDisplayUsableBounds() failed: %s", "SDL_GetDisplayUsableBounds() failed: %s",
SDL_GetError()); SDL_GetError());
SDL_zero(usableBounds);
}
// Use the usable display bounds if they are valid
if (usableBounds.w != 0 &&
WMUtils::hasDisplayUsableBounds() &&
m_StreamConfig.width <= usableBounds.w &&
m_StreamConfig.height <= usableBounds.h) {
// We fit pixel-perfect in the usable area, so don't scale at all
width = m_StreamConfig.width;
height = m_StreamConfig.height;
}
else if (usableBounds.w != 0 || SDL_GetDisplayBounds(displayIndex, &usableBounds) == 0) {
// Otherwise, use 80% of usable bounds and preserve aspect ratio
SDL_Rect src, dst;
src.x = src.y = dst.x = dst.y = 0;
src.w = m_StreamConfig.width;
src.h = m_StreamConfig.height;
dst.w = ((int)(usableBounds.w * 0.80f)) & ~0x1; // even width
dst.h = ((int)(usableBounds.h * 0.80f)) & ~0x1; // even height
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
// Only scale the size down, never up
if (m_StreamConfig.width >= dst.w || m_StreamConfig.height >= dst.h) {
width = dst.w;
height = dst.h;
}
else {
width = m_StreamConfig.width;
height = m_StreamConfig.height;
}
}
else {
// Oh well, hope for the best
width = m_StreamConfig.width; width = m_StreamConfig.width;
height = m_StreamConfig.height; height = m_StreamConfig.height;
} }
+4 -1
View File
@@ -38,7 +38,6 @@
#include <asm/hwprobe.h> #include <asm/hwprobe.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#else #else
#define __NR_riscv_hwprobe 258
struct riscv_hwprobe { struct riscv_hwprobe {
int64_t key; int64_t key;
uint64_t value; uint64_t value;
@@ -46,6 +45,10 @@ struct riscv_hwprobe {
#define RISCV_HWPROBE_KEY_IMA_EXT_0 4 #define RISCV_HWPROBE_KEY_IMA_EXT_0 4
#endif #endif
#ifndef __NR_riscv_hwprobe
#define __NR_riscv_hwprobe 258
#endif
// RISC-V Scalar AES [E]ncryption and [D]ecryption // RISC-V Scalar AES [E]ncryption and [D]ecryption
#ifndef RISCV_HWPROBE_EXT_ZKND #ifndef RISCV_HWPROBE_EXT_ZKND
#define RISCV_HWPROBE_EXT_ZKND (1 << 11) #define RISCV_HWPROBE_EXT_ZKND (1 << 11)
@@ -10,6 +10,8 @@ DxVsyncSource::DxVsyncSource(Pacer* pacer) :
m_LastMonitor(nullptr) m_LastMonitor(nullptr)
{ {
SDL_zero(m_WaitForVblankEventParams); SDL_zero(m_WaitForVblankEventParams);
m_WaitForVblankEventParams.VidPnSourceId = D3DDDI_ID_UNINITIALIZED;
SDL_zero(m_AdapterLuid);
} }
DxVsyncSource::~DxVsyncSource() DxVsyncSource::~DxVsyncSource()
@@ -72,41 +74,43 @@ bool DxVsyncSource::isAsync()
return false; return false;
} }
void DxVsyncSource::waitForVsync() bool DxVsyncSource::waitForVsync()
{ {
NTSTATUS status; NTSTATUS status;
// If the monitor has changed from last time, open the new adapter // If the monitor has changed from last time, open the new adapter
HMONITOR currentMonitor = MonitorFromWindow(m_Window, MONITOR_DEFAULTTONEAREST); HMONITOR currentMonitor = MonitorFromWindow(m_Window, MONITOR_DEFAULTTONEAREST);
if (currentMonitor != m_LastMonitor) { if (!currentMonitor || currentMonitor != m_LastMonitor) {
// Close the old adapter
if (m_WaitForVblankEventParams.hAdapter != 0) {
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
closeAdapterParams.hAdapter = m_WaitForVblankEventParams.hAdapter;
m_D3DKMTCloseAdapter(&closeAdapterParams);
m_WaitForVblankEventParams.hAdapter = 0;
}
m_LastMonitor = nullptr;
// We can't wait if we're not on any monitor
if (!currentMonitor) {
return false;
}
MONITORINFOEXA monitorInfo = {}; MONITORINFOEXA monitorInfo = {};
monitorInfo.cbSize = sizeof(monitorInfo); monitorInfo.cbSize = sizeof(monitorInfo);
if (!GetMonitorInfoA(currentMonitor, &monitorInfo)) { if (!GetMonitorInfoA(currentMonitor, &monitorInfo)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"GetMonitorInfo() failed: %d", "GetMonitorInfo() failed: %d",
GetLastError()); GetLastError());
return; return false;
} }
DEVMODEA monitorMode; DEVMODEA monitorMode = {};
monitorMode.dmSize = sizeof(monitorMode); monitorMode.dmSize = sizeof(monitorMode);
if (!EnumDisplaySettingsA(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &monitorMode)) { if (!EnumDisplaySettingsA(monitorInfo.szDevice, ENUM_CURRENT_SETTINGS, &monitorMode)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"EnumDisplaySettings() failed: %d", "EnumDisplaySettings() failed: %d",
GetLastError()); GetLastError());
return; return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Monitor changed: %s %d Hz",
monitorInfo.szDevice,
monitorMode.dmDisplayFrequency);
// Close the old adapter
if (m_WaitForVblankEventParams.hAdapter != 0) {
D3DKMT_CLOSEADAPTER closeAdapterParams = {};
closeAdapterParams.hAdapter = m_WaitForVblankEventParams.hAdapter;
m_D3DKMTCloseAdapter(&closeAdapterParams);
} }
D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {}; D3DKMT_OPENADAPTERFROMHDC openAdapterParams = {};
@@ -115,7 +119,7 @@ void DxVsyncSource::waitForVsync()
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"CreateDC() failed: %d", "CreateDC() failed: %d",
GetLastError()); GetLastError());
return; return false;
} }
// Open the new adapter // Open the new adapter
@@ -126,21 +130,38 @@ void DxVsyncSource::waitForVsync()
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"D3DKMTOpenAdapterFromHdc() failed: %x", "D3DKMTOpenAdapterFromHdc() failed: %x",
status); status);
return; return false;
}
// VidPnSourceId is scoped to the adapter, so we must also check the adapter LUID
if (m_AdapterLuid.LowPart != openAdapterParams.AdapterLuid.LowPart ||
m_AdapterLuid.HighPart != openAdapterParams.AdapterLuid.HighPart ||
m_WaitForVblankEventParams.VidPnSourceId != openAdapterParams.VidPnSourceId) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Monitor changed: %s %d Hz",
monitorInfo.szDevice,
monitorMode.dmDisplayFrequency);
} }
m_WaitForVblankEventParams.hAdapter = openAdapterParams.hAdapter; m_WaitForVblankEventParams.hAdapter = openAdapterParams.hAdapter;
m_WaitForVblankEventParams.hDevice = 0; m_WaitForVblankEventParams.hDevice = 0;
m_WaitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId; m_WaitForVblankEventParams.VidPnSourceId = openAdapterParams.VidPnSourceId;
m_AdapterLuid = openAdapterParams.AdapterLuid;
m_LastMonitor = currentMonitor; m_LastMonitor = currentMonitor;
} }
status = m_D3DKMTWaitForVerticalBlankEvent(&m_WaitForVblankEventParams); status = m_D3DKMTWaitForVerticalBlankEvent(&m_WaitForVblankEventParams);
if (status != STATUS_SUCCESS) { if (status != STATUS_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, if (status != STATUS_GRAPHICS_PRESENT_OCCLUDED) {
"D3DKMTWaitForVerticalBlankEvent() failed: %x", SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
status); "D3DKMTWaitForVerticalBlankEvent() failed: %x",
return; status);
}
m_LastMonitor = nullptr;
return false;
} }
return true;
} }
@@ -10,6 +10,7 @@ typedef UINT D3DKMT_HANDLE;
typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID; typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID;
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) #define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_GRAPHICS_PRESENT_OCCLUDED ((NTSTATUS)0xC01E0006L) #define STATUS_GRAPHICS_PRESENT_OCCLUDED ((NTSTATUS)0xC01E0006L)
#define D3DDDI_ID_UNINITIALIZED (UINT)(~0)
typedef struct _D3DKMT_OPENADAPTERFROMHDC { typedef struct _D3DKMT_OPENADAPTERFROMHDC {
HDC hDc; HDC hDc;
D3DKMT_HANDLE hAdapter; D3DKMT_HANDLE hAdapter;
@@ -39,13 +40,14 @@ public:
virtual bool isAsync() override; virtual bool isAsync() override;
virtual void waitForVsync() override; virtual bool waitForVsync() override;
private: private:
Pacer* m_Pacer; Pacer* m_Pacer;
HMODULE m_Gdi32Handle; HMODULE m_Gdi32Handle;
HWND m_Window; HWND m_Window;
HMONITOR m_LastMonitor; HMONITOR m_LastMonitor;
LUID m_AdapterLuid;
D3DKMT_WAITFORVERTICALBLANKEVENT m_WaitForVblankEventParams; D3DKMT_WAITFORVERTICALBLANKEVENT m_WaitForVblankEventParams;
PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc; PFND3DKMTOPENADAPTERFROMHDC m_D3DKMTOpenAdapterFromHdc;
@@ -115,13 +115,20 @@ int Pacer::vsyncThread(void *context)
while (!me->m_Stopping) { while (!me->m_Stopping) {
if (async) { if (async) {
// Wait for the VSync source to invoke signalVsync() or 100ms to elapse // Wait for the VSync source to invoke signalVsync() or 100ms to elapse
me->m_FrameQueueLock.lock(); QMutexLocker locker(&me->m_FrameQueueLock);
me->m_VsyncSignalled.wait(&me->m_FrameQueueLock, 100); me->m_VsyncSignalled.wait(&me->m_FrameQueueLock, 100);
me->m_FrameQueueLock.unlock();
} }
else { else {
// Let the VSync source wait in the context of our thread // Let the VSync source wait in the context of our thread
me->m_VsyncSource->waitForVsync(); if (!me->m_VsyncSource->waitForVsync()) {
// Fall back to a fixed delay if the VSync source fails
//
// NB: We don't actually expect signalVsync() to be called, since
// this is a synchronous VSync source, but we wait on m_VsyncSignalled
// anyway so that the destructor can immediately wake us on shutdown.
QMutexLocker locker(&me->m_FrameQueueLock);
me->m_VsyncSignalled.wait(&me->m_FrameQueueLock, 1000 / me->m_DisplayFps);
}
} }
if (me->m_Stopping) { if (me->m_Stopping) {
@@ -148,24 +155,25 @@ int Pacer::renderThread(void* context)
// Wait for the renderer to be ready for the next frame // Wait for the renderer to be ready for the next frame
me->m_VsyncRenderer->waitToRender(); me->m_VsyncRenderer->waitToRender();
// Acquire the frame queue lock to protect the queue and AVFrame* frame;
// the not empty condition {
me->m_FrameQueueLock.lock(); // Acquire the frame queue lock to protect the queue and
// the not empty condition
QMutexLocker locker(&me->m_FrameQueueLock);
// Wait for a frame to be ready to render // Wait for a frame to be ready to render
while (!me->m_Stopping && me->m_RenderQueue.isEmpty()) { while (!me->m_Stopping && me->m_RenderQueue.isEmpty()) {
me->m_RenderQueueNotEmpty.wait(&me->m_FrameQueueLock); me->m_RenderQueueNotEmpty.wait(&me->m_FrameQueueLock);
}
if (me->m_Stopping) {
// Exit this thread
break;
}
frame = me->m_RenderQueue.dequeue();
} }
if (me->m_Stopping) {
// Exit this thread
me->m_FrameQueueLock.unlock();
break;
}
AVFrame* frame = me->m_RenderQueue.dequeue();
me->m_FrameQueueLock.unlock();
me->renderFrame(frame); me->renderFrame(frame);
} }
@@ -249,7 +257,7 @@ void Pacer::handleVsync(int timeUntilNextVsyncMillis)
return; return;
} }
if (m_Stopping) { if (m_Stopping || m_PacingQueue.isEmpty()) {
m_FrameQueueLock.unlock(); m_FrameQueueLock.unlock();
return; return;
} }
@@ -349,7 +357,7 @@ void Pacer::renderFrame(AVFrame* frame)
av_frame_free(&frame); av_frame_free(&frame);
// Drop frames if we have too many queued up for a while // Drop frames if we have too many queued up for a while
m_FrameQueueLock.lock(); QMutexLocker frameQueueLocker(&m_FrameQueueLock);
int frameDropTarget; int frameDropTarget;
@@ -382,13 +390,11 @@ void Pacer::renderFrame(AVFrame* frame)
AVFrame* frame = m_RenderQueue.dequeue(); AVFrame* frame = m_RenderQueue.dequeue();
// Drop the lock while we call av_frame_free() // Drop the lock while we call av_frame_free()
m_FrameQueueLock.unlock(); frameQueueLocker.unlock();
m_VideoStats->pacerDroppedFrames++; m_VideoStats->pacerDroppedFrames++;
av_frame_free(&frame); av_frame_free(&frame);
m_FrameQueueLock.lock(); frameQueueLocker.relock();
} }
m_FrameQueueLock.unlock();
} }
void Pacer::dropFrameForEnqueue(QQueue<AVFrame*>& queue) void Pacer::dropFrameForEnqueue(QQueue<AVFrame*>& queue)
@@ -22,9 +22,10 @@ public:
// sources require calls to waitForVsync(). // sources require calls to waitForVsync().
virtual bool isAsync() = 0; virtual bool isAsync() = 0;
virtual void waitForVsync() { virtual bool waitForVsync() {
// Synchronous sources must implement waitForVsync()! // Synchronous sources must implement waitForVsync()!
SDL_assert(false); SDL_assert(false);
return false;
} }
}; };
+1
View File
@@ -12,6 +12,7 @@ namespace WMUtils {
bool isRunningWayland(); bool isRunningWayland();
bool isRunningWindowManager(); bool isRunningWindowManager();
bool isRunningDesktopEnvironment(); bool isRunningDesktopEnvironment();
bool hasDisplayUsableBounds();
QString getDrmCardOverride(); QString getDrmCardOverride();
bool isGpuSlow(); bool isGpuSlow();
} }
+46
View File
@@ -205,6 +205,52 @@ bool WMUtils::isRunningDesktopEnvironment()
#endif #endif
} }
bool WMUtils::hasDisplayUsableBounds()
{
static SDL_atomic_t hasDisplayUsableBounds;
// If the value is not set yet, populate it now.
int val = SDL_AtomicGet(&hasDisplayUsableBounds);
if (!(val & VALUE_SET)) {
int numDisplays = SDL_GetNumVideoDisplays();
bool hasUsableBounds = false;
for (int i = 0; i < numDisplays; i++) {
SDL_Rect displayBounds;
SDL_Rect usableBounds;
if (SDL_GetDisplayBounds(i, &displayBounds) != 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"SDL_GetDisplayBounds(%d) failed: %s",
i, SDL_GetError());
continue;
}
if (SDL_GetDisplayUsableBounds(i, &usableBounds) != 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"SDL_GetDisplayUsableBounds(%d) failed: %s",
i, SDL_GetError());
continue;
}
// If at least one display has usable bounds that differ from the
// complete bounds, we assume that usable bounds are accurate.
if (!SDL_RectEquals(&displayBounds, &usableBounds)) {
hasUsableBounds = true;
break;
}
}
// Populate the value to return and have for next time.
// This can race with another thread populating the same data,
// but that's no big deal.
val = VALUE_SET | (hasUsableBounds ? VALUE_TRUE : 0);
SDL_AtomicSet(&hasDisplayUsableBounds, val);
}
return !!(val & VALUE_TRUE);
}
bool WMUtils::isGpuSlow() bool WMUtils::isGpuSlow()
{ {
bool ret; bool ret;
+25 -3
View File
@@ -1,3 +1,25 @@
/*
* h264bitstream - a library for reading and writing H.264 video
* Copyright (C) 2005-2007 Auroras Entertainment, LLC
* Copyright (C) 2008-2011 Avail-TVN
*
* Written by Alex Izvorski <aizvorski@gmail.com> and Alex Giladi <alex.giladi@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _H264_BS_H #ifndef _H264_BS_H
#define _H264_BS_H 1 #define _H264_BS_H 1
@@ -79,7 +101,7 @@ static inline int bs_eof(bs_t* b) { if (b->p >= b->end) { return 1; } else { ret
static inline int bs_overrun(bs_t* b) { if (b->p > b->end) { return 1; } else { return 0; } } static inline int bs_overrun(bs_t* b) { if (b->p > b->end) { return 1; } else { return 0; } }
static inline int bs_pos(bs_t* b) { if (b->p > b->end) { return (b->end - b->start); } else { return (b->p - b->start); } } static inline int bs_pos(bs_t* b) { if (b->p > b->end) { return (int)(b->end - b->start); } else { return (int)(b->p - b->start); } }
static inline uint32_t bs_read_u1(bs_t* b) static inline uint32_t bs_read_u1(bs_t* b)
{ {
@@ -186,7 +208,7 @@ static inline void bs_write_u(bs_t* b, int n, uint32_t v)
int i; int i;
for (i = 0; i < n; i++) for (i = 0; i < n; i++)
{ {
bs_write_u1(b, (v >> ( n - i - 1 ))&0x01 ); bs_write_u1(b, (n - i - 1 >= 32) ? 0 : ((v >> ( n - i - 1 ))&0x01) );
} }
} }
@@ -195,7 +217,7 @@ static inline void bs_write_u8(bs_t* b, uint32_t v)
#ifdef FAST_U8 #ifdef FAST_U8
if (b->bits_left == 8 && ! bs_eof(b)) // can do fast write if (b->bits_left == 8 && ! bs_eof(b)) // can do fast write
{ {
b->p[0] = v; b->p[0] = (uint8_t)v;
b->p++; b->p++;
return; return;
} }
+22
View File
@@ -1,3 +1,25 @@
/*
* h264bitstream - a library for reading and writing H.264 video
* Copyright (C) 2005-2007 Auroras Entertainment, LLC
* Copyright (C) 2008-2011 Avail-TVN
*
* Written by Alex Izvorski <aizvorski@gmail.com> and Alex Giladi <alex.giladi@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
+33
View File
@@ -1,3 +1,26 @@
/*
* h264bitstream - a library for reading and writing H.264 video
* Copyright (C) 2005-2007 Auroras Entertainment, LLC
* Copyright (C) 2008-2011 Avail-TVN
* Copyright (C) 2012 Alex Izvorski
*
* Written by Alex Izvorski <aizvorski@gmail.com> and Alex Giladi <alex.giladi@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
@@ -361,6 +384,11 @@ void write_seq_parameter_set_rbsp(sps_t* sps, bs_t* b)
} }
else if( sps->pic_order_cnt_type == 1 ) else if( sps->pic_order_cnt_type == 1 )
{ {
if (sps->num_ref_frames_in_pic_order_cnt_cycle > 256)
{
sps->num_ref_frames_in_pic_order_cnt_cycle = 256;
}
bs_write_u1(b, sps->delta_pic_order_always_zero_flag); bs_write_u1(b, sps->delta_pic_order_always_zero_flag);
bs_write_se(b, sps->offset_for_non_ref_pic); bs_write_se(b, sps->offset_for_non_ref_pic);
bs_write_se(b, sps->offset_for_top_to_bottom_field); bs_write_se(b, sps->offset_for_top_to_bottom_field);
@@ -506,6 +534,11 @@ void write_vui_parameters(sps_t* sps, bs_t* b)
void write_hrd_parameters(hrd_t* hrd, bs_t* b) void write_hrd_parameters(hrd_t* hrd, bs_t* b)
{ {
if (hrd->cpb_cnt_minus1 > 31)
{
hrd->cpb_cnt_minus1 = 31;
}
bs_write_ue(b, hrd->cpb_cnt_minus1); bs_write_ue(b, hrd->cpb_cnt_minus1);
bs_write_u(b, 4, hrd->bit_rate_scale); bs_write_u(b, 4, hrd->bit_rate_scale);
bs_write_u(b, 4, hrd->cpb_size_scale); bs_write_u(b, 4, hrd->cpb_size_scale);
+22
View File
@@ -1,3 +1,25 @@
/*
* h264bitstream - a library for reading and writing H.264 video
* Copyright (C) 2005-2007 Auroras Entertainment, LLC
* Copyright (C) 2008-2011 Avail-TVN
*
* Written by Alex Izvorski <aizvorski@gmail.com> and Alex Giladi <alex.giladi@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _H264_STREAM_H #ifndef _H264_STREAM_H
#define _H264_STREAM_H 1 #define _H264_STREAM_H 1
-3
View File
@@ -13,9 +13,6 @@ TEMPLATE = lib
# Build a static library # Build a static library
CONFIG += staticlib CONFIG += staticlib
# Disable warnings
CONFIG += warn_off
# Include global qmake defs # Include global qmake defs
include(../globaldefs.pri) include(../globaldefs.pri)
+1 -1
View File
@@ -250,7 +250,7 @@ if not x%QT_PATH:\5.=%==x%QT_PATH% (
set WINDEPLOYQT_ARGS=--no-qmltooling --no-virtualkeyboard set WINDEPLOYQT_ARGS=--no-qmltooling --no-virtualkeyboard
) else ( ) else (
rem Qt 6.8+ rem Qt 6.8+
set WINDEPLOYQT_ARGS=--no-system-d3d-compiler --no-system-dxc-compiler --skip-plugin-types qmltooling,generic --no-ffmpeg set WINDEPLOYQT_ARGS=--no-system-d3d-compiler --no-system-dxc-compiler --skip-plugin-types qmltooling,generic --no-ffmpeg --force-openssl
set WINDEPLOYQT_ARGS=!WINDEPLOYQT_ARGS! --no-quickcontrols2fusion --no-quickcontrols2imagine --no-quickcontrols2universal set WINDEPLOYQT_ARGS=!WINDEPLOYQT_ARGS! --no-quickcontrols2fusion --no-quickcontrols2imagine --no-quickcontrols2universal
set WINDEPLOYQT_ARGS=!WINDEPLOYQT_ARGS! --no-quickcontrols2fusionstyleimpl --no-quickcontrols2imaginestyleimpl --no-quickcontrols2universalstyleimpl --no-quickcontrols2windowsstyleimpl --no-quickcontrols2fluentwinui3styleimpl set WINDEPLOYQT_ARGS=!WINDEPLOYQT_ARGS! --no-quickcontrols2fusionstyleimpl --no-quickcontrols2imaginestyleimpl --no-quickcontrols2universalstyleimpl --no-quickcontrols2windowsstyleimpl --no-quickcontrols2fluentwinui3styleimpl
) )