Compare commits

..
1 Commits
Author SHA1 Message Date
Cameron Gutman 60fadcbf36 Add Qt EGLFS plugin to AppImage 2023-11-07 00:55:21 -06:00
135 changed files with 5239 additions and 12628 deletions

No files matched your search

+3 -4
View File
@@ -37,13 +37,12 @@ List the games you've tried that exhibit the issue. To see if the issue is game-
- OS: [e.g. Windows 10 1809] - OS: [e.g. Windows 10 1809]
- Moonlight Version: [e.g. v0.9.0] - Moonlight Version: [e.g. v0.9.0]
- GPU: [e.g. Intel HD Graphics 520] - GPU: [e.g. Intel HD Graphics 520]
- Linux package type (if applicable): [e.g. Flatpak]
**Server PC details (please complete the following information)** **Server PC details (please complete the following information)**
- OS: [e.g. Windows 10 1809] - OS: [e.g. Windows 10 1809]
- Sunshine or GeForce Experience version: [e.g. Sunshine v0.21.0] - GeForce Experience version: [e.g. 3.16.0.140]
- GPU: [e.g. AMD Radeon RX 7900 XT] - Nvidia GPU driver: [e.g. 417.35]
- GPU driver: [e.g. 24.1.1] - Antivirus and firewall software: [e.g. Windows Defender and Windows Firewall]
**Moonlight Logs (please attach)** **Moonlight Logs (please attach)**
- On Windows, `Moonlight-###.log` files can be found in `%TEMP%`. Simply type that into the File Explorer path field to navigate there. - On Windows, `Moonlight-###.log` files can be found in `%TEMP%`. Simply type that into the File Explorer path field to navigate there.
+11
View File
@@ -158,6 +158,17 @@ private:
L"EZFRD32.dll", L"EZFRD32.dll",
L"EZFRD64.dll", L"EZFRD64.dll",
// These are the newer dList DLLs for Optimus hybrid graphics DDI.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/display/hybrid-system-ddi
//
// We forcefully block them from loading because Optimus has a bug that
// deadlocks DXVA2 when we present with D3DPRESENT_DONOTWAIT. This will prevent
// Optimus from ever using the dGPU even if the user has requested it.
// https://github.com/moonlight-stream/moonlight-qt/issues/240
// https://github.com/moonlight-stream/moonlight-qt/issues/235
L"nvdlist.dll",
L"nvdlistx.dll",
// These are the older dList/AppInit DLLs for Optimus hybrid graphics DDI. // These are the older dList/AppInit DLLs for Optimus hybrid graphics DDI.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/display/hybrid-system-ddi // https://docs.microsoft.com/en-us/windows-hardware/drivers/display/hybrid-system-ddi
// //
+2 -3
View File
@@ -53,10 +53,9 @@ Hosting for Moonlight's Debian and L4T package repositories is graciously provid
* Qt 5.9 SDK or later. Qt 6 is also supported. * Qt 5.9 SDK or later. Qt 6 is also supported.
* GCC or Clang * GCC or Clang
* Install the required packages: * Install the required packages:
* Debian/Ubuntu: `libegl1-mesa-dev libgl1-mesa-dev libopus-dev libqt5svg5-dev libsdl2-dev libsdl2-ttf-dev libssl-dev libavcodec-dev libavformat-dev libva-dev libvdpau-dev libxkbcommon-dev qtwayland5 qt5-qmake qtbase5-dev qtdeclarative5-dev qtquickcontrols2-5-dev wayland-protocols qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtquick-window2 qml-module-qtquick2` * Debian/Ubuntu: `libegl1-mesa-dev libgl1-mesa-dev libopus-dev libqt5svg5-dev libsdl2-dev libsdl2-ttf-dev libssl-dev libavcodec-dev libva-dev libvdpau-dev libxkbcommon-dev qtwayland5 qt5-qmake qtbase5-dev qtdeclarative5-dev qtquickcontrols2-5-dev wayland-protocols qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtquick-window2 qml-module-qtquick2`
* RedHat/Fedora: `openssl-devel SDL2-devel SDL2_ttf-devel ffmpeg-devel qt5-qtsvg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel` * RedHat/Fedora: `openssl-devel SDL2-devel SDL2_ttf-devel ffmpeg-devel qt5-qtsvg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel`
* FFmpeg 4.0 or later is required to build. If your distro doesn't package FFmpeg 4.0 or later, you can build and install it from source on https://ffmpeg.org/ * FFmpeg 4.0 or later is required to build. If your distro doesn't package FFmpeg 4.0 or later, you can build and install it from source on https://ffmpeg.org/
* Building the Vulkan HDR renderer requires a `libplacebo-dev`/`libplacebo-devel` version of at least v338.0 and FFmpeg 6.1 or later.
### Steam Link Build Requirements ### Steam Link Build Requirements
* [Steam Link SDK](https://github.com/ValveSoftware/steamlink-sdk) cloned on your build system * [Steam Link SDK](https://github.com/ValveSoftware/steamlink-sdk) cloned on your build system
@@ -76,7 +75,7 @@ Hosting for Moonlight's Debian and L4T package repositories is graciously provid
* To build from the command line for development use on macOS or Linux, run `qmake moonlight-qt.pro` then `make debug` or `make release` * To build from the command line for development use on macOS or Linux, run `qmake moonlight-qt.pro` then `make debug` or `make release`
* To create an embedded build for a single-purpose device, use `qmake "CONFIG+=embedded" moonlight-qt.pro` and build normally. * To create an embedded build for a single-purpose device, use `qmake "CONFIG+=embedded" moonlight-qt.pro` and build normally.
* This build will lack windowed mode, Discord/Help links, and other features that don't make sense on an embedded device. * This build will lack windowed mode, Discord/Help links, and other features that don't make sense on an embedded device.
* For platforms with poor GPU performance, add `"CONFIG+=gpuslow"` to prefer direct KMSDRM rendering over GL/Vulkan renderers. Direct KMSDRM rendering can use dedicated YUV/RGB conversion and scaling hardware rather than slower GPU shaders for these operations. * For platforms with poor GL performance, add `"CONFIG+=glslow"` to prefer direct KMSDRM rendering over EGL/GLES renderers. Direct KMSDRM rendering can use dedicated YUV/RGB conversion and scaling hardware rather than slower GPU shaders for these operations.
## Contribute ## Contribute
1. Fork us 1. Fork us
+2
View File
@@ -31,6 +31,8 @@
</array> </array>
<key>GCSupportsControllerUserInteraction</key> <key>GCSupportsControllerUserInteraction</key>
<true/> <true/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.games</string>
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>
<dict> <dict>
<key>NSAllowsArbitraryLoads</key> <key>NSAllowsArbitraryLoads</key>
+5 -34
View File
@@ -120,13 +120,6 @@ unix:!macx {
CONFIG += cuda CONFIG += cuda
} }
} }
!disable-libplacebo {
packagesExist(libplacebo) {
PKGCONFIG += libplacebo
CONFIG += libplacebo
}
}
} }
!disable-wayland { !disable-wayland {
@@ -152,8 +145,8 @@ win32:!winrt {
CONFIG += soundio discord-rpc CONFIG += soundio discord-rpc
} }
macx { macx {
LIBS += -lssl -lcrypto -lavcodec.61 -lavutil.59 -lopus -framework SDL2 -framework SDL2_ttf LIBS += -lssl -lcrypto -lavcodec.60 -lavutil.58 -lopus -framework SDL2 -framework SDL2_ttf
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal -framework QuartzCore LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal
# For libsoundio # For libsoundio
LIBS += -framework CoreAudio -framework AudioUnit LIBS += -framework CoreAudio -framework AudioUnit
@@ -298,7 +291,7 @@ mmal {
# significantly better performance than EGL on the Pi. Setting # significantly better performance than EGL on the Pi. Setting
# this option allows EGL usage even if built with MMAL support. # this option allows EGL usage even if built with MMAL support.
# #
# It is highly recommended to also build with 'gpuslow' to avoid # It is highly recommended to also build with 'glslow' to avoid
# EGL being preferred if direct DRM rendering is available. # EGL being preferred if direct DRM rendering is available.
allow-egl-with-mmal { allow-egl-with-mmal {
message(Allowing EGL usage with MMAL enabled) message(Allowing EGL usage with MMAL enabled)
@@ -329,16 +322,6 @@ cuda {
# ffnvcodec uses libdl in cuda_load_functions()/cuda_free_functions() # ffnvcodec uses libdl in cuda_load_functions()/cuda_free_functions()
LIBS += -ldl LIBS += -ldl
} }
libplacebo {
message(Vulkan support enabled via libplacebo)
DEFINES += HAVE_LIBPLACEBO_VULKAN
SOURCES += \
streaming/video/ffmpeg-renderers/plvk.cpp \
streaming/video/ffmpeg-renderers/plvk_c.c
HEADERS += \
streaming/video/ffmpeg-renderers/plvk.h
}
config_EGL { config_EGL {
message(EGL renderer selected) message(EGL renderer selected)
@@ -385,8 +368,7 @@ macx {
message(VideoToolbox renderer selected) message(VideoToolbox renderer selected)
SOURCES += \ SOURCES += \
streaming/video/ffmpeg-renderers/vt_avsamplelayer.mm \ streaming/video/ffmpeg-renderers/vt.mm
streaming/video/ffmpeg-renderers/vt_metal.mm
HEADERS += \ HEADERS += \
streaming/video/ffmpeg-renderers/vt.h streaming/video/ffmpeg-renderers/vt.h
@@ -414,16 +396,6 @@ glslow {
DEFINES += GL_IS_SLOW DEFINES += GL_IS_SLOW
} }
vkslow {
message(Vulkan slow build)
DEFINES += VULKAN_IS_SLOW
}
gpuslow {
message(GPU slow build)
DEFINES += GL_IS_SLOW VULKAN_IS_SLOW
}
wayland { wayland {
message(Wayland extensions enabled) message(Wayland extensions enabled)
@@ -460,8 +432,7 @@ TRANSLATIONS += \
languages/qml_pt_BR.ts \ languages/qml_pt_BR.ts \
languages/qml_pl.ts \ languages/qml_pl.ts \
languages/qml_cs.ts \ languages/qml_cs.ts \
languages/qml_he.ts \ languages/qml_he.ts
languages/qml_ckb.ts
# Additional import path used to resolve QML modules in Qt Creator's code model # Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH = QML_IMPORT_PATH =
+9 -6
View File
@@ -1,7 +1,7 @@
#include "computermanager.h" #include "computermanager.h"
#include "boxartmanager.h" #include "boxartmanager.h"
#include "nvhttp.h" #include "nvhttp.h"
#include "nvpairingmanager.h" #include "settings/streamingpreferences.h"
#include <Limelight.h> #include <Limelight.h>
#include <QtEndian> #include <QtEndian>
@@ -144,8 +144,8 @@ private:
NvComputer* m_Computer; NvComputer* m_Computer;
}; };
ComputerManager::ComputerManager(StreamingPreferences* prefs) ComputerManager::ComputerManager(QObject *parent)
: m_Prefs(prefs), : QObject(parent),
m_PollingRef(0), m_PollingRef(0),
m_MdnsBrowser(nullptr), m_MdnsBrowser(nullptr),
m_CompatFetcher(nullptr), m_CompatFetcher(nullptr),
@@ -352,7 +352,9 @@ void ComputerManager::startPolling()
return; return;
} }
if (m_Prefs->enableMdns) { StreamingPreferences prefs;
if (prefs.enableMdns) {
// Start an MDNS query for GameStream hosts // Start an MDNS query for GameStream hosts
m_MdnsServer.reset(new QMdnsEngine::Server()); m_MdnsServer.reset(new QMdnsEngine::Server());
m_MdnsBrowser = new QMdnsEngine::Browser(m_MdnsServer.data(), "_nvstream._tcp.local."); m_MdnsBrowser = new QMdnsEngine::Browser(m_MdnsServer.data(), "_nvstream._tcp.local.");
@@ -782,9 +784,10 @@ private:
return serverInfo; return serverInfo;
} catch (...) { } catch (...) {
if (!m_Mdns) { if (!m_Mdns) {
unsigned int portTestResult; StreamingPreferences prefs;
int portTestResult;
if (m_ComputerManager->m_Prefs->detectNetworkBlocking) { if (prefs.detectNetworkBlocking) {
// We failed to connect to the specified PC. Let's test to make sure this network // We failed to connect to the specified PC. Let's test to make sure this network
// isn't blocking Moonlight, so we can tell the user about it. // isn't blocking Moonlight, so we can tell the user about it.
portTestResult = LiTestClientConnectivity("qt.conntest.moonlight-stream.org", 443, portTestResult = LiTestClientConnectivity("qt.conntest.moonlight-stream.org", 443,
+3 -17
View File
@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "nvcomputer.h" #include "nvcomputer.h"
#include "settings/streamingpreferences.h" #include "nvpairingmanager.h"
#include "settings/compatfetcher.h" #include "settings/compatfetcher.h"
#include <qmdnsengine/server.h> #include <qmdnsengine/server.h>
@@ -72,16 +72,10 @@ private slots:
void handleResolvedTimeout() void handleResolvedTimeout()
{ {
if (m_Addresses.isEmpty()) { if (m_Addresses.isEmpty()) {
if (m_Retries-- > 0) {
// Try again // Try again
qInfo() << "Resolving" << hostname() << "timed out. Retrying..."; qInfo() << "Resolving" << hostname() << "timed out. Retrying...";
resolve(); resolve();
} }
else {
qWarning() << "Giving up on resolving" << hostname() << "after repeated failures";
cleanup();
}
}
else { else {
Q_ASSERT(!m_Addresses.isEmpty()); Q_ASSERT(!m_Addresses.isEmpty());
emit resolvedHost(this, m_Addresses); emit resolvedHost(this, m_Addresses);
@@ -98,7 +92,7 @@ signals:
void resolvedHost(MdnsPendingComputer*,QVector<QHostAddress>&); void resolvedHost(MdnsPendingComputer*,QVector<QHostAddress>&);
private: private:
void cleanup() void resolve()
{ {
// Delete our resolver, so we're guaranteed that nothing is referencing m_Server. // Delete our resolver, so we're guaranteed that nothing is referencing m_Server.
delete m_Resolver; delete m_Resolver;
@@ -107,12 +101,6 @@ private:
// Now delete our strong reference that we held on behalf of m_Resolver. // Now delete our strong reference that we held on behalf of m_Resolver.
// The server may be destroyed after we make this call. // The server may be destroyed after we make this call.
m_Server.reset(); m_Server.reset();
}
void resolve()
{
// Clean up any existing resolver object and server references
cleanup();
// Re-acquire a strong reference if the server still exists. // Re-acquire a strong reference if the server still exists.
m_Server = m_ServerWeak.toStrongRef(); m_Server = m_ServerWeak.toStrongRef();
@@ -132,7 +120,6 @@ private:
QSharedPointer<QMdnsEngine::Server> m_Server; QSharedPointer<QMdnsEngine::Server> m_Server;
QMdnsEngine::Resolver* m_Resolver; QMdnsEngine::Resolver* m_Resolver;
QVector<QHostAddress> m_Addresses; QVector<QHostAddress> m_Addresses;
int m_Retries = 10;
}; };
class ComputerPollingEntry class ComputerPollingEntry
@@ -218,7 +205,7 @@ class ComputerManager : public QObject
friend class DelayedFlushThread; friend class DelayedFlushThread;
public: public:
explicit ComputerManager(StreamingPreferences* prefs); explicit ComputerManager(QObject *parent = nullptr);
virtual ~ComputerManager(); virtual ~ComputerManager();
@@ -270,7 +257,6 @@ private:
void startPollingComputer(NvComputer* computer); void startPollingComputer(NvComputer* computer);
StreamingPreferences* m_Prefs;
int m_PollingRef; int m_PollingRef;
QReadWriteLock m_Lock; QReadWriteLock m_Lock;
QMap<QString, NvComputer*> m_KnownHosts; QMap<QString, NvComputer*> m_KnownHosts;
-7
View File
@@ -6,13 +6,6 @@ ComputerSeeker::ComputerSeeker(ComputerManager *manager, QString computerName, Q
: QObject(parent), m_ComputerManager(manager), m_ComputerName(computerName), : QObject(parent), m_ComputerManager(manager), m_ComputerName(computerName),
m_TimeoutTimer(new QTimer(this)) m_TimeoutTimer(new QTimer(this))
{ {
// If we know this computer, send a WOL packet to wake it up in case it is asleep.
for (NvComputer * computer: m_ComputerManager->getComputers()) {
if (this->matchComputer(computer)) {
computer->wake();
}
}
m_TimeoutTimer->setSingleShot(true); m_TimeoutTimer->setSingleShot(true);
connect(m_TimeoutTimer, &QTimer::timeout, connect(m_TimeoutTimer, &QTimer::timeout,
this, &ComputerSeeker::onTimeout); this, &ComputerSeeker::onTimeout);
+2 -16
View File
@@ -373,7 +373,6 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
s.connectToHost(copyOfActiveAddress.address(), copyOfActiveAddress.port()); s.connectToHost(copyOfActiveAddress.address(), copyOfActiveAddress.port());
if (s.waitForConnected(3000)) { if (s.waitForConnected(3000)) {
Q_ASSERT(!s.localAddress().isNull()); Q_ASSERT(!s.localAddress().isNull());
Q_ASSERT(!s.peerAddress().isNull());
for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) { for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) {
// Ensure the interface is up // Ensure the interface is up
@@ -407,19 +406,6 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
return ReachabilityType::RI_VPN; return ReachabilityType::RI_VPN;
} }
#ifdef Q_OS_WINDOWS
if (nic.name().startsWith("iftype53_") || nic.name().startsWith("iftype131_")) {
// Match by NDIS interface type. These values are Microsoft's recommended values for VPN connections:
// https://learn.microsoft.com/en-US/troubleshoot/windows-client/networking/windows-connection-manager-disconnects-wlan#more-information
//
// The following VPNs use IF_TYPE_PROP_VIRTUAL under Windows:
// - WireguardNT VPNs
// - All WinTun-based VPNs (such as Slack Nebula)
// - OpenVPN with tap-windows6
return ReachabilityType::RI_VPN;
}
#endif
if (nic.hardwareAddress().startsWith("00:FF", Qt::CaseInsensitive)) { if (nic.hardwareAddress().startsWith("00:FF", Qt::CaseInsensitive)) {
// OpenVPN TAP interfaces have a MAC address starting with 00:FF on Windows // OpenVPN TAP interfaces have a MAC address starting with 00:FF on Windows
return ReachabilityType::RI_VPN; return ReachabilityType::RI_VPN;
@@ -435,9 +421,9 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
return ReachabilityType::RI_VPN; return ReachabilityType::RI_VPN;
} }
// Didn't meet any of our VPN heuristics. Let's see if the peer address is on-link. // Didn't meet any of our VPN heuristics. Let's see if it's on-link.
Q_ASSERT(addr.prefixLength() >= 0); Q_ASSERT(addr.prefixLength() >= 0);
if (addr.prefixLength() >= 0 && s.localAddress().isInSubnet(s.peerAddress(), addr.prefixLength())) { if (addr.prefixLength() >= 0 && s.localAddress().isInSubnet(addr.ip(), addr.prefixLength())) {
return ReachabilityType::RI_LAN; return ReachabilityType::RI_LAN;
} }
+1 -2
View File
@@ -223,8 +223,7 @@ NvHTTP::startApp(QString verb,
"&surroundAudioInfo="+QString::number(SURROUNDAUDIOINFO_FROM_AUDIO_CONFIGURATION(streamConfig->audioConfiguration))+ "&surroundAudioInfo="+QString::number(SURROUNDAUDIOINFO_FROM_AUDIO_CONFIGURATION(streamConfig->audioConfiguration))+
"&remoteControllersBitmap="+QString::number(gamepadMask)+ "&remoteControllersBitmap="+QString::number(gamepadMask)+
"&gcmap="+QString::number(gamepadMask)+ "&gcmap="+QString::number(gamepadMask)+
"&gcpersist="+QString::number(persistGameControllersOnDisconnect ? 1 : 0)+ "&gcpersist="+QString::number(persistGameControllersOnDisconnect ? 1 : 0),
LiGetLaunchUrlQueryParameters(),
LAUNCH_TIMEOUT_MS); LAUNCH_TIMEOUT_MS);
qInfo() << "Launch response:" << response; qInfo() << "Launch response:" << response;
+2 -2
View File
@@ -298,8 +298,8 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
} }
QByteArray pairingSecret = NvHTTP::getXmlStringFromHex(respXml, "pairingsecret"); QByteArray pairingSecret = NvHTTP::getXmlStringFromHex(respXml, "pairingsecret");
QByteArray serverSecret = pairingSecret.left(16); QByteArray serverSecret = QByteArray(pairingSecret.data(), 16);
QByteArray serverSignature = pairingSecret.mid(16); QByteArray serverSignature = QByteArray(&pairingSecret.data()[16], 256);
if (!verifySignature(serverSecret, if (!verifySignature(serverSecret,
serverSignature, serverSignature,
+1 -10
View File
@@ -74,7 +74,6 @@ SystemProperties::SystemProperties()
Q_ASSERT(!monitorRefreshRates.isEmpty()); Q_ASSERT(!monitorRefreshRates.isEmpty());
Q_ASSERT(!monitorNativeResolutions.isEmpty()); Q_ASSERT(!monitorNativeResolutions.isEmpty());
Q_ASSERT(!monitorSafeAreaResolutions.isEmpty());
} }
QRect SystemProperties::getNativeResolution(int displayIndex) QRect SystemProperties::getNativeResolution(int displayIndex)
@@ -83,12 +82,6 @@ QRect SystemProperties::getNativeResolution(int displayIndex)
return monitorNativeResolutions.value(displayIndex); return monitorNativeResolutions.value(displayIndex);
} }
QRect SystemProperties::getSafeAreaResolution(int displayIndex)
{
// Returns default constructed QRect if out of bounds
return monitorSafeAreaResolutions.value(displayIndex);
}
int SystemProperties::getRefreshRate(int displayIndex) int SystemProperties::getRefreshRate(int displayIndex)
{ {
// Returns 0 if out of bounds // Returns 0 if out of bounds
@@ -206,12 +199,10 @@ void SystemProperties::refreshDisplaysInternal()
SDL_DisplayMode bestMode; SDL_DisplayMode bestMode;
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) { for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
SDL_DisplayMode desktopMode; SDL_DisplayMode desktopMode;
SDL_Rect safeArea;
if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode, &safeArea)) { if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode)) {
if (desktopMode.w <= 8192 && desktopMode.h <= 8192) { if (desktopMode.w <= 8192 && desktopMode.h <= 8192) {
monitorNativeResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h)); monitorNativeResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
monitorSafeAreaResolutions.insert(displayIndex, QRect(0, 0, safeArea.w, safeArea.h));
} }
else { else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
-2
View File
@@ -30,7 +30,6 @@ public:
Q_INVOKABLE void refreshDisplays(); Q_INVOKABLE void refreshDisplays();
Q_INVOKABLE QRect getNativeResolution(int displayIndex); Q_INVOKABLE QRect getNativeResolution(int displayIndex);
Q_INVOKABLE QRect getSafeAreaResolution(int displayIndex);
Q_INVOKABLE int getRefreshRate(int displayIndex); Q_INVOKABLE int getRefreshRate(int displayIndex);
signals: signals:
@@ -53,7 +52,6 @@ private:
QString unmappedGamepads; QString unmappedGamepads;
QSize maximumResolution; QSize maximumResolution;
QList<QRect> monitorNativeResolutions; QList<QRect> monitorNativeResolutions;
QList<QRect> monitorSafeAreaResolutions;
QList<int> monitorRefreshRates; QList<int> monitorRefreshRates;
QString versionString; QString versionString;
bool supportsHdr; bool supportsHdr;
-9
View File
@@ -316,7 +316,6 @@ StreamCommandLineParser::StreamCommandLineParser()
{"auto", StreamingPreferences::VCC_AUTO}, {"auto", StreamingPreferences::VCC_AUTO},
{"H.264", StreamingPreferences::VCC_FORCE_H264}, {"H.264", StreamingPreferences::VCC_FORCE_H264},
{"HEVC", StreamingPreferences::VCC_FORCE_HEVC}, {"HEVC", StreamingPreferences::VCC_FORCE_HEVC},
{"AV1", StreamingPreferences::VCC_FORCE_AV1},
}; };
m_VideoDecoderMap = { m_VideoDecoderMap = {
{"auto", StreamingPreferences::VDS_AUTO}, {"auto", StreamingPreferences::VDS_AUTO},
@@ -372,8 +371,6 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
parser.addToggleOption("reverse-scroll-direction", "inverted scroll direction"); parser.addToggleOption("reverse-scroll-direction", "inverted scroll direction");
parser.addToggleOption("swap-gamepad-buttons", "swap A/B and X/Y gamepad buttons (Nintendo-style)"); parser.addToggleOption("swap-gamepad-buttons", "swap A/B and X/Y gamepad buttons (Nintendo-style)");
parser.addToggleOption("keep-awake", "prevent display sleep while streaming"); parser.addToggleOption("keep-awake", "prevent display sleep while streaming");
parser.addToggleOption("performance-overlay", "show performance overlay");
parser.addToggleOption("hdr", "HDR streaming");
parser.addChoiceOption("capture-system-keys", "capture system key combos", m_CaptureSysKeysModeMap.keys()); parser.addChoiceOption("capture-system-keys", "capture system key combos", m_CaptureSysKeysModeMap.keys());
parser.addChoiceOption("video-codec", "video codec", m_VideoCodecMap.keys()); parser.addChoiceOption("video-codec", "video codec", m_VideoCodecMap.keys());
parser.addChoiceOption("video-decoder", "video decoder", m_VideoDecoderMap.keys()); parser.addChoiceOption("video-decoder", "video decoder", m_VideoDecoderMap.keys());
@@ -488,12 +485,6 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
// Resolve --keep-awake and --no-keep-awake options // Resolve --keep-awake and --no-keep-awake options
preferences->keepAwake = parser.getToggleOptionValue("keep-awake", preferences->keepAwake); preferences->keepAwake = parser.getToggleOptionValue("keep-awake", preferences->keepAwake);
// Resolve --performance-overlay and --no-performance-overlay options
preferences->showPerformanceOverlay = parser.getToggleOptionValue("performance-overlay", preferences->showPerformanceOverlay);
// Resolve --hdr and --no-hdr options
preferences->enableHdr = parser.getToggleOptionValue("hdr", preferences->enableHdr);
// Resolve --capture-system-keys option // Resolve --capture-system-keys option
if (parser.isSet("capture-system-keys")) { if (parser.isSet("capture-system-keys")) {
preferences->captureSysKeysMode = mapValue(m_CaptureSysKeysModeMap, parser.getChoiceOptionValue("capture-system-keys")); preferences->captureSysKeysMode = mapValue(m_CaptureSysKeysModeMap, parser.getChoiceOptionValue("capture-system-keys"));
@@ -1,40 +0,0 @@
From 78261eb6e2fe728fa5d3843b5b8962b1ca559b7e Mon Sep 17 00:00:00 2001
From: David Rosca <nowrep@gmail.com>
Date: Sat, 16 Dec 2023 14:52:01 +0100
Subject: [PATCH] Vulkan: Don't try to reuse old swapchain
---
src/vulkan/swapchain.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/src/vulkan/swapchain.c b/src/vulkan/swapchain.c
index 0646e57c..c0b07ce4 100644
--- a/src/vulkan/swapchain.c
+++ b/src/vulkan/swapchain.c
@@ -612,13 +612,21 @@ static bool vk_sw_recreate(pl_swapchain sw, int w, int h)
}
#endif
+ {
+ pl_gpu_flush(gpu);
+ vk_wait_idle(vk);
+ for (int i = 0; i < vk->pool_graphics->num_queues; i++)
+ vk->QueueWaitIdle(vk->pool_graphics->queues[i]);
+ vk->DestroySwapchainKHR(vk->dev, p->swapchain, PL_VK_ALLOC);
+ }
+
// Calling `vkCreateSwapchainKHR` puts sinfo.oldSwapchain into a retired
// state whether the call succeeds or not, so we always need to garbage
// collect it afterwards - asynchronously as it may still be in use
- sinfo.oldSwapchain = p->swapchain;
+ /* sinfo.oldSwapchain = p->swapchain; */
p->swapchain = VK_NULL_HANDLE;
VkResult res = vk->CreateSwapchainKHR(vk->dev, &sinfo, PL_VK_ALLOC, &p->swapchain);
- vk_dev_callback(vk, VK_CB_FUNC(destroy_swapchain), vk, vk_wrap_handle(sinfo.oldSwapchain));
+ /* vk_dev_callback(vk, VK_CB_FUNC(destroy_swapchain), vk, vk_wrap_handle(sinfo.oldSwapchain)); */
PL_VK_ASSERT(res, "vk->CreateSwapchainKHR(...)");
// Get the new swapchain images
--
2.43.0
@@ -34,44 +34,6 @@
</screenshots> </screenshots>
<releases> <releases>
<release version="6.0.0" date="2024-06-06">
<description>
<p>New features:</p>
<ul>
<li>HDR is now supported on Steam Deck and other HDR-capable Linux systems using a new Vulkan-based renderer</li>
<li>Vulkan Video decoding of H.264, HEVC, and AV1 is now supported using the new Vulkan renderer</li>
<li>Full E2E stream encryption is now supported when streaming from Sunshine v0.22 or later</li>
<li>Initiating a stream via the command-line will now automatically wake the target machine if it is sleeping</li>
<li>Error codes are now displayed in the connection termination dialog</li>
<li>Detailed PC state information is now available via the new "View Details" context menu option (similar to the Android client)</li>
<li>The performance overlay can now be configured to be shown by default when starting a stream</li>
</ul>
<p>Behavior Changes:</p>
<ul>
<li>The selected bitrate is no longer silently adjusted by Moonlight based upon the video codec or HDR mode</li>
<li>The selected mouse mode when starting a stream no longer influences the acceleration curve when the mouse mode is toggled using the Ctrl+Alt+Shift+M key combo while streaming</li>
<li>Hardcoded fallback paths for VAAPI and VDPAU drivers are no longer used</li>
<li>Per-session keys (rikey and rikeyid values) are now automatically stripped from logs</li>
<li>Log messages will now be logged to stderr rather than stdout</li>
<li>EGL is now always used instead of GLX for loading OpenGL in X11 environments on non-x86 platforms</li>
</ul>
<p>Bugfixes:</p>
<ul>
<li>Improved reliability of streaming during momentary drops of WiFi connections</li>
<li>Improved performance with high polling rate mice</li>
<li>Improved input latency when a gamepad is connected</li>
<li>Added missing command-line options to control HDR and AV1 codec usage</li>
<li>Fixed instant connection termination error when streaming over the Internet behind certain Carrier-Grade NAT devices</li>
<li>Fixed incorrect MTU detection when streaming over the Internet without a VPN</li>
<li>Fixed recovery of audio when no audio output device is present when the stream starts</li>
<li>Fixed merging of gamepad state when multi-controller mode is disabled</li>
<li>Fixed indefinitely resending mDNS queries when a host could not be resolved</li>
<li>Fixed inability to pair when the host is not using a RSA-2048 key</li>
<li>Updated included gamepad mappings</li>
<li>Updated community-contributed translations from Weblate</li>
</ul>
</description>
</release>
<release version="5.0.1" date="2023-11-02"> <release version="5.0.1" date="2023-11-02">
<description> <description>
<p>Bugfixes:</p> <p>Bugfixes:</p>
-4
View File
@@ -5,10 +5,6 @@
# pairing data will be lost each reboot. # pairing data will be lost each reboot.
HOME=/usr/local/moonlight HOME=/usr/local/moonlight
# Enable Steam Controller detection in SDL
# https://github.com/moonlight-stream/moonlight-qt/issues/697
export SDL_ENABLE_STEAM_CONTROLLERS=1
# Renice PE_Single_CPU which seems to host A/V stuff # Renice PE_Single_CPU which seems to host A/V stuff
renice -10 -p $(pidof PE_Single_CPU) renice -10 -p $(pidof PE_Single_CPU)
+2 -2
View File
@@ -256,7 +256,7 @@ CenteredGridView {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.RightButton; acceptedButtons: Qt.RightButton;
onClicked: { onClicked: {
parent.pressAndHold() parent.onPressAndHold()
} }
} }
@@ -350,7 +350,7 @@ CenteredGridView {
function quitApp() { function quitApp() {
var component = Qt.createComponent("QuitSegue.qml") var component = Qt.createComponent("QuitSegue.qml")
var params = {"appName": appName, "quitRunningAppFn": () => appModel.quitRunningApp()} var params = {"appName": appName, "quitRunningAppFn": appModel.quitRunningApp}
if (segueToStream) { if (segueToStream) {
// Store the session and app name if we're going to stream after // Store the session and app name if we're going to stream after
// successfully quitting the old app. // successfully quitting the old app.
+1 -1
View File
@@ -85,7 +85,7 @@ Item {
function quitApp() { function quitApp() {
var component = Qt.createComponent("QuitSegue.qml") var component = Qt.createComponent("QuitSegue.qml")
var params = {"appName": appName, "quitRunningAppFn": () => launcher.quitRunningApp()} var params = {"appName": appName, "quitRunningAppFn": launcher.quitRunningApp}
stackView.push(component.createObject(stackView, params)) stackView.push(component.createObject(stackView, params))
} }
+1 -18
View File
@@ -6,7 +6,6 @@ import ComputerModel 1.0
import ComputerManager 1.0 import ComputerManager 1.0
import StreamingPreferences 1.0 import StreamingPreferences 1.0
import SystemProperties 1.0
import SdlGamepadKeyNavigation 1.0 import SdlGamepadKeyNavigation 1.0
CenteredGridView { CenteredGridView {
@@ -216,14 +215,6 @@ CenteredGridView {
deletePcDialog.open() deletePcDialog.open()
} }
} }
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("View Details")
onTriggered: {
showPcDetailsDialog.pcDetails = model.details
showPcDetailsDialog.open()
}
}
} }
} }
@@ -271,7 +262,7 @@ CenteredGridView {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.RightButton; acceptedButtons: Qt.RightButton;
onClicked: { onClicked: {
parent.pressAndHold() parent.onPressAndHold()
} }
} }
@@ -404,13 +395,5 @@ CenteredGridView {
} }
} }
NavigableMessageDialog {
id: showPcDetailsDialog
property string pcDetails : "";
text: showPcDetailsDialog.pcDetails
imageSrc: "qrc:/res/baseline-help_outline-24px.svg"
standardButtons: Dialog.Ok
}
ScrollBar.vertical: ScrollBar {} ScrollBar.vertical: ScrollBar {}
} }
+43 -51
View File
@@ -133,18 +133,33 @@ Flickable {
AutoResizingComboBox { AutoResizingComboBox {
property int lastIndexValue property int lastIndexValue
function addDetectedResolution(friendlyNamePrefix, rect) { // ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
// Refresh display data before using it to build the list
SystemProperties.refreshDisplays()
// Add native resolutions for all attached displays
var done = false
for (var displayIndex = 0; !done; displayIndex++) {
var screenRect = SystemProperties.getNativeResolution(displayIndex);
if (screenRect.width === 0) {
// Exceeded max count of displays
done = true
break
}
var indexToAdd = 0 var indexToAdd = 0
for (var j = 0; j < resolutionComboBox.count; j++) { for (var j = 0; j < resolutionComboBox.count; j++) {
var existing_width = parseInt(resolutionListModel.get(j).video_width); var existing_width = parseInt(resolutionListModel.get(j).video_width);
var existing_height = parseInt(resolutionListModel.get(j).video_height); var existing_height = parseInt(resolutionListModel.get(j).video_height);
if (rect.width === existing_width && rect.height === existing_height) { if (screenRect.width === existing_width && screenRect.height === existing_height) {
// Duplicate entry, skip // Duplicate entry, skip
indexToAdd = -1 indexToAdd = -1
break break
} }
else if (rect.width * rect.height > existing_width * existing_height) { else if (screenRect.width * screenRect.height > existing_width * existing_height) {
// Candidate entrypoint after this entry // Candidate entrypoint after this entry
indexToAdd = j + 1 indexToAdd = j + 1
} }
@@ -154,35 +169,14 @@ Flickable {
if (indexToAdd >= 0) { if (indexToAdd >= 0) {
resolutionListModel.insert(indexToAdd, resolutionListModel.insert(indexToAdd,
{ {
"text": friendlyNamePrefix+" ("+rect.width+"x"+rect.height+")", "text": "Native ("+screenRect.width+"x"+screenRect.height+")",
"video_width": ""+rect.width, "video_width": ""+screenRect.width,
"video_height": ""+rect.height, "video_height": ""+screenRect.height,
"is_custom": false "is_custom": false
}) })
} }
} }
// ignore setting the index at first, and actually set it when the component is loaded
Component.onCompleted: {
// Refresh display data before using it to build the list
SystemProperties.refreshDisplays()
// Add native and safe area resolutions for all attached displays
var done = false
for (var displayIndex = 0; !done; displayIndex++) {
var screenRect = SystemProperties.getNativeResolution(displayIndex);
var safeAreaRect = SystemProperties.getSafeAreaResolution(displayIndex);
if (screenRect.width === 0) {
// Exceeded max count of displays
done = true
break
}
addDetectedResolution(qsTr("Native"), screenRect)
addDetectedResolution(qsTr("Native (Excluding Notch)"), safeAreaRect)
}
// Prune resolutions that are over the decoder's maximum // Prune resolutions that are over the decoder's maximum
var max_pixels = SystemProperties.maximumResolution.width * SystemProperties.maximumResolution.height; var max_pixels = SystemProperties.maximumResolution.width * SystemProperties.maximumResolution.height;
if (max_pixels > 0) { if (max_pixels > 0) {
@@ -216,7 +210,7 @@ Flickable {
if (!index_set) { if (!index_set) {
// We did not find a match. This must be a custom resolution. // We did not find a match. This must be a custom resolution.
resolutionListModel.append({ resolutionListModel.append({
"text": qsTr("Custom")+" ("+StreamingPreferences.width+"x"+StreamingPreferences.height+")", "text": "Custom ("+StreamingPreferences.width+"x"+StreamingPreferences.height+")",
"video_width": ""+StreamingPreferences.width, "video_width": ""+StreamingPreferences.width,
"video_height": ""+StreamingPreferences.height, "video_height": ""+StreamingPreferences.height,
"is_custom": true "is_custom": true
@@ -225,7 +219,7 @@ Flickable {
} }
else { else {
resolutionListModel.append({ resolutionListModel.append({
"text": qsTr("Custom"), "text": "Custom",
"video_width": "", "video_width": "",
"video_height": "", "video_height": "",
"is_custom": true "is_custom": true
@@ -695,7 +689,7 @@ Flickable {
Component.onCompleted: { Component.onCompleted: {
// Refresh the text after translations change // Refresh the text after translations change
languageChanged.connect(valueChanged) languageChanged.connect(onValueChanged)
} }
} }
@@ -1058,10 +1052,6 @@ Flickable {
text: "עִבְרִית" // Hebrew text: "עִבְרִית" // Hebrew
val: StreamingPreferences.LANG_HE val: StreamingPreferences.LANG_HE
} */ } */
/* ListElement {
text: "کرمانجیی خواروو" // Central Kurdish
val: StreamingPreferences.LANG_CKB
} */
} }
// ::onActivated must be used, as it only listens for when the index is changed by a human // ::onActivated must be used, as it only listens for when the index is changed by a human
onActivated : { onActivated : {
@@ -1359,7 +1349,15 @@ Flickable {
font.pointSize: 12 font.pointSize: 12
checked: StreamingPreferences.swapFaceButtons checked: StreamingPreferences.swapFaceButtons
onCheckedChanged: { onCheckedChanged: {
// Check if the value changed (this is called on init too)
if (StreamingPreferences.swapFaceButtons !== checked) {
StreamingPreferences.swapFaceButtons = checked StreamingPreferences.swapFaceButtons = checked
// Save and restart SdlGamepadKeyNavigation so it can pull the new value
StreamingPreferences.save()
SdlGamepadKeyNavigation.disable()
SdlGamepadKeyNavigation.enable()
}
} }
ToolTip.delay: 1000 ToolTip.delay: 1000
@@ -1623,6 +1621,10 @@ Flickable {
if (StreamingPreferences.enableMdns != checked) { if (StreamingPreferences.enableMdns != checked) {
StreamingPreferences.enableMdns = checked StreamingPreferences.enableMdns = checked
// We must save the updated preference to ensure
// ComputerManager can observe the change internally.
StreamingPreferences.save()
// Restart polling so the mDNS change takes effect // Restart polling so the mDNS change takes effect
if (window.pollingActive) { if (window.pollingActive) {
ComputerManager.stopPollingAsync() ComputerManager.stopPollingAsync()
@@ -1639,26 +1641,16 @@ Flickable {
font.pointSize: 12 font.pointSize: 12
checked: StreamingPreferences.detectNetworkBlocking checked: StreamingPreferences.detectNetworkBlocking
onCheckedChanged: { onCheckedChanged: {
// This is called on init, so only do the work if we've
// actually changed the value.
if (StreamingPreferences.detectNetworkBlocking != checked) {
StreamingPreferences.detectNetworkBlocking = checked StreamingPreferences.detectNetworkBlocking = checked
}
}
CheckBox { // We must save the updated preference to ensure
id: showPerformanceOverlay // ComputerManager can observe the change internally.
width: parent.width StreamingPreferences.save()
text: qsTr("Show performance stats while streaming") }
font.pointSize: 12
checked: StreamingPreferences.showPerformanceOverlay
onCheckedChanged: {
StreamingPreferences.showPerformanceOverlay = checked
} }
ToolTip.delay: 1000
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: qsTr("Display real-time stream performance information while streaming.") + "\n\n" +
qsTr("You can toggle it at any time while streaming using Ctrl+Alt+Shift+S or Select+L1+R1+X.") + "\n\n" +
qsTr("The performance overlay is not supported on Steam Link or Raspberry Pi.")
} }
} }
} }
+1 -1
View File
@@ -176,7 +176,7 @@ Item {
gc() gc()
// Run the streaming session to completion // Run the streaming session to completion
session.exec(Window.window) session.exec(Screen.virtualX, Screen.virtualY)
} }
sourceComponent: Item {} sourceComponent: Item {}
-41
View File
@@ -42,46 +42,6 @@ QVariant ComputerModel::data(const QModelIndex& index, int role) const
return computer->state == NvComputer::CS_UNKNOWN; return computer->state == NvComputer::CS_UNKNOWN;
case ServerSupportedRole: case ServerSupportedRole:
return computer->isSupportedServerVersion; return computer->isSupportedServerVersion;
case DetailsRole: {
QString state, pairState;
switch (computer->state) {
case NvComputer::CS_ONLINE:
state = tr("Online");
break;
case NvComputer::CS_OFFLINE:
state = tr("Offline");
break;
default:
state = tr("Unknown");
break;
}
switch (computer->pairState) {
case NvComputer::PS_PAIRED:
pairState = tr("Paired");
break;
case NvComputer::PS_NOT_PAIRED:
pairState = tr("Unpaired");
break;
default:
pairState = tr("Unknown");
break;
}
return tr("Name: %1").arg(computer->name) + '\n' +
tr("Status: %1").arg(state) + '\n' +
tr("Active Address: %1").arg(computer->activeAddress.toString()) + '\n' +
tr("UUID: %1").arg(computer->uuid) + '\n' +
tr("Local Address: %1").arg(computer->localAddress.toString()) + '\n' +
tr("Remote Address: %1").arg(computer->remoteAddress.toString()) + '\n' +
tr("IPv6 Address: %1").arg(computer->ipv6Address.toString()) + '\n' +
tr("Manual Address: %1").arg(computer->manualAddress.toString()) + '\n' +
tr("MAC Address: %1").arg(computer->macAddress.isEmpty() ? tr("Unknown") : QString(computer->macAddress.toHex(':'))) + '\n' +
tr("Pair State: %1").arg(pairState) + '\n' +
tr("Running Game ID: %1").arg(computer->state == NvComputer::CS_ONLINE ? QString::number(computer->currentGameId) : tr("Unknown")) + '\n' +
tr("HTTPS Port: %1").arg(computer->state == NvComputer::CS_ONLINE ? QString::number(computer->activeHttpsPort) : tr("Unknown"));
}
default: default:
return QVariant(); return QVariant();
} }
@@ -109,7 +69,6 @@ QHash<int, QByteArray> ComputerModel::roleNames() const
names[WakeableRole] = "wakeable"; names[WakeableRole] = "wakeable";
names[StatusUnknownRole] = "statusUnknown"; names[StatusUnknownRole] = "statusUnknown";
names[ServerSupportedRole] = "serverSupported"; names[ServerSupportedRole] = "serverSupported";
names[DetailsRole] = "details";
return names; return names;
} }
+1 -2
View File
@@ -15,8 +15,7 @@ class ComputerModel : public QAbstractListModel
BusyRole, BusyRole,
WakeableRole, WakeableRole,
StatusUnknownRole, StatusUnknownRole,
ServerSupportedRole, ServerSupportedRole
DetailsRole
}; };
public: public:
+44 -31
View File
@@ -19,48 +19,26 @@ ApplicationWindow {
property bool clearOnBack: false property bool clearOnBack: false
id: window id: window
visible: true
width: 1280 width: 1280
height: 600 height: 600
Component.onCompleted: {
// Override the background color to Material 2 colors for Qt 6.5+ // Override the background color to Material 2 colors for Qt 6.5+
// in order to improve contrast between GFE's placeholder box art // in order to improve contrast between GFE's placeholder box art
// and the background of the app grid. // and the background of the app grid.
Component.onCompleted: {
if (SystemProperties.usesMaterial3Theme) { if (SystemProperties.usesMaterial3Theme) {
Material.background = "#303030" Material.background = "#303030"
} }
}
// Show the window according to the user's preferences visibility: {
if (SystemProperties.hasDesktopEnvironment) { if (SystemProperties.hasDesktopEnvironment) {
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) { if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_WINDOWED) return "Windowed"
window.showMaximized() else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) return "Maximized"
} else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_FULLSCREEN) return "FullScreen"
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_FULLSCREEN) {
window.showFullScreen()
}
else {
window.show()
}
} else { } else {
window.showFullScreen() return "FullScreen"
}
// Display any modal dialogs for configuration warnings
if (SystemProperties.isWow64) {
wow64Dialog.open()
}
else if (!SystemProperties.hasHardwareAcceleration) {
if (SystemProperties.isRunningXWayland) {
xWaylandDialog.open()
}
else {
noHwDecoderDialog.open()
}
}
if (SystemProperties.unmappedGamepads) {
unmappedGamepadDialog.unmappedGamepads = SystemProperties.unmappedGamepads
unmappedGamepadDialog.open()
} }
} }
@@ -178,6 +156,41 @@ ApplicationWindow {
} }
} }
property bool initialized: false
// BUG: Using onAfterSynchronizing: here causes very strange
// failures on Linux. Many shaders fail to compile and we
// eventually segfault deep inside the Qt OpenGL code.
onAfterRendering: {
// We use this callback to trigger dialog display because
// it only happens once the window is fully constructed.
// Doing it earlier can lead to the dialog appearing behind
// the window or otherwise without input focus.
if (!initialized) {
// Set initialized before calling anything else, because
// pumping the event loop can cause us to get another
// onAfterRendering call and potentially reenter this code.
initialized = true;
if (SystemProperties.isWow64) {
wow64Dialog.open()
}
else if (!SystemProperties.hasHardwareAcceleration) {
if (SystemProperties.isRunningXWayland) {
xWaylandDialog.open()
}
else {
noHwDecoderDialog.open()
}
}
if (SystemProperties.unmappedGamepads) {
unmappedGamepadDialog.unmappedGamepads = SystemProperties.unmappedGamepads
unmappedGamepadDialog.open()
}
}
}
// Workaround for lack of instanceof in Qt 5.9. // Workaround for lack of instanceof in Qt 5.9.
// //
// Based on https://stackoverflow.com/questions/13923794/how-to-do-a-is-a-typeof-or-instanceof-in-qml // Based on https://stackoverflow.com/questions/13923794/how-to-do-a-is-a-typeof-or-instanceof-in-qml
@@ -270,7 +283,7 @@ ApplicationWindow {
visible: SystemProperties.hasBrowser && visible: SystemProperties.hasBrowser &&
qmltypeof(stackView.currentItem, "SettingsView") qmltypeof(stackView.currentItem, "SettingsView")
iconSource: "qrc:/res/discord.svg" iconSource: "qrc:/res/Discord-Logo-White.svg"
ToolTip.delay: 1000 ToolTip.delay: 1000
ToolTip.timeout: 3000 ToolTip.timeout: 3000
+5 -4
View File
@@ -4,13 +4,13 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QWindow> #include <QWindow>
#include "settings/streamingpreferences.h"
#include "settings/mappingmanager.h" #include "settings/mappingmanager.h"
#define AXIS_NAVIGATION_REPEAT_DELAY 150 #define AXIS_NAVIGATION_REPEAT_DELAY 150
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation(StreamingPreferences* prefs) SdlGamepadKeyNavigation::SdlGamepadKeyNavigation()
: m_Prefs(prefs), : m_Enabled(false),
m_Enabled(false),
m_UiNavMode(false), m_UiNavMode(false),
m_FirstPoll(false), m_FirstPoll(false),
m_LastAxisNavigationEventTime(0) m_LastAxisNavigationEventTime(0)
@@ -93,6 +93,7 @@ void SdlGamepadKeyNavigation::disable()
void SdlGamepadKeyNavigation::onPollingTimerFired() void SdlGamepadKeyNavigation::onPollingTimerFired()
{ {
SDL_Event event; SDL_Event event;
StreamingPreferences prefs;
// Discard any pending button events on the first poll to avoid picking up // Discard any pending button events on the first poll to avoid picking up
// stale input data from the stream session (like the quit combo). // stale input data from the stream session (like the quit combo).
@@ -120,7 +121,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
QEvent::Type::KeyPress : QEvent::Type::KeyRelease; QEvent::Type::KeyPress : QEvent::Type::KeyRelease;
// Swap face buttons if needed // Swap face buttons if needed
if (m_Prefs->swapFaceButtons) { if (prefs.swapFaceButtons) {
switch (event.cbutton.button) { switch (event.cbutton.button) {
case SDL_CONTROLLER_BUTTON_A: case SDL_CONTROLLER_BUTTON_A:
event.cbutton.button = SDL_CONTROLLER_BUTTON_B; event.cbutton.button = SDL_CONTROLLER_BUTTON_B;
+1 -4
View File
@@ -5,14 +5,12 @@
#include <SDL.h> #include <SDL.h>
#include "settings/streamingpreferences.h"
class SdlGamepadKeyNavigation : public QObject class SdlGamepadKeyNavigation : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
SdlGamepadKeyNavigation(StreamingPreferences* prefs); SdlGamepadKeyNavigation();
~SdlGamepadKeyNavigation(); ~SdlGamepadKeyNavigation();
@@ -31,7 +29,6 @@ private slots:
void onPollingTimerFired(); void onPollingTimerFired();
private: private:
StreamingPreferences* m_Prefs;
QTimer* m_PollingTimer; QTimer* m_PollingTimer;
QList<SDL_GameController*> m_Gamepads; QList<SDL_GameController*> m_Gamepads;
bool m_Enabled; bool m_Enabled;
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+244 -378
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+188 -322
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+215 -349
View File
File diff suppressed because it is too large. Load diff
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+182 -316
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+185 -319
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+181 -315
View File
File diff suppressed because it is too large. Load diff
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+182 -316
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+268 -402
View File
File diff suppressed because it is too large. Load diff
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+192 -326
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+194 -328
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+181 -315
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+181 -315
View File
File diff suppressed because it is too large. Load diff
+33 -97
View File
@@ -10,8 +10,7 @@
#include <QFont> #include <QFont>
#include <QCursor> #include <QCursor>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QTemporaryFile> #include <QFile>
#include <QRegularExpression>
// Don't let SDL hook our main function, since Qt is already // Don't let SDL hook our main function, since Qt is already
// doing the same thing. This needs to be before any headers // doing the same thing. This needs to be before any headers
@@ -45,24 +44,26 @@
#include "settings/streamingpreferences.h" #include "settings/streamingpreferences.h"
#include "gui/sdlgamepadkeynavigation.h" #include "gui/sdlgamepadkeynavigation.h"
#if defined(Q_OS_WIN32) #if !defined(QT_DEBUG) && defined(Q_OS_WIN32)
#define IS_UNSPECIFIED_HANDLE(x) ((x) == INVALID_HANDLE_VALUE || (x) == NULL) // Log to file for release Windows builds
#define USE_CUSTOM_LOGGER
// Log to file or console dynamically for Windows builds
#define LOG_TO_FILE #define LOG_TO_FILE
#elif defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
// Use stdout logger on all Linux/BSD builds
#define USE_CUSTOM_LOGGER
#elif !defined(QT_DEBUG) && defined(Q_OS_DARWIN) #elif !defined(QT_DEBUG) && defined(Q_OS_DARWIN)
// Log to file for release Mac builds // Log to file for release Mac builds
#define USE_CUSTOM_LOGGER
#define LOG_TO_FILE #define LOG_TO_FILE
#else #else
// Log to console for debug Mac builds // For debug Windows and Mac builds, use default logger
#endif #endif
#ifdef USE_CUSTOM_LOGGER
static QElapsedTimer s_LoggerTime; static QElapsedTimer s_LoggerTime;
static QTextStream s_LoggerStream(stderr); static QTextStream s_LoggerStream(stdout);
static QMutex s_LoggerLock; static QMutex s_LoggerLock;
static bool s_SuppressVerboseOutput; static bool s_SuppressVerboseOutput;
static QRegularExpression k_RikeyRegex("&rikey=\\w+");
static QRegularExpression k_RikeyIdRegex("&rikeyid=[\\d-]+");
#ifdef LOG_TO_FILE #ifdef LOG_TO_FILE
// Max log file size of 10 MB // Max log file size of 10 MB
#define MAX_LOG_SIZE_BYTES (10 * 1024 * 1024) #define MAX_LOG_SIZE_BYTES (10 * 1024 * 1024)
@@ -75,10 +76,6 @@ void logToLoggerStream(QString& message)
{ {
QMutexLocker lock(&s_LoggerLock); QMutexLocker lock(&s_LoggerLock);
// Strip session encryption keys and IVs from the logs
message.replace(k_RikeyRegex, "&rikey=REDACTED");
message.replace(k_RikeyIdRegex, "&rikeyid=REDACTED");
#ifdef LOG_TO_FILE #ifdef LOG_TO_FILE
if (s_LogLimitReached) { if (s_LogLimitReached) {
return; return;
@@ -219,6 +216,8 @@ void ffmpegLogToDiskHandler(void* ptr, int level, const char* fmt, va_list vl)
#endif #endif
#endif
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <Windows.h> #include <Windows.h>
@@ -307,26 +306,14 @@ int main(int argc, char *argv[])
qputenv("QML_DISK_CACHE_PATH", Path::getQmlCacheDir().toUtf8()); qputenv("QML_DISK_CACHE_PATH", Path::getQmlCacheDir().toUtf8());
} }
#ifdef Q_OS_WIN32 #ifdef USE_CUSTOM_LOGGER
// Grab the original std handles before we potentially redirect them later
HANDLE oldConOut = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE oldConErr = GetStdHandle(STD_ERROR_HANDLE);
#endif
#ifdef LOG_TO_FILE #ifdef LOG_TO_FILE
QDir tempDir(Path::getLogDir()); QDir tempDir(Path::getLogDir());
#ifdef Q_OS_WIN32
// Only log to a file if the user didn't redirect stderr somewhere else
if (IS_UNSPECIFIED_HANDLE(oldConErr))
#endif
{
s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch()))); s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch())));
if (s_LoggerFile->open(QIODevice::WriteOnly | QIODevice::Text)) { if (s_LoggerFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream(stderr) << "Redirecting log output to " << s_LoggerFile->fileName() << Qt::endl; QTextStream(stderr) << "Redirecting log output to " << s_LoggerFile->fileName() << Qt::endl;
s_LoggerStream.setDevice(s_LoggerFile); s_LoggerStream.setDevice(s_LoggerFile);
} }
}
#endif #endif
s_LoggerTime.start(); s_LoggerTime.start();
@@ -337,6 +324,8 @@ int main(int argc, char *argv[])
av_log_set_callback(ffmpegLogToDiskHandler); av_log_set_callback(ffmpegLogToDiskHandler);
#endif #endif
#endif
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
// Create a crash dump when we crash on Windows // Create a crash dump when we crash on Windows
SetUnhandledExceptionFilter(UnhandledExceptionHandler); SetUnhandledExceptionFilter(UnhandledExceptionHandler);
@@ -362,11 +351,6 @@ int main(int argc, char *argv[])
SSL_free(nullptr); SSL_free(nullptr);
#endif #endif
// We keep this at function scope to ensure it stays around while we're running,
// becaue the Qt QPA will need to read it. Since the temporary file is only
// created when open() is called, this doesn't do any harm for other platforms.
QTemporaryFile eglfsConfigFile("eglfs_override_XXXXXX.conf");
// Avoid using High DPI on EGLFS. It breaks font rendering. // Avoid using High DPI on EGLFS. It breaks font rendering.
// https://bugreports.qt.io/browse/QTBUG-64377 // https://bugreports.qt.io/browse/QTBUG-64377
// //
@@ -400,17 +384,6 @@ int main(int argc, char *argv[])
qWarning() << "Unable to find a KMSDRM display device!"; qWarning() << "Unable to find a KMSDRM display device!";
qWarning() << "On the Raspberry Pi, you must enable the 'fake KMS' driver in raspi-config to use Moonlight outside of the GUI environment."; qWarning() << "On the Raspberry Pi, you must enable the 'fake KMS' driver in raspi-config to use Moonlight outside of the GUI environment.";
} }
else if (!qEnvironmentVariableIsSet("QT_QPA_EGLFS_KMS_CONFIG")) {
// HACK: Remove this when Qt is fixed to properly check for display support before picking a card
QString cardOverride = WMUtils::getDrmCardOverride();
if (!cardOverride.isEmpty()) {
if (eglfsConfigFile.open()) {
qInfo() << "Overriding default Qt EGLFS card selection to" << cardOverride;
QTextStream(&eglfsConfigFile) << "{ \"device\": \"" << cardOverride << "\" }";
qputenv("QT_QPA_EGLFS_KMS_CONFIG", eglfsConfigFile.fileName().toUtf8());
}
}
}
} }
// EGLFS uses OpenGLES 2.0, so we will too. Some embedded platforms may not // EGLFS uses OpenGLES 2.0, so we will too. Some embedded platforms may not
@@ -420,18 +393,9 @@ int main(int argc, char *argv[])
#endif #endif
} }
#if !defined(Q_PROCESSOR_X86) && defined(SDL_HINT_VIDEO_X11_FORCE_EGL)
// Some ARM and RISC-V embedded devices don't have working GLX which can cause
// SDL to fail to find a working OpenGL implementation at all. Let's force EGL
// on non-x86 platforms, since GLX is deprecated anyway.
SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1");
#endif
#ifdef Q_OS_MACOS
// This avoids using the default keychain for SSL, which may cause // This avoids using the default keychain for SSL, which may cause
// password prompts on macOS. // password prompts on macOS.
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1"); qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
#endif
#if defined(Q_OS_WIN32) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #if defined(Q_OS_WIN32) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
if (!qEnvironmentVariableIsSet("QT_OPENGL")) { if (!qEnvironmentVariableIsSet("QT_OPENGL")) {
@@ -454,12 +418,6 @@ int main(int argc, char *argv[])
qputenv("QSG_RENDER_LOOP", "basic"); qputenv("QSG_RENDER_LOOP", "basic");
#endif #endif
#if defined(Q_OS_DARWIN) && defined(QT_DEBUG)
// Enable Metal valiation for debug builds
qputenv("MTL_DEBUG_LAYER", "1");
qputenv("MTL_SHADER_VALIDATION", "1");
#endif
// We don't want system proxies to apply to us // We don't want system proxies to apply to us
QNetworkProxyFactory::setUseSystemConfiguration(false); QNetworkProxyFactory::setUseSystemConfiguration(false);
@@ -538,13 +496,6 @@ int main(int argc, char *argv[])
// SDL doing it for us behind our backs. // SDL doing it for us behind our backs.
SDL_SetHint("SDL_MOUSE_AUTO_CAPTURE", "0"); SDL_SetHint("SDL_MOUSE_AUTO_CAPTURE", "0");
// SDL will try to lock the mouse cursor on Wayland if it's not visible in order to
// support applications that assume they can warp the cursor (which isn't possible
// on Wayland). We don't want this behavior because it interferes with seamless mouse
// mode when toggling between windowed and fullscreen modes by unexpectedly locking
// the mouse cursor.
SDL_SetHint("SDL_VIDEO_WAYLAND_EMULATE_MOUSE_WARP", "0");
#ifdef QT_DEBUG #ifdef QT_DEBUG
// Allow thread naming using exceptions on debug builds. SDL doesn't use SEH // Allow thread naming using exceptions on debug builds. SDL doesn't use SEH
// when throwing the exceptions, so we don't enable it for release builds out // when throwing the exceptions, so we don't enable it for release builds out
@@ -554,34 +505,25 @@ int main(int argc, char *argv[])
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
#ifndef STEAM_LINK
// Force use of the KMSDRM backend for SDL when using Qt platform plugins
// that directly draw to the display without a windowing system.
if (QGuiApplication::platformName() == "eglfs" || QGuiApplication::platformName() == "linuxfb") {
qputenv("SDL_VIDEODRIVER", "kmsdrm");
}
#endif
GlobalCommandLineParser parser; GlobalCommandLineParser parser;
GlobalCommandLineParser::ParseResult commandLineParserResult = parser.parse(app.arguments()); GlobalCommandLineParser::ParseResult commandLineParserResult = parser.parse(app.arguments());
switch (commandLineParserResult) { switch (commandLineParserResult) {
case GlobalCommandLineParser::ListRequested: case GlobalCommandLineParser::ListRequested:
#ifdef USE_CUSTOM_LOGGER
// Don't log to the console since it will jumble the command output // Don't log to the console since it will jumble the command output
s_SuppressVerboseOutput = true; s_SuppressVerboseOutput = true;
#endif
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
// If we don't have stdout or stderr handles (which will normally be the case // Attach to the console to be able to print output.
// since we're a /SUBSYSTEM:WINDOWS app), attach to our parent console and use // Since we're a /SUBSYSTEM:WINDOWS app, we won't be attached by default.
// that for stdout and stderr.
//
// If we do have stdout or stderr handles, that means the user has used standard
// handle redirection. In that case, we don't want to override those handles.
if (AttachConsole(ATTACH_PARENT_PROCESS)) { if (AttachConsole(ATTACH_PARENT_PROCESS)) {
// If we didn't have an old stdout/stderr handle, use the new CONOUT$ handle HANDLE conOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (IS_UNSPECIFIED_HANDLE(oldConOut)) { if (conOut != INVALID_HANDLE_VALUE && conOut != NULL) {
freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stdout);
setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0);
} }
if (IS_UNSPECIFIED_HANDLE(oldConErr)) { HANDLE conErr = GetStdHandle(STD_ERROR_HANDLE);
if (conErr != INVALID_HANDLE_VALUE && conErr != NULL) {
freopen("CONOUT$", "w", stderr); freopen("CONOUT$", "w", stderr);
setvbuf(stderr, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0);
} }
@@ -605,7 +547,8 @@ int main(int argc, char *argv[])
runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch); runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch);
// Apply the initial translation based on user preference // Apply the initial translation based on user preference
StreamingPreferences::get()->retranslate(); StreamingPreferences prefs;
prefs.retranslate();
// Trickily declare the translation for dialog buttons // Trickily declare the translation for dialog buttons
QCoreApplication::translate("QPlatformTheme", "&Yes"); QCoreApplication::translate("QPlatformTheme", "&Yes");
@@ -670,8 +613,8 @@ int main(int argc, char *argv[])
qmlRegisterUncreatableType<Session>("Session", 1, 0, "Session", "Session cannot be created from QML"); qmlRegisterUncreatableType<Session>("Session", 1, 0, "Session", "Session cannot be created from QML");
qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0, qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0,
"ComputerManager", "ComputerManager",
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* { [](QQmlEngine*, QJSEngine*) -> QObject* {
return new ComputerManager(StreamingPreferences::get(qmlEngine)); return new ComputerManager();
}); });
qmlRegisterSingletonType<AutoUpdateChecker>("AutoUpdateChecker", 1, 0, qmlRegisterSingletonType<AutoUpdateChecker>("AutoUpdateChecker", 1, 0,
"AutoUpdateChecker", "AutoUpdateChecker",
@@ -685,13 +628,13 @@ int main(int argc, char *argv[])
}); });
qmlRegisterSingletonType<SdlGamepadKeyNavigation>("SdlGamepadKeyNavigation", 1, 0, qmlRegisterSingletonType<SdlGamepadKeyNavigation>("SdlGamepadKeyNavigation", 1, 0,
"SdlGamepadKeyNavigation", "SdlGamepadKeyNavigation",
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* { [](QQmlEngine*, QJSEngine*) -> QObject* {
return new SdlGamepadKeyNavigation(StreamingPreferences::get(qmlEngine)); return new SdlGamepadKeyNavigation();
}); });
qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0, qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0,
"StreamingPreferences", "StreamingPreferences",
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* { [](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
return StreamingPreferences::get(qmlEngine); return new StreamingPreferences(qmlEngine);
}); });
// Create the identity manager on the main thread // Create the identity manager on the main thread
@@ -722,7 +665,7 @@ int main(int argc, char *argv[])
case GlobalCommandLineParser::StreamRequested: case GlobalCommandLineParser::StreamRequested:
{ {
initialView = "qrc:/gui/CliStartStreamSegue.qml"; initialView = "qrc:/gui/CliStartStreamSegue.qml";
StreamingPreferences* preferences = StreamingPreferences::get(); StreamingPreferences* preferences = new StreamingPreferences(&app);
StreamCommandLineParser streamParser; StreamCommandLineParser streamParser;
streamParser.parse(app.arguments(), preferences); streamParser.parse(app.arguments(), preferences);
QString host = streamParser.getHost(); QString host = streamParser.getHost();
@@ -754,7 +697,7 @@ int main(int argc, char *argv[])
ListCommandLineParser listParser; ListCommandLineParser listParser;
listParser.parse(app.arguments()); listParser.parse(app.arguments());
auto launcher = new CliListApps::Launcher(listParser.getHost(), listParser, &app); auto launcher = new CliListApps::Launcher(listParser.getHost(), listParser, &app);
launcher->execute(new ComputerManager(StreamingPreferences::get())); launcher->execute(new ComputerManager(&app));
hasGUI = false; hasGUI = false;
break; break;
} }
@@ -775,12 +718,5 @@ int main(int argc, char *argv[])
// sometimes freezing and blocking process exit. // sometimes freezing and blocking process exit.
QThreadPool::globalInstance()->waitForDone(30000); QThreadPool::globalInstance()->waitForDone(30000);
#ifdef Q_OS_WIN32
// Without an explicit flush, console redirection for the list command
// doesn't work reliably (sometimes the target file contains no text).
fflush(stderr);
fflush(stdout);
#endif
return err; return err;
} }
+1
View File
@@ -0,0 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240" fill="white"><path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

-1
View File
@@ -1 +0,0 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="white"><path d="M20.317 4.3698a19.7913 19.7913 0 0 0-4.8851-1.5152.0741.0741 0 0 0-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 0 0-.0785-.037 19.7363 19.7363 0 0 0-4.8852 1.515.0699.0699 0 0 0-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 0 0 .0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 0 0 .0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 0 0-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 0 1-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 0 1 .0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 0 1 .0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 0 1-.0066.1276 12.2986 12.2986 0 0 1-1.873.8914.0766.0766 0 0 0-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 0 0 .0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 0 0 .0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 0 0-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

+1 -4
View File
@@ -17,7 +17,7 @@
<file>res/baseline-help_outline-24px.svg</file> <file>res/baseline-help_outline-24px.svg</file>
<file>res/baseline-error_outline-24px.svg</file> <file>res/baseline-error_outline-24px.svg</file>
<file>res/baseline-check_circle_outline-24px.svg</file> <file>res/baseline-check_circle_outline-24px.svg</file>
<file>res/discord.svg</file> <file>res/Discord-Logo-White.svg</file>
<file>languages/qml_de.ts</file> <file>languages/qml_de.ts</file>
<file>languages/qml_de.qm</file> <file>languages/qml_de.qm</file>
<file>languages/qml_fr.ts</file> <file>languages/qml_fr.ts</file>
@@ -64,8 +64,6 @@
<file>languages/qml_cs.qm</file> <file>languages/qml_cs.qm</file>
<file>languages/qml_he.ts</file> <file>languages/qml_he.ts</file>
<file>languages/qml_he.qm</file> <file>languages/qml_he.qm</file>
<file>languages/qml_ckb.ts</file>
<file>languages/qml_ckb.qm</file>
<!-- Don't include pt_BR until it is more complete --> <!-- Don't include pt_BR until it is more complete -->
<!--file>languages/qml_pt_BR.qm</file--> <!--file>languages/qml_pt_BR.qm</file-->
<!--file>languages/qml_pt_BR.ts</file--> <!--file>languages/qml_pt_BR.ts</file-->
@@ -84,6 +82,5 @@
<file alias="d3d11_genyuv_pixel.fxc">shaders/d3d11_genyuv_pixel.fxc</file> <file alias="d3d11_genyuv_pixel.fxc">shaders/d3d11_genyuv_pixel.fxc</file>
<file alias="d3d11_bt601lim_pixel.fxc">shaders/d3d11_bt601lim_pixel.fxc</file> <file alias="d3d11_bt601lim_pixel.fxc">shaders/d3d11_bt601lim_pixel.fxc</file>
<file alias="d3d11_bt2020lim_pixel.fxc">shaders/d3d11_bt2020lim_pixel.fxc</file> <file alias="d3d11_bt2020lim_pixel.fxc">shaders/d3d11_bt2020lim_pixel.fxc</file>
<file alias="vt_renderer.metal">shaders/vt_renderer.metal</file>
</qresource> </qresource>
</RCC> </RCC>
+7 -45
View File
@@ -5,7 +5,6 @@
#include <QTranslator> #include <QTranslator>
#include <QCoreApplication> #include <QCoreApplication>
#include <QLocale> #include <QLocale>
#include <QReadWriteLock>
#include <QtMath> #include <QtMath>
#include <QtDebug> #include <QtDebug>
@@ -38,7 +37,6 @@
#define SER_DEFAULTVER "defaultver" #define SER_DEFAULTVER "defaultver"
#define SER_PACKETSIZE "packetsize" #define SER_PACKETSIZE "packetsize"
#define SER_DETECTNETBLOCKING "detectnetblocking" #define SER_DETECTNETBLOCKING "detectnetblocking"
#define SER_SHOWPERFOVERLAY "showperfoverlay"
#define SER_SWAPMOUSEBUTTONS "swapmousebuttons" #define SER_SWAPMOUSEBUTTONS "swapmousebuttons"
#define SER_MUTEONFOCUSLOSS "muteonfocusloss" #define SER_MUTEONFOCUSLOSS "muteonfocusloss"
#define SER_BACKGROUNDGAMEPAD "backgroundgamepad" #define SER_BACKGROUNDGAMEPAD "backgroundgamepad"
@@ -50,50 +48,18 @@
#define CURRENT_DEFAULT_VER 2 #define CURRENT_DEFAULT_VER 2
static StreamingPreferences* s_GlobalPrefs; StreamingPreferences::StreamingPreferences(QObject *parent)
static QReadWriteLock s_GlobalPrefsLock; : QObject(parent),
m_QmlEngine(nullptr)
StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine)
: m_QmlEngine(qmlEngine)
{ {
reload(); reload();
} }
StreamingPreferences* StreamingPreferences::get(QQmlEngine *qmlEngine) StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent)
: QObject(parent),
m_QmlEngine(qmlEngine)
{ {
{ reload();
QReadLocker readGuard(&s_GlobalPrefsLock);
// If we have a preference object and it's associated with a QML engine or
// if the caller didn't specify a QML engine, return the existing object.
if (s_GlobalPrefs && (s_GlobalPrefs->m_QmlEngine || !qmlEngine)) {
// The lifetime logic here relies on the QML engine also being a singleton.
Q_ASSERT(!qmlEngine || s_GlobalPrefs->m_QmlEngine == qmlEngine);
return s_GlobalPrefs;
}
}
{
QWriteLocker writeGuard(&s_GlobalPrefsLock);
// If we already have an preference object but the QML engine is now available,
// associate the QML engine with the preferences.
if (s_GlobalPrefs) {
if (!s_GlobalPrefs->m_QmlEngine) {
s_GlobalPrefs->m_QmlEngine = qmlEngine;
}
else {
// We could reach this codepath if another thread raced with us
// and created the object while we were outside the pref lock.
Q_ASSERT(!qmlEngine || s_GlobalPrefs->m_QmlEngine == qmlEngine);
}
}
else {
s_GlobalPrefs = new StreamingPreferences(qmlEngine);
}
return s_GlobalPrefs;
}
} }
void StreamingPreferences::reload() void StreamingPreferences::reload()
@@ -131,7 +97,6 @@ void StreamingPreferences::reload()
richPresence = settings.value(SER_RICHPRESENCE, true).toBool(); richPresence = settings.value(SER_RICHPRESENCE, true).toBool();
gamepadMouse = settings.value(SER_GAMEPADMOUSE, true).toBool(); gamepadMouse = settings.value(SER_GAMEPADMOUSE, true).toBool();
detectNetworkBlocking = settings.value(SER_DETECTNETBLOCKING, true).toBool(); detectNetworkBlocking = settings.value(SER_DETECTNETBLOCKING, true).toBool();
showPerformanceOverlay = settings.value(SER_SHOWPERFOVERLAY, false).toBool();
packetSize = settings.value(SER_PACKETSIZE, 0).toInt(); packetSize = settings.value(SER_PACKETSIZE, 0).toInt();
swapMouseButtons = settings.value(SER_SWAPMOUSEBUTTONS, false).toBool(); swapMouseButtons = settings.value(SER_SWAPMOUSEBUTTONS, false).toBool();
muteOnFocusLoss = settings.value(SER_MUTEONFOCUSLOSS, false).toBool(); muteOnFocusLoss = settings.value(SER_MUTEONFOCUSLOSS, false).toBool();
@@ -287,8 +252,6 @@ QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Langua
return "cs"; return "cs";
case LANG_HE: case LANG_HE:
return "he"; return "he";
case LANG_CKB:
return "ckb";
case LANG_AUTO: case LANG_AUTO:
default: default:
return QLocale::system().name(); return QLocale::system().name();
@@ -317,7 +280,6 @@ void StreamingPreferences::save()
settings.setValue(SER_GAMEPADMOUSE, gamepadMouse); settings.setValue(SER_GAMEPADMOUSE, gamepadMouse);
settings.setValue(SER_PACKETSIZE, packetSize); settings.setValue(SER_PACKETSIZE, packetSize);
settings.setValue(SER_DETECTNETBLOCKING, detectNetworkBlocking); settings.setValue(SER_DETECTNETBLOCKING, detectNetworkBlocking);
settings.setValue(SER_SHOWPERFOVERLAY, showPerformanceOverlay);
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig)); settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
settings.setValue(SER_HDR, enableHdr); settings.setValue(SER_HDR, enableHdr);
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig)); settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
+2 -7
View File
@@ -9,7 +9,8 @@ class StreamingPreferences : public QObject
Q_OBJECT Q_OBJECT
public: public:
static StreamingPreferences* get(QQmlEngine *qmlEngine = nullptr); StreamingPreferences(QObject *parent = nullptr);
StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent = nullptr);
Q_INVOKABLE static int Q_INVOKABLE static int
getDefaultBitrate(int width, int height, int fps); getDefaultBitrate(int width, int height, int fps);
@@ -91,7 +92,6 @@ public:
LANG_PL, LANG_PL,
LANG_CS, LANG_CS,
LANG_HE, LANG_HE,
LANG_CKB,
}; };
Q_ENUM(Language); Q_ENUM(Language);
@@ -120,7 +120,6 @@ public:
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged) Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged)
Q_PROPERTY(bool gamepadMouse MEMBER gamepadMouse NOTIFY gamepadMouseChanged) Q_PROPERTY(bool gamepadMouse MEMBER gamepadMouse NOTIFY gamepadMouseChanged)
Q_PROPERTY(bool detectNetworkBlocking MEMBER detectNetworkBlocking NOTIFY detectNetworkBlockingChanged) Q_PROPERTY(bool detectNetworkBlocking MEMBER detectNetworkBlocking NOTIFY detectNetworkBlockingChanged)
Q_PROPERTY(bool showPerformanceOverlay MEMBER showPerformanceOverlay NOTIFY showPerformanceOverlayChanged)
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged) Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged) Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
Q_PROPERTY(bool enableHdr MEMBER enableHdr NOTIFY enableHdrChanged) Q_PROPERTY(bool enableHdr MEMBER enableHdr NOTIFY enableHdrChanged)
@@ -157,7 +156,6 @@ public:
bool richPresence; bool richPresence;
bool gamepadMouse; bool gamepadMouse;
bool detectNetworkBlocking; bool detectNetworkBlocking;
bool showPerformanceOverlay;
bool swapMouseButtons; bool swapMouseButtons;
bool muteOnFocusLoss; bool muteOnFocusLoss;
bool backgroundGamepad; bool backgroundGamepad;
@@ -198,7 +196,6 @@ signals:
void richPresenceChanged(); void richPresenceChanged();
void gamepadMouseChanged(); void gamepadMouseChanged();
void detectNetworkBlockingChanged(); void detectNetworkBlockingChanged();
void showPerformanceOverlayChanged();
void mouseButtonsChanged(); void mouseButtonsChanged();
void muteOnFocusLossChanged(); void muteOnFocusLossChanged();
void backgroundGamepadChanged(); void backgroundGamepadChanged();
@@ -209,8 +206,6 @@ signals:
void languageChanged(); void languageChanged();
private: private:
explicit StreamingPreferences(QQmlEngine *qmlEngine);
QString getSuffixFromLanguage(Language lang); QString getSuffixFromLanguage(Language lang);
QQmlEngine* m_QmlEngine; QQmlEngine* m_QmlEngine;
-41
View File
@@ -1,41 +0,0 @@
using namespace metal;
struct Vertex
{
float4 position [[ position ]];
float2 texCoords;
};
struct CscParams
{
float3 matrix[3];
float3 offsets;
};
constexpr sampler s(coord::normalized, address::clamp_to_edge, filter::linear);
vertex Vertex vs_draw(constant Vertex *vertices [[ buffer(0) ]], uint id [[ vertex_id ]])
{
return vertices[id];
}
fragment float4 ps_draw_biplanar(Vertex v [[ stage_in ]],
constant CscParams &cscParams [[ buffer(0) ]],
texture2d<float> luminancePlane [[ texture(0) ]],
texture2d<float> chrominancePlane [[ texture(1) ]])
{
float3 yuv = float3(luminancePlane.sample(s, v.texCoords).r, chrominancePlane.sample(s, v.texCoords).rg);
yuv -= cscParams.offsets;
float3 rgb;
rgb.r = dot(yuv, cscParams.matrix[0]);
rgb.g = dot(yuv, cscParams.matrix[1]);
rgb.b = dot(yuv, cscParams.matrix[2]);
return float4(rgb, 1.0f);
}
fragment float4 ps_draw_rgb(Vertex v [[ stage_in ]],
texture2d<float> rgbTexture [[ texture(0) ]])
{
return rgbTexture.sample(s, v.texCoords);
}
+40 -53
View File
@@ -64,48 +64,6 @@ IAudioRenderer* Session::createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATI
return nullptr; return nullptr;
} }
bool Session::initializeAudioRenderer()
{
int error;
SDL_assert(m_OriginalAudioConfig.channelCount > 0);
SDL_assert(m_AudioRenderer == nullptr);
SDL_assert(m_OpusDecoder == nullptr);
m_AudioRenderer = createAudioRenderer(&m_OriginalAudioConfig);
// We may be unable to create an audio renderer right now
if (m_AudioRenderer == nullptr) {
return false;
}
// Allow the chosen renderer to remap Opus channels as needed to ensure proper output
m_ActiveAudioConfig = m_OriginalAudioConfig;
m_AudioRenderer->remapChannels(&m_ActiveAudioConfig);
// Create the Opus decoder with the renderer's preferred channel mapping
m_OpusDecoder =
opus_multistream_decoder_create(m_ActiveAudioConfig.sampleRate,
m_ActiveAudioConfig.channelCount,
m_ActiveAudioConfig.streams,
m_ActiveAudioConfig.coupledStreams,
m_ActiveAudioConfig.mapping,
&error);
if (m_OpusDecoder == nullptr) {
delete m_AudioRenderer;
m_AudioRenderer = nullptr;
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to create decoder: %d",
error);
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Audio stream has %d channels",
m_ActiveAudioConfig.channelCount);
return true;
}
int Session::getAudioRendererCapabilities(int audioConfiguration) int Session::getAudioRendererCapabilities(int audioConfiguration)
{ {
// Build a fake OPUS_MULTISTREAM_CONFIGURATION to give // Build a fake OPUS_MULTISTREAM_CONFIGURATION to give
@@ -150,8 +108,39 @@ int Session::arInit(int /* audioConfiguration */,
const POPUS_MULTISTREAM_CONFIGURATION opusConfig, const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
void* /* arContext */, int /* arFlags */) void* /* arContext */, int /* arFlags */)
{ {
SDL_memcpy(&s_ActiveSession->m_OriginalAudioConfig, opusConfig, sizeof(*opusConfig)); int error;
s_ActiveSession->initializeAudioRenderer();
SDL_memcpy(&s_ActiveSession->m_AudioConfig, opusConfig, sizeof(*opusConfig));
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(&s_ActiveSession->m_AudioConfig);
if (s_ActiveSession->m_AudioRenderer == nullptr) {
return -2;
}
// Allow the chosen renderer to remap Opus channels as needed to ensure proper output
s_ActiveSession->m_AudioRenderer->remapChannels(&s_ActiveSession->m_AudioConfig);
// Create the Opus decoder with the renderer's preferred channel mapping
s_ActiveSession->m_OpusDecoder =
opus_multistream_decoder_create(s_ActiveSession->m_AudioConfig.sampleRate,
s_ActiveSession->m_AudioConfig.channelCount,
s_ActiveSession->m_AudioConfig.streams,
s_ActiveSession->m_AudioConfig.coupledStreams,
s_ActiveSession->m_AudioConfig.mapping,
&error);
if (s_ActiveSession->m_OpusDecoder == NULL) {
delete s_ActiveSession->m_AudioRenderer;
s_ActiveSession->m_AudioRenderer = nullptr;
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to create decoder: %d",
error);
return -1;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Audio stream has %d channels",
s_ActiveSession->m_AudioConfig.channelCount);
return 0; return 0;
} }
@@ -205,7 +194,7 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
} }
if (s_ActiveSession->m_AudioRenderer != nullptr) { if (s_ActiveSession->m_AudioRenderer != nullptr) {
int desiredSize = sizeof(short) * s_ActiveSession->m_ActiveAudioConfig.samplesPerFrame * s_ActiveSession->m_ActiveAudioConfig.channelCount; int desiredSize = sizeof(short) * s_ActiveSession->m_AudioConfig.samplesPerFrame * s_ActiveSession->m_AudioConfig.channelCount;
void* buffer = s_ActiveSession->m_AudioRenderer->getAudioBuffer(&desiredSize); void* buffer = s_ActiveSession->m_AudioRenderer->getAudioBuffer(&desiredSize);
if (buffer == nullptr) { if (buffer == nullptr) {
return; return;
@@ -215,13 +204,13 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
(unsigned char*)sampleData, (unsigned char*)sampleData,
sampleLength, sampleLength,
(short*)buffer, (short*)buffer,
desiredSize / sizeof(short) / s_ActiveSession->m_ActiveAudioConfig.channelCount, desiredSize / sizeof(short) / s_ActiveSession->m_AudioConfig.channelCount,
0); 0);
// Update desiredSize with the number of bytes actually populated by the decoding operation // Update desiredSize with the number of bytes actually populated by the decoding operation
if (samplesDecoded > 0) { if (samplesDecoded > 0) {
SDL_assert(desiredSize >= (int)(sizeof(short) * samplesDecoded * s_ActiveSession->m_ActiveAudioConfig.channelCount)); SDL_assert(desiredSize >= (int)(sizeof(short) * samplesDecoded * s_ActiveSession->m_AudioConfig.channelCount));
desiredSize = sizeof(short) * samplesDecoded * s_ActiveSession->m_ActiveAudioConfig.channelCount; desiredSize = sizeof(short) * samplesDecoded * s_ActiveSession->m_AudioConfig.channelCount;
} }
else { else {
desiredSize = 0; desiredSize = 0;
@@ -231,9 +220,6 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Reinitializing audio renderer after failure"); "Reinitializing audio renderer after failure");
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
s_ActiveSession->m_OpusDecoder = nullptr;
delete s_ActiveSession->m_AudioRenderer; delete s_ActiveSession->m_AudioRenderer;
s_ActiveSession->m_AudioRenderer = nullptr; s_ActiveSession->m_AudioRenderer = nullptr;
} }
@@ -248,7 +234,9 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
// to drop samples to account for the time we've spent blocking audio rendering // to drop samples to account for the time we've spent blocking audio rendering
// so we return to real-time playback and don't accumulate latency. // so we return to real-time playback and don't accumulate latency.
Uint32 audioReinitStartTime = SDL_GetTicks(); Uint32 audioReinitStartTime = SDL_GetTicks();
if (s_ActiveSession->initializeAudioRenderer()) {
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(&s_ActiveSession->m_AudioConfig);
Uint32 audioReinitStopTime = SDL_GetTicks(); Uint32 audioReinitStopTime = SDL_GetTicks();
s_ActiveSession->m_DropAudioEndTime = audioReinitStopTime + (audioReinitStopTime - audioReinitStartTime); s_ActiveSession->m_DropAudioEndTime = audioReinitStopTime + (audioReinitStopTime - audioReinitStartTime);
@@ -256,5 +244,4 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
"Audio reinitialization took %d ms - starting drop window", "Audio reinitialization took %d ms - starting drop window",
audioReinitStopTime - audioReinitStartTime); audioReinitStopTime - audioReinitStartTime);
} }
}
} }
+8 -14
View File
@@ -100,6 +100,12 @@ void* SdlAudioRenderer::getAudioBuffer(int*)
bool SdlAudioRenderer::submitAudio(int bytesWritten) bool SdlAudioRenderer::submitAudio(int bytesWritten)
{ {
// Our device may enter a permanent error status upon removal, so we need
// to recreate the audio device to pick up the new default audio device.
if (SDL_GetAudioDeviceStatus(m_AudioDevice) == SDL_AUDIO_STOPPED) {
return false;
}
if (bytesWritten == 0) { if (bytesWritten == 0) {
// Nothing to do // Nothing to do
return true; return true;
@@ -112,20 +118,8 @@ bool SdlAudioRenderer::submitAudio(int bytesWritten)
} }
// Provide backpressure on the queue to ensure too many frames don't build up // Provide backpressure on the queue to ensure too many frames don't build up
// in SDL's audio queue, but don't wait forever to avoid a deadlock if the // in SDL's audio queue.
// audio device fails. while (SDL_GetQueuedAudioSize(m_AudioDevice) / m_FrameSize > 10) {
for (int i = 0; i < 100; i++) {
// Our device may enter a permanent error status upon removal, so we need
// to recreate the audio device to pick up the new default audio device.
if (SDL_GetAudioDeviceStatus(m_AudioDevice) == SDL_AUDIO_STOPPED) {
return false;
}
// Only queue more samples where there are 10 frames or less in SDL's queue
if (SDL_GetQueuedAudioSize(m_AudioDevice) / m_FrameSize <= 10) {
break;
}
SDL_Delay(1); SDL_Delay(1);
} }
+5 -32
View File
@@ -112,43 +112,16 @@ void SdlInputHandler::handleAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
pointerId = (uint32_t)event->fingerId; pointerId = (uint32_t)event->fingerId;
} }
// Try to send it as a native pen/touch event, otherwise fall back to our touch emulation // Try to send it as a native touch event, otherwise fall back to our touch emulation
if (LiGetHostFeatureFlags() & LI_FF_PEN_TOUCH_EVENTS) { if (LiSendTouchEvent(eventType, pointerId, vidrelx / dst.w, vidrely / dst.h, event->pressure,
#if SDL_VERSION_ATLEAST(2, 0, 22) 0.0f, 0.0f, LI_ROT_UNKNOWN) == LI_ERR_UNSUPPORTED) {
bool isPen = false; emulateAbsoluteFingerEvent(event);
int numTouchDevices = SDL_GetNumTouchDevices();
for (int i = 0; i < numTouchDevices; i++) {
if (event->touchId == SDL_GetTouchDevice(i)) {
const char* touchName = SDL_GetTouchName(i);
// SDL will report "pen" as the name of pen input devices on Windows.
// https://github.com/libsdl-org/SDL/pull/5926
isPen = touchName && SDL_strcmp(touchName, "pen") == 0;
break;
} }
} else if (!m_DisabledTouchFeedback) {
if (isPen) {
LiSendPenEvent(eventType, LI_TOOL_TYPE_PEN, 0, vidrelx / dst.w, vidrely / dst.h, event->pressure,
0.0f, 0.0f, LI_ROT_UNKNOWN, LI_TILT_UNKNOWN);
}
else
#endif
{
LiSendTouchEvent(eventType, pointerId, vidrelx / dst.w, vidrely / dst.h, event->pressure,
0.0f, 0.0f, LI_ROT_UNKNOWN);
}
if (!m_DisabledTouchFeedback) {
// Disable touch feedback when passing touch natively // Disable touch feedback when passing touch natively
disableTouchFeedback(); disableTouchFeedback();
m_DisabledTouchFeedback = true; m_DisabledTouchFeedback = true;
} }
}
else {
emulateAbsoluteFingerEvent(event);
}
} }
void SdlInputHandler::emulateAbsoluteFingerEvent(SDL_TouchFingerEvent* event) void SdlInputHandler::emulateAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
+9 -46
View File
@@ -68,48 +68,15 @@ void SdlInputHandler::sendGamepadState(GamepadState* state)
} }
} }
unsigned char lt = state->lt;
unsigned char rt = state->rt;
short lsX = state->lsX;
short lsY = state->lsY;
short rsX = state->rsX;
short rsY = state->rsY;
// When in single controller mode, merge all gamepad state together
if (!m_MultiController) {
for (int i = 0; i < MAX_GAMEPADS; i++) {
if (m_GamepadState[i].index == state->index) {
buttons |= m_GamepadState[i].buttons;
if (lt < m_GamepadState[i].lt) {
lt = m_GamepadState[i].lt;
}
if (rt < m_GamepadState[i].rt) {
rt = m_GamepadState[i].rt;
}
// We use abs() here instead of qAbs() for get proper integer promotion to
// correctly handle abs(-32768), which is not representable in a short.
if (abs(lsX) < abs(m_GamepadState[i].lsX) || abs(lsY) < abs(m_GamepadState[i].lsY)) {
lsX = m_GamepadState[i].lsX;
lsY = m_GamepadState[i].lsY;
}
if (abs(rsX) < abs(m_GamepadState[i].rsX) || abs(rsY) < abs(m_GamepadState[i].rsY)) {
rsX = m_GamepadState[i].rsX;
rsY = m_GamepadState[i].rsY;
}
}
}
}
LiSendMultiControllerEvent(state->index, LiSendMultiControllerEvent(state->index,
m_GamepadMask, m_GamepadMask,
buttons, buttons,
lt, state->lt,
rt, state->rt,
lsX, state->lsX,
lsY, state->lsY,
rsX, state->rsX,
rsY); state->rsY);
} }
void SdlInputHandler::sendGamepadBatteryState(GamepadState* state, SDL_JoystickPowerLevel level) void SdlInputHandler::sendGamepadBatteryState(GamepadState* state, SDL_JoystickPowerLevel level)
@@ -157,11 +124,11 @@ Uint32 SdlInputHandler::mouseEmulationTimerCallback(Uint32 interval, void *param
{ {
auto gamepad = reinterpret_cast<GamepadState*>(param); auto gamepad = reinterpret_cast<GamepadState*>(param);
int rawX; short rawX;
int rawY; short rawY;
// Determine which analog stick is currently receiving the strongest input // Determine which analog stick is currently receiving the strongest input
if (abs(gamepad->lsX) + abs(gamepad->lsY) > abs(gamepad->rsX) + abs(gamepad->rsY)) { if ((uint32_t)qAbs(gamepad->lsX) + qAbs(gamepad->lsY) > (uint32_t)qAbs(gamepad->rsX) + qAbs(gamepad->rsY)) {
rawX = gamepad->lsX; rawX = gamepad->lsX;
rawY = -gamepad->lsY; rawY = -gamepad->lsY;
} }
@@ -955,10 +922,6 @@ QString SdlInputHandler::getUnmappedGamepads()
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
// Flush stale events so they aren't processed by the main session event loop
SDL_FlushEvents(SDL_JOYDEVICEADDED, SDL_JOYDEVICEREMOVED);
SDL_FlushEvents(SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEREMAPPED);
return ret; return ret;
} }
+8
View File
@@ -42,6 +42,14 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i
// Allow gamepad input when the app doesn't have focus if requested // Allow gamepad input when the app doesn't have focus if requested
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, prefs.backgroundGamepad ? "1" : "0"); SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, prefs.backgroundGamepad ? "1" : "0");
// If absolute mouse mode is enabled, use relative mode warp (which
// is via normal motion events that are influenced by mouse acceleration).
// Otherwise, we'll use raw input capture which is straight from the device
// without modification by the OS.
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP,
prefs.absoluteMouseMode ? "1" : "0",
SDL_HINT_OVERRIDE);
#if !SDL_VERSION_ATLEAST(2, 0, 15) #if !SDL_VERSION_ATLEAST(2, 0, 15)
// For older versions of SDL (2.0.14 and earlier), use SDL_HINT_GRAB_KEYBOARD // For older versions of SDL (2.0.14 and earlier), use SDL_HINT_GRAB_KEYBOARD
SDL_SetHintWithPriority(SDL_HINT_GRAB_KEYBOARD, SDL_SetHintWithPriority(SDL_HINT_GRAB_KEYBOARD,
+1 -1
View File
@@ -114,7 +114,7 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
} }
// Send this text to the PC // Send this text to the PC
LiSendUtf8TextEvent(text, (unsigned int)strlen(text)); LiSendUtf8TextEvent(text, strlen(text));
// SDL_GetClipboardText() allocates, so we must free // SDL_GetClipboardText() allocates, so we must free
SDL_free((void*)text); SDL_free((void*)text);
+8 -23
View File
@@ -79,24 +79,6 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
return; return;
} }
// Batch all pending mouse motion events to save CPU time
Sint32 x = event->x, y = event->y, xrel = event->xrel, yrel = event->yrel;
SDL_Event nextEvent;
while (SDL_PeepEvents(&nextEvent, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) {
event = &nextEvent.motion;
// Ignore synthetic mouse events
if (event->which != SDL_TOUCH_MOUSEID) {
x = event->x;
y = event->y;
xrel += event->xrel;
yrel += event->yrel;
}
}
// We should not reference the original event anymore
event = nullptr;
if (m_AbsoluteMouseMode) { if (m_AbsoluteMouseMode) {
int windowWidth, windowHeight; int windowWidth, windowHeight;
SDL_GetWindowSize(m_Window, &windowWidth, &windowHeight); SDL_GetWindowSize(m_Window, &windowWidth, &windowHeight);
@@ -115,11 +97,14 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
// Use the stream and window sizes to determine the video region // Use the stream and window sizes to determine the video region
StreamUtils::scaleSourceToDestinationSurface(&src, &dst); StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
mouseInVideoRegion = isMouseInVideoRegion(x, y, windowWidth, windowHeight); mouseInVideoRegion = isMouseInVideoRegion(event->x,
event->y,
windowWidth,
windowHeight);
// Clamp motion to the video region // Clamp motion to the video region
x = qMin(qMax(x - dst.x, 0), dst.w); short x = qMin(qMax(event->x - dst.x, 0), dst.w);
y = qMin(qMax(y - dst.y, 0), dst.h); short y = qMin(qMax(event->y - dst.y, 0), dst.h);
// Send the mouse position update if one of the following is true: // Send the mouse position update if one of the following is true:
// a) it is in the video region now // a) it is in the video region now
@@ -134,7 +119,7 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
} }
} }
if (mouseInVideoRegion || m_MouseWasInVideoRegion || m_PendingMouseButtonsAllUpOnVideoRegionLeave) { if (mouseInVideoRegion || m_MouseWasInVideoRegion || m_PendingMouseButtonsAllUpOnVideoRegionLeave) {
LiSendMousePositionEvent((short)x, (short)y, dst.w, dst.h); LiSendMousePositionEvent(x, y, dst.w, dst.h);
} }
// Adjust the cursor visibility if applicable // Adjust the cursor visibility if applicable
@@ -150,7 +135,7 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
m_MouseWasInVideoRegion = mouseInVideoRegion; m_MouseWasInVideoRegion = mouseInVideoRegion;
} }
else { else {
LiSendMouseMoveEvent(xrel, yrel); LiSendMouseMoveEvent(event->xrel, event->yrel);
} }
} }
+44 -184
View File
@@ -52,7 +52,6 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QCursor> #include <QCursor>
#include <QWindow> #include <QWindow>
#include <QScreen>
#define CONN_TEST_SERVER "qt.conntest.moonlight-stream.org" #define CONN_TEST_SERVER "qt.conntest.moonlight-stream.org"
@@ -133,11 +132,7 @@ void Session::clConnectionTerminated(int errorCode)
default: default:
s_ActiveSession->m_UnexpectedTermination = true; s_ActiveSession->m_UnexpectedTermination = true;
emit s_ActiveSession->displayLaunchError(tr("Connection terminated"));
// We'll assume large errors are hex values
bool hexError = qAbs(errorCode) > 1000;
emit s_ActiveSession->displayLaunchError(tr("Connection terminated") + "\n\n" +
tr("Error code: %1").arg(errorCode, hexError ? 8 : 0, hexError ? 16 : 10, QChar('0')));
break; break;
} }
@@ -541,15 +536,17 @@ bool Session::populateDecoderProperties(SDL_Window* window)
} }
Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences) Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences)
: m_Preferences(preferences ? preferences : StreamingPreferences::get()), : m_Preferences(preferences ? preferences : new StreamingPreferences(this)),
m_IsFullScreen(m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED || !WMUtils::isRunningDesktopEnvironment()), m_IsFullScreen(m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED || !WMUtils::isRunningDesktopEnvironment()),
m_Computer(computer), m_Computer(computer),
m_App(app), m_App(app),
m_Window(nullptr), m_Window(nullptr),
m_VideoDecoder(nullptr), m_VideoDecoder(nullptr),
m_DecoderLock(0), m_DecoderLock(0),
m_AudioDisabled(false),
m_AudioMuted(false), m_AudioMuted(false),
m_QtWindow(nullptr), m_DisplayOriginX(0),
m_DisplayOriginY(0),
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
m_InputHandler(nullptr), m_InputHandler(nullptr),
m_MouseEmulationRefCount(0), m_MouseEmulationRefCount(0),
@@ -565,40 +562,6 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
bool Session::initialize() bool Session::initialize()
{ {
#ifdef Q_OS_DARWIN
// If we have a notch and the user specified one of the two native display modes
// (notched or notchless), override the fullscreen mode to ensure it works as expected.
// - SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES=0 will place the video underneath the notch
// - SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES=1 will place the video below the notch
bool shouldUseFullScreenSpaces = true;
SDL_DisplayMode desktopMode;
SDL_Rect safeArea;
for (int displayIndex = 0; StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode, &safeArea); displayIndex++) {
// Check if this display has a notch (safeArea != desktopMode)
if (desktopMode.h != safeArea.h || desktopMode.w != safeArea.w) {
// Check if we're trying to stream at the full native resolution (including notch)
if (m_Preferences->width == desktopMode.w && m_Preferences->height == desktopMode.h) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Overriding default fullscreen mode for native fullscreen resolution");
shouldUseFullScreenSpaces = false;
break;
}
}
}
// Using modesetting on modern versions of macOS is extremely unreliable
// and leads to hangs, deadlocks, and other nasty stuff. The only time
// people seem to use it is to get the full screen on notched Macs,
// which setting SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES=1 also accomplishes
// with much less headache.
//
// https://github.com/moonlight-stream/moonlight-qt/issues/973
// https://github.com/moonlight-stream/moonlight-qt/issues/999
// https://github.com/moonlight-stream/moonlight-qt/issues/1211
// https://github.com/moonlight-stream/moonlight-qt/issues/1218
SDL_SetHint(SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES, shouldUseFullScreenSpaces ? "1" : "0");
#endif
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) { if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s", "SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
@@ -635,18 +598,13 @@ bool Session::initialize()
m_StreamConfig.height = m_Preferences->height; m_StreamConfig.height = m_Preferences->height;
m_StreamConfig.fps = m_Preferences->fps; m_StreamConfig.fps = m_Preferences->fps;
m_StreamConfig.bitrate = m_Preferences->bitrateKbps; m_StreamConfig.bitrate = m_Preferences->bitrateKbps;
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
m_StreamConfig.av1BitratePercentageMultiplier = 75;
#ifndef STEAM_LINK #ifndef STEAM_LINK
// Opt-in to all encryption features if we detect that the platform
// has AES cryptography acceleration instructions and more than 2 cores.
if (StreamUtils::hasFastAes() && SDL_GetCPUCount() > 2) {
m_StreamConfig.encryptionFlags = ENCFLG_ALL;
}
else {
// Enable audio encryption as long as we're not on Steam Link. // Enable audio encryption as long as we're not on Steam Link.
// That hardware can hardly handle Opus decoding at all. // That hardware can hardly handle Opus decoding at all.
m_StreamConfig.encryptionFlags = ENCFLG_AUDIO; m_StreamConfig.encryptionFlags = ENCFLG_AUDIO;
}
#endif #endif
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
@@ -760,15 +718,6 @@ bool Session::initialize()
if (m_Preferences->enableHdr) { if (m_Preferences->enableHdr) {
m_StreamConfig.supportedVideoFormats |= VIDEO_FORMAT_AV1_MAIN10; m_StreamConfig.supportedVideoFormats |= VIDEO_FORMAT_AV1_MAIN10;
} }
// We'll try to fall back to HEVC first if AV1 fails. We'd rather not fall back
// straight to H.264 if the user asked for AV1 and the host doesn't support it.
if (m_StreamConfig.supportedVideoFormats & VIDEO_FORMAT_AV1_MAIN8) {
m_StreamConfig.supportedVideoFormats |= VIDEO_FORMAT_H265;
}
if (m_StreamConfig.supportedVideoFormats & VIDEO_FORMAT_AV1_MAIN10) {
m_StreamConfig.supportedVideoFormats |= VIDEO_FORMAT_H265_MAIN10;
}
break; break;
} }
@@ -783,12 +732,7 @@ bool Session::initialize()
} }
// Fall-through // Fall-through
case StreamingPreferences::WM_FULLSCREEN: case StreamingPreferences::WM_FULLSCREEN:
#ifdef Q_OS_DARWIN
// Don't use "real" fullscreen on macOS. See comments above.
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN_DESKTOP;
#else
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN; m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
#endif
break; break;
} }
@@ -870,13 +814,21 @@ bool Session::validateLaunch(SDL_Window* testWindow)
emitLaunchWarning(tr("Your host software or GPU doesn't support encoding AV1.")); emitLaunchWarning(tr("Your host software or GPU doesn't support encoding AV1."));
} }
// We'll try to fall back to HEVC first if AV1 fails. We'd rather not fall back
// straight to H.264 if the user asked for AV1 and the host doesn't support it.
if (m_StreamConfig.supportedVideoFormats & VIDEO_FORMAT_AV1_MAIN8) {
m_StreamConfig.supportedVideoFormats |= VIDEO_FORMAT_H265;
}
if (m_StreamConfig.supportedVideoFormats & VIDEO_FORMAT_AV1_MAIN10) {
m_StreamConfig.supportedVideoFormats |= VIDEO_FORMAT_H265_MAIN10;
}
// Moonlight-common-c will handle this case already, but we want // Moonlight-common-c will handle this case already, but we want
// to set this explicitly here so we can do our hardware acceleration // to set this explicitly here so we can do our hardware acceleration
// check below. // check below.
m_StreamConfig.supportedVideoFormats &= ~VIDEO_FORMAT_MASK_AV1; m_StreamConfig.supportedVideoFormats &= ~VIDEO_FORMAT_MASK_AV1;
} }
else if (!m_Preferences->enableHdr && // HDR is checked below else if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Auto VCC is already checked in initialize() m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Auto VCC is already checked in initialize()
!isHardwareDecodeAvailable(testWindow, !isHardwareDecodeAvailable(testWindow,
m_Preferences->videoDecoderSelection, m_Preferences->videoDecoderSelection,
@@ -899,8 +851,7 @@ bool Session::validateLaunch(SDL_Window* testWindow)
// check below. // check below.
m_StreamConfig.supportedVideoFormats &= ~VIDEO_FORMAT_MASK_H265; m_StreamConfig.supportedVideoFormats &= ~VIDEO_FORMAT_MASK_H265;
} }
else if (!m_Preferences->enableHdr && // HDR is checked below else if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Auto VCC is already checked in initialize() m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Auto VCC is already checked in initialize()
!isHardwareDecodeAvailable(testWindow, !isHardwareDecodeAvailable(testWindow,
m_Preferences->videoDecoderSelection, m_Preferences->videoDecoderSelection,
@@ -1011,7 +962,8 @@ bool Session::validateLaunch(SDL_Window* testWindow)
} }
// If nothing worked, warn the user that audio will not work // If nothing worked, warn the user that audio will not work
if (!audioTestPassed) { m_AudioDisabled = !audioTestPassed;
if (m_AudioDisabled) {
emitLaunchWarning(tr("Failed to open audio device. Audio will be unavailable during this session.")); emitLaunchWarning(tr("Failed to open audio device. Audio will be unavailable during this session."));
} }
@@ -1134,21 +1086,15 @@ void Session::getWindowDimensions(int& x, int& y,
// Create our window on the same display that Qt's UI // Create our window on the same display that Qt's UI
// was being displayed on. // was being displayed on.
else { else {
Q_ASSERT(m_QtWindow != nullptr);
if (m_QtWindow != nullptr) {
QScreen* screen = m_QtWindow->screen();
if (screen != nullptr) {
QRect displayRect = screen->geometry();
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Qt UI screen is at (%d,%d)", "Qt UI screen is at (%d,%d)",
displayRect.x(), displayRect.y()); m_DisplayOriginX, m_DisplayOriginY);
for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) { for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
SDL_Rect displayBounds; SDL_Rect displayBounds;
if (SDL_GetDisplayBounds(i, &displayBounds) == 0) { if (SDL_GetDisplayBounds(i, &displayBounds) == 0) {
if (displayBounds.x == displayRect.x() && if (displayBounds.x == m_DisplayOriginX &&
displayBounds.y == displayRect.y()) { displayBounds.y == m_DisplayOriginY) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"SDL found matching display %d", "SDL found matching display %d",
i); i);
@@ -1163,12 +1109,6 @@ void Session::getWindowDimensions(int& x, int& y,
} }
} }
} }
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Qt window is not associated with a QScreen!");
}
}
}
SDL_Rect usableBounds; SDL_Rect usableBounds;
if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) == 0) { if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) == 0) {
@@ -1219,8 +1159,7 @@ void Session::updateOptimalWindowDisplayMode()
// If this doesn't fit the selected resolution, use the native // If this doesn't fit the selected resolution, use the native
// resolution of the panel (unscaled). // resolution of the panel (unscaled).
if (desktopMode.w < m_ActiveVideoWidth || desktopMode.h < m_ActiveVideoHeight) { if (desktopMode.w < m_ActiveVideoWidth || desktopMode.h < m_ActiveVideoHeight) {
SDL_Rect safeArea; if (!StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode)) {
if (!StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode, &safeArea)) {
return; return;
} }
} }
@@ -1485,7 +1424,8 @@ bool Session::startConnectionAsync()
} }
int err = LiStartConnection(&hostInfo, &m_StreamConfig, &k_ConnCallbacks, int err = LiStartConnection(&hostInfo, &m_StreamConfig, &k_ConnCallbacks,
&m_VideoCallbacks, &m_AudioCallbacks, &m_VideoCallbacks,
m_AudioDisabled ? nullptr : &m_AudioCallbacks,
NULL, 0, NULL, 0); NULL, 0, NULL, 0);
if (err != 0) { if (err != 0) {
// We already displayed an error dialog in the stage failure // We already displayed an error dialog in the stage failure
@@ -1532,9 +1472,10 @@ public:
Session* m_Session; Session* m_Session;
}; };
void Session::exec(QWindow* qtWindow) void Session::exec(int displayOriginX, int displayOriginY)
{ {
m_QtWindow = qtWindow; m_DisplayOriginX = displayOriginX;
m_DisplayOriginY = displayOriginY;
// Use a separate thread for the streaming session on X11 or Wayland // Use a separate thread for the streaming session on X11 or Wayland
// to ensure we don't stomp on Qt's GL context. This breaks when using // to ensure we don't stomp on Qt's GL context. This breaks when using
@@ -1638,26 +1579,16 @@ void Session::execInternal()
// We always want a resizable window with High DPI enabled // We always want a resizable window with High DPI enabled
Uint32 defaultWindowFlags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE; Uint32 defaultWindowFlags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE;
// If we're starting in windowed mode and the Moonlight GUI is maximized or // If we're starting in windowed mode and the Moonlight GUI is maximized,
// minimized, match that with the streaming window. // match that with the streaming window.
if (!m_IsFullScreen && m_QtWindow != nullptr) { if (!m_IsFullScreen) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QWindowList windows = QGuiApplication::topLevelWindows();
// Qt 5.10+ can propagate multiple states together for (const QWindow* window : windows) {
if (m_QtWindow->windowStates() & Qt::WindowMaximized) { if (window->windowState() & Qt::WindowMaximized) {
defaultWindowFlags |= SDL_WINDOW_MAXIMIZED; defaultWindowFlags |= SDL_WINDOW_MAXIMIZED;
break;
} }
if (m_QtWindow->windowStates() & Qt::WindowMinimized) {
defaultWindowFlags |= SDL_WINDOW_MINIMIZED;
} }
#else
// Qt 5.9 only supports a single state at a time
if (m_QtWindow->windowState() == Qt::WindowMaximized) {
defaultWindowFlags |= SDL_WINDOW_MAXIMIZED;
}
else if (m_QtWindow->windowState() == Qt::WindowMinimized) {
defaultWindowFlags |= SDL_WINDOW_MINIMIZED;
}
#endif
} }
// We use only the computer name on macOS to match Apple conventions where the // We use only the computer name on macOS to match Apple conventions where the
@@ -1700,13 +1631,16 @@ void Session::execInternal()
// HACK: Remove once proper Dark Mode support lands in SDL // HACK: Remove once proper Dark Mode support lands in SDL
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
if (m_QtWindow != nullptr) { {
BOOL darkModeEnabled; BOOL darkModeEnabled = FALSE;
// Query whether dark mode is enabled for our Qt window (which tracks the OS dark mode state) // Query whether dark mode is enabled for our Qt window (which tracks the OS dark mode state)
if (FAILED(DwmGetWindowAttribute((HWND)m_QtWindow->winId(), DWMWA_USE_IMMERSIVE_DARK_MODE, &darkModeEnabled, sizeof(darkModeEnabled))) && QWindowList windows = QGuiApplication::topLevelWindows();
FAILED(DwmGetWindowAttribute((HWND)m_QtWindow->winId(), DWMWA_USE_IMMERSIVE_DARK_MODE_OLD, &darkModeEnabled, sizeof(darkModeEnabled)))) { for (const QWindow* window : windows) {
darkModeEnabled = FALSE; if (SUCCEEDED(DwmGetWindowAttribute((HWND)window->winId(), DWMWA_USE_IMMERSIVE_DARK_MODE, &darkModeEnabled, sizeof(darkModeEnabled))) ||
SUCCEEDED(DwmGetWindowAttribute((HWND)window->winId(), DWMWA_USE_IMMERSIVE_DARK_MODE_OLD, &darkModeEnabled, sizeof(darkModeEnabled)))) {
break;
}
} }
SDL_SysWMinfo info; SDL_SysWMinfo info;
@@ -1838,9 +1772,6 @@ void Session::execInternal()
// Start rich presence to indicate we're in game // Start rich presence to indicate we're in game
RichPresenceManager presence(*m_Preferences, m_App.name); RichPresenceManager presence(*m_Preferences, m_App.name);
// Toggle the stats overlay if requested by the user
m_OverlayManager.setOverlayState(Overlay::OverlayDebug, m_Preferences->showPerformanceOverlay);
// Hijack this thread to be the SDL main thread. We have to do this // Hijack this thread to be the SDL main thread. We have to do this
// because we want to suspend all Qt processing until the stream is over. // because we want to suspend all Qt processing until the stream is over.
SDL_Event event; SDL_Event event;
@@ -1984,52 +1915,6 @@ void Session::execInternal()
break; break;
} }
// Allow the renderer to handle the state change without being recreated
if (m_VideoDecoder) {
bool forceRecreation = false;
WINDOW_STATE_CHANGE_INFO windowChangeInfo = {};
windowChangeInfo.window = m_Window;
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
windowChangeInfo.stateChangeFlags |= WINDOW_STATE_CHANGE_SIZE;
windowChangeInfo.width = event.window.data1;
windowChangeInfo.height = event.window.data2;
}
int newDisplayIndex = SDL_GetWindowDisplayIndex(m_Window);
if (newDisplayIndex != currentDisplayIndex) {
windowChangeInfo.stateChangeFlags |= WINDOW_STATE_CHANGE_DISPLAY;
windowChangeInfo.displayIndex = newDisplayIndex;
// If the refresh rates have changed, we will need to go through the full
// decoder recreation path to ensure Pacer is switched to the new display
// and that we apply any V-Sync disablement rules that may be needed for
// this display.
SDL_DisplayMode oldMode, newMode;
if (SDL_GetCurrentDisplayMode(currentDisplayIndex, &oldMode) < 0 ||
SDL_GetCurrentDisplayMode(newDisplayIndex, &newMode) < 0 ||
oldMode.refresh_rate != newMode.refresh_rate) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Forcing renderer recreation due to refresh rate change between displays");
forceRecreation = true;
}
}
if (!forceRecreation && m_VideoDecoder->notifyWindowChanged(&windowChangeInfo)) {
// Update the window display mode based on our current monitor
// NB: Avoid a useless modeset by only doing this if it changed.
if (newDisplayIndex != currentDisplayIndex) {
currentDisplayIndex = newDisplayIndex;
updateOptimalWindowDisplayMode();
}
break;
}
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Recreating renderer for window event: %d (%d %d)", "Recreating renderer for window event: %d (%d %d)",
event.window.event, event.window.event,
@@ -2202,31 +2087,6 @@ DispatchDeferredCleanup:
m_VideoDecoder = nullptr; m_VideoDecoder = nullptr;
SDL_AtomicUnlock(&m_DecoderLock); SDL_AtomicUnlock(&m_DecoderLock);
// Propagate state changes from the SDL window back to the Qt window
//
// NB: We're making a conscious decision not to propagate the maximized
// or normal state of the window here. The thinking is that users may
// routinely maximize the streaming window simply to view the stream
// in a larger window, but they don't necessarily want the UI in such
// a large window.
if (!m_IsFullScreen && m_QtWindow != nullptr && m_Window != nullptr) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
if (SDL_GetWindowFlags(m_Window) & SDL_WINDOW_MINIMIZED) {
m_QtWindow->setWindowStates(m_QtWindow->windowStates() | Qt::WindowMinimized);
}
else if (m_QtWindow->windowStates() & Qt::WindowMinimized) {
m_QtWindow->setWindowStates(m_QtWindow->windowStates() & ~Qt::WindowMinimized);
}
#else
if (SDL_GetWindowFlags(m_Window) & SDL_WINDOW_MINIMIZED) {
m_QtWindow->setWindowState(Qt::WindowMinimized);
}
else if (m_QtWindow->windowState() & Qt::WindowMinimized) {
m_QtWindow->setWindowState(Qt::WindowNoState);
}
#endif
}
// This must be called after the decoder is deleted, because // This must be called after the decoder is deleted, because
// the renderer may want to interact with the window // the renderer may want to interact with the window
SDL_DestroyWindow(m_Window); SDL_DestroyWindow(m_Window);
+4 -7
View File
@@ -1,7 +1,6 @@
#pragma once #pragma once
#include <QSemaphore> #include <QSemaphore>
#include <QWindow>
#include <Limelight.h> #include <Limelight.h>
#include <opus_multistream.h> #include <opus_multistream.h>
@@ -27,7 +26,7 @@ public:
// Use Session::exec() or DeferredSessionCleanupTask instead. // Use Session::exec() or DeferredSessionCleanupTask instead.
virtual ~Session() {}; virtual ~Session() {};
Q_INVOKABLE void exec(QWindow* qtWindow); Q_INVOKABLE void exec(int displayOriginX, int displayOriginY);
static static
void getDecoderInfo(SDL_Window* window, void getDecoderInfo(SDL_Window* window,
@@ -79,8 +78,6 @@ private:
IAudioRenderer* createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATION opusConfig); IAudioRenderer* createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATION opusConfig);
bool initializeAudioRenderer();
bool testAudio(int audioConfiguration); bool testAudio(int audioConfiguration);
int getAudioRendererCapabilities(int audioConfiguration); int getAudioRendererCapabilities(int audioConfiguration);
@@ -169,7 +166,8 @@ private:
bool m_AudioDisabled; bool m_AudioDisabled;
bool m_AudioMuted; bool m_AudioMuted;
Uint32 m_FullScreenFlag; Uint32 m_FullScreenFlag;
QWindow* m_QtWindow; int m_DisplayOriginX;
int m_DisplayOriginY;
bool m_ThreadedExec; bool m_ThreadedExec;
bool m_UnexpectedTermination; bool m_UnexpectedTermination;
SdlInputHandler* m_InputHandler; SdlInputHandler* m_InputHandler;
@@ -186,8 +184,7 @@ private:
OpusMSDecoder* m_OpusDecoder; OpusMSDecoder* m_OpusDecoder;
IAudioRenderer* m_AudioRenderer; IAudioRenderer* m_AudioRenderer;
OPUS_MULTISTREAM_CONFIGURATION m_ActiveAudioConfig; OPUS_MULTISTREAM_CONFIGURATION m_AudioConfig;
OPUS_MULTISTREAM_CONFIGURATION m_OriginalAudioConfig;
int m_AudioSampleCount; int m_AudioSampleCount;
Uint32 m_DropAudioEndTime; Uint32 m_DropAudioEndTime;
+5 -150
View File
@@ -6,67 +6,10 @@
#include <ApplicationServices/ApplicationServices.h> #include <ApplicationServices/ApplicationServices.h>
#endif #endif
#ifdef Q_OS_WINDOWS
#include <Windows.h>
#endif
#ifdef Q_OS_LINUX
#include <sys/auxv.h>
#if defined(Q_PROCESSOR_ARM)
#ifndef HWCAP2_AES
#define HWCAP2_AES (1 << 0)
#endif
#elif defined(Q_PROCESSOR_RISCV)
#if __has_include(<sys/hwprobe.h>)
#include <sys/hwprobe.h>
#else
#include <unistd.h>
#if __has_include(<asm/hwprobe.h>)
#include <asm/hwprobe.h>
#include <sys/syscall.h>
#else
#define __NR_riscv_hwprobe 258
struct riscv_hwprobe {
int64_t key;
uint64_t value;
};
#define RISCV_HWPROBE_KEY_IMA_EXT_0 4
#endif
// RISC-V Scalar AES [E]ncryption and [D]ecryption
#ifndef RISCV_HWPROBE_EXT_ZKND
#define RISCV_HWPROBE_EXT_ZKND (1 << 11)
#define RISCV_HWPROBE_EXT_ZKNE (1 << 12)
#endif
// RISC-V Vector AES
#ifndef RISCV_HWPROBE_EXT_ZVKNED
#define RISCV_HWPROBE_EXT_ZVKNED (1 << 21)
#endif
static int __riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
size_t cpu_count, unsigned long *cpus,
unsigned int flags)
{
return syscall(__NR_riscv_hwprobe, pairs, pair_count, cpu_count, cpus, flags);
}
#endif
#endif
#endif
Uint32 StreamUtils::getPlatformWindowFlags() Uint32 StreamUtils::getPlatformWindowFlags()
{ {
#if defined(Q_OS_DARWIN) #ifdef Q_OS_DARWIN
return SDL_WINDOW_METAL; return SDL_WINDOW_METAL;
#elif defined(HAVE_LIBPLACEBO_VULKAN)
// We'll fall back to GL if Vulkan fails
return SDL_WINDOW_VULKAN;
#else #else
return 0; return 0;
#endif #endif
@@ -149,51 +92,7 @@ int StreamUtils::getDisplayRefreshRate(SDL_Window* window)
return mode.refresh_rate; return mode.refresh_rate;
} }
bool StreamUtils::hasFastAes() bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode)
{
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#if (__has_builtin(__builtin_cpu_supports) || (defined(__GNUC__) && __GNUC__ >= 6)) && defined(Q_PROCESSOR_X86)
return __builtin_cpu_supports("aes");
#elif defined(__BUILTIN_CPU_SUPPORTS__) && defined(Q_PROCESSOR_POWER)
return __builtin_cpu_supports("vcrypto");
#elif defined(Q_OS_WINDOWS) && defined(Q_PROCESSOR_ARM)
return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE);
#elif defined(_MSC_VER) && defined(Q_PROCESSOR_X86)
int regs[4];
__cpuid(regs, 1);
return regs[2] & (1 << 25); // AES-NI
#elif defined(Q_OS_DARWIN)
// Everything that runs Catalina and later has AES-NI or ARMv8 crypto instructions
return true;
#elif defined(Q_OS_LINUX) && defined(Q_PROCESSOR_ARM) && QT_POINTER_SIZE == 4
return getauxval(AT_HWCAP2) & HWCAP2_AES;
#elif defined(Q_OS_LINUX) && defined(Q_PROCESSOR_ARM) && QT_POINTER_SIZE == 8
return getauxval(AT_HWCAP) & HWCAP_AES;
#elif defined(Q_PROCESSOR_RISCV)
riscv_hwprobe pairs[1] = {
{ RISCV_HWPROBE_KEY_IMA_EXT_0, 0 },
};
// If this syscall is not implemented, we'll get -ENOSYS
// and the value field will remain zero.
__riscv_hwprobe(pairs, SDL_arraysize(pairs), 0, nullptr, 0);
return (pairs[0].value & (RISCV_HWPROBE_EXT_ZKNE | RISCV_HWPROBE_EXT_ZKND)) ==
(RISCV_HWPROBE_EXT_ZKNE | RISCV_HWPROBE_EXT_ZKND) ||
(pairs[0].value & RISCV_HWPROBE_EXT_ZVKNED);
#elif QT_POINTER_SIZE == 4
#warning Unknown 32-bit platform. Assuming AES is slow on this CPU.
return false;
#else
#warning Unknown 64-bit platform. Assuming AES is fast on this CPU.
return true;
#endif
}
bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode, SDL_Rect* safeArea)
{ {
#ifdef Q_OS_DARWIN #ifdef Q_OS_DARWIN
#define MAX_DISPLAYS 16 #define MAX_DISPLAYS 16
@@ -201,6 +100,9 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode,
uint32_t displayCount = 0; uint32_t displayCount = 0;
CGGetActiveDisplayList(MAX_DISPLAYS, displayIds, &displayCount); CGGetActiveDisplayList(MAX_DISPLAYS, displayIds, &displayCount);
if (displayIndex >= displayCount) { if (displayIndex >= displayCount) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Too many displays: %d vs %d",
displayIndex, displayCount);
return false; return false;
} }
@@ -220,42 +122,8 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode,
break; break;
} }
} }
safeArea->x = 0;
safeArea->y = 0;
safeArea->w = mode->w;
safeArea->h = mode->h;
#if TARGET_CPU_ARM64
// Now that we found the native full-screen mode, let's look for one that matches along
// the width but not the height and we'll assume that's the safe area full-screen mode.
//
// There doesn't appear to be a CG API or flag that will tell us that a given mode
// is a "safe area" mode, so we have to use our own (brittle) heuristics. :(
//
// To avoid potential false positives, let's avoid checking for external displays, since
// we might have scenarios like a 1920x1200 display with an alternate 1920x1080 mode
// which would falsely trigger our notch detection here.
if (CGDisplayIsBuiltin(displayIds[displayIndex])) {
for (CFIndex i = 0; i < count; i++) {
auto cgMode = (CGDisplayModeRef)(CFArrayGetValueAtIndex(modeList, i));
auto cgModeWidth = static_cast<int>(CGDisplayModeGetWidth(cgMode));
auto cgModeHeight = static_cast<int>(CGDisplayModeGetHeight(cgMode));
// If the modes differ by more than 100, we'll assume it's not a notch mode
if (mode->w == cgModeWidth && mode->h != cgModeHeight && mode->h <= cgModeHeight + 100) {
safeArea->w = cgModeWidth;
safeArea->h = cgModeHeight;
}
}
}
#endif
CFRelease(modeList); CFRelease(modeList);
// Special case for probing for notched displays prior to video subsystem initialization
// in Session::initialize() for Darwin only!
if (SDL_WasInit(SDL_INIT_VIDEO)) {
// Now find the SDL mode that matches the CG native mode // Now find the SDL mode that matches the CG native mode
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) { for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
SDL_DisplayMode thisMode; SDL_DisplayMode thisMode;
@@ -267,14 +135,7 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode,
} }
} }
} }
}
#else #else
SDL_assert(SDL_WasInit(SDL_INIT_VIDEO));
if (displayIndex >= SDL_GetNumVideoDisplays()) {
return false;
}
// We need to get the true display resolution without DPI scaling (since we use High DPI). // We need to get the true display resolution without DPI scaling (since we use High DPI).
// Windows returns the real display resolution here, even if DPI scaling is enabled. // Windows returns the real display resolution here, even if DPI scaling is enabled.
// macOS and Wayland report a resolution that includes the DPI scaling factor. Picking // macOS and Wayland report a resolution that includes the DPI scaling factor. Picking
@@ -296,13 +157,7 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode,
return false; return false;
} }
} }
safeArea->x = 0;
safeArea->y = 0;
safeArea->w = mode->w;
safeArea->h = mode->h;
#endif #endif
return true; return true;
} }
+1 -4
View File
@@ -29,11 +29,8 @@ public:
void screenSpaceToNormalizedDeviceCoords(SDL_Rect* src, SDL_FRect* dst, int viewportWidth, int viewportHeight); void screenSpaceToNormalizedDeviceCoords(SDL_Rect* src, SDL_FRect* dst, int viewportWidth, int viewportHeight);
static static
bool getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode, SDL_Rect* safeArea); bool getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode);
static static
int getDisplayRefreshRate(SDL_Window* window); int getDisplayRefreshRate(SDL_Window* window);
static
bool hasFastAes();
}; };
-16
View File
@@ -45,21 +45,6 @@ typedef struct _DECODER_PARAMETERS {
bool testOnly; bool testOnly;
} DECODER_PARAMETERS, *PDECODER_PARAMETERS; } DECODER_PARAMETERS, *PDECODER_PARAMETERS;
#define WINDOW_STATE_CHANGE_SIZE 0x01
#define WINDOW_STATE_CHANGE_DISPLAY 0x02
typedef struct _WINDOW_STATE_CHANGE_INFO {
SDL_Window* window;
uint32_t stateChangeFlags;
// Populated if WINDOW_STATE_CHANGE_SIZE is set
int width;
int height;
// Populated if WINDOW_STATE_CHANGE_DISPLAY is set
int displayIndex;
} WINDOW_STATE_CHANGE_INFO, *PWINDOW_STATE_CHANGE_INFO;
class IVideoDecoder { class IVideoDecoder {
public: public:
virtual ~IVideoDecoder() {} virtual ~IVideoDecoder() {}
@@ -74,5 +59,4 @@ public:
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0; virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
virtual void renderFrameOnMainThread() = 0; virtual void renderFrameOnMainThread() = 0;
virtual void setHdrMode(bool enabled) = 0; virtual void setHdrMode(bool enabled) = 0;
virtual bool notifyWindowChanged(PWINDOW_STATE_CHANGE_INFO info) = 0;
}; };
+129 -67
View File
@@ -75,8 +75,6 @@ static_assert(sizeof(CSC_CONST_BUF) % 16 == 0, "Constant buffer sizes must be a
D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass) D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
: m_DecoderSelectionPass(decoderSelectionPass), : m_DecoderSelectionPass(decoderSelectionPass),
m_DevicesWithFL11Support(0),
m_DevicesWithCodecSupport(0),
m_Factory(nullptr), m_Factory(nullptr),
m_Device(nullptr), m_Device(nullptr),
m_SwapChain(nullptr), m_SwapChain(nullptr),
@@ -84,7 +82,6 @@ D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
m_RenderTargetView(nullptr), m_RenderTargetView(nullptr),
m_LastColorSpace(-1), m_LastColorSpace(-1),
m_LastFullRange(false), m_LastFullRange(false),
m_LastColorTrc(AVCOL_TRC_UNSPECIFIED),
m_AllowTearing(false), m_AllowTearing(false),
m_VideoGenericPixelShader(nullptr), m_VideoGenericPixelShader(nullptr),
m_VideoBt601LimPixelShader(nullptr), m_VideoBt601LimPixelShader(nullptr),
@@ -93,7 +90,8 @@ D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
m_VideoTexture(nullptr), m_VideoTexture(nullptr),
m_OverlayLock(0), m_OverlayLock(0),
m_OverlayPixelShader(nullptr), m_OverlayPixelShader(nullptr),
m_HwDeviceContext(nullptr) m_HwDeviceContext(nullptr),
m_HwFramesContext(nullptr)
{ {
RtlZeroMemory(m_OverlayVertexBuffers, sizeof(m_OverlayVertexBuffers)); RtlZeroMemory(m_OverlayVertexBuffers, sizeof(m_OverlayVertexBuffers));
RtlZeroMemory(m_OverlayTextures, sizeof(m_OverlayTextures)); RtlZeroMemory(m_OverlayTextures, sizeof(m_OverlayTextures));
@@ -139,6 +137,10 @@ D3D11VARenderer::~D3D11VARenderer()
SAFE_COM_RELEASE(m_RenderTargetView); SAFE_COM_RELEASE(m_RenderTargetView);
SAFE_COM_RELEASE(m_SwapChain); SAFE_COM_RELEASE(m_SwapChain);
if (m_HwFramesContext != nullptr) {
av_buffer_unref(&m_HwFramesContext);
}
// Force destruction of the swapchain immediately // Force destruction of the swapchain immediately
if (m_DeviceContext != nullptr) { if (m_DeviceContext != nullptr) {
m_DeviceContext->ClearState(); m_DeviceContext->ClearState();
@@ -162,7 +164,6 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter
bool success = false; bool success = false;
IDXGIAdapter1* adapter = nullptr; IDXGIAdapter1* adapter = nullptr;
DXGI_ADAPTER_DESC1 adapterDesc; DXGI_ADAPTER_DESC1 adapterDesc;
D3D_FEATURE_LEVEL featureLevel;
HRESULT hr; HRESULT hr;
SDL_assert(m_Device == nullptr); SDL_assert(m_Device == nullptr);
@@ -212,7 +213,7 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter
0, 0,
D3D11_SDK_VERSION, D3D11_SDK_VERSION,
&m_Device, &m_Device,
&featureLevel, nullptr,
&m_DeviceContext); &m_DeviceContext);
if (FAILED(hr)) { if (FAILED(hr)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@@ -220,11 +221,6 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter
hr); hr);
goto Exit; goto Exit;
} }
else if (featureLevel >= D3D_FEATURE_LEVEL_11_0) {
// Remember that we found a non-software D3D11 devices with support for
// feature level 11.0 or later (Fermi, Terascale 2, or Ivy Bridge and later)
m_DevicesWithFL11Support++;
}
if (!checkDecoderSupport(adapter)) { if (!checkDecoderSupport(adapter)) {
m_DeviceContext->Release(); m_DeviceContext->Release();
@@ -234,10 +230,6 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter
goto Exit; goto Exit;
} }
else {
// Remember that we found a device with support for decoding this codec
m_DevicesWithCodecSupport++;
}
success = true; success = true;
@@ -429,6 +421,10 @@ bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
return false; return false;
} }
// Surfaces must be 16 pixel aligned for H.264 and 128 pixel aligned for everything else
// https://github.com/FFmpeg/FFmpeg/blob/a234e5cd80224c95a205c1f3e297d8c04a1374c3/libavcodec/dxva2.c#L609-L616
m_TextureAlignment = (params->videoFormat & VIDEO_FORMAT_MASK_H264) ? 16 : 128;
if (!setupRenderingResources()) { if (!setupRenderingResources()) {
return false; return false;
} }
@@ -462,10 +458,44 @@ bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
} }
} }
{
m_HwFramesContext = av_hwframe_ctx_alloc(m_HwDeviceContext);
if (!m_HwFramesContext) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to allocate D3D11VA frame context");
return false;
}
AVHWFramesContext* framesContext = (AVHWFramesContext*)m_HwFramesContext->data;
// We require NV12 or P010 textures for our shader
framesContext->format = AV_PIX_FMT_D3D11;
framesContext->sw_format = (params->videoFormat & VIDEO_FORMAT_MASK_10BIT) ?
AV_PIX_FMT_P010 : AV_PIX_FMT_NV12;
framesContext->width = FFALIGN(params->width, m_TextureAlignment);
framesContext->height = FFALIGN(params->height, m_TextureAlignment);
// We can have up to 16 reference frames plus a working surface
framesContext->initial_pool_size = 17;
AVD3D11VAFramesContext* d3d11vaFramesContext = (AVD3D11VAFramesContext*)framesContext->hwctx;
d3d11vaFramesContext->BindFlags = D3D11_BIND_DECODER;
int err = av_hwframe_ctx_init(m_HwFramesContext);
if (err < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to initialize D3D11VA frame context: %d",
err);
return false;
}
// Create our video texture and SRVs // Create our video texture and SRVs
if (!setupVideoTexture()) { if (!setupVideoTexture()) {
return false; return false;
} }
}
return true; return true;
} }
@@ -480,6 +510,87 @@ bool D3D11VARenderer::prepareDecoderContext(AVCodecContext* context, AVDictionar
return true; return true;
} }
bool D3D11VARenderer::prepareDecoderContextInGetFormat(AVCodecContext *context, AVPixelFormat)
{
// hw_frames_ctx must be initialized in ffGetFormat().
context->hw_frames_ctx = av_buffer_ref(m_HwFramesContext);
return true;
}
void D3D11VARenderer::setHdrMode(bool enabled)
{
HRESULT hr;
// According to MSDN, we need to lock the context even if we're just using DXGI functions
// https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-render-multi-thread-intro
lockContext(this);
if (enabled) {
DXGI_HDR_METADATA_HDR10 hdr10Metadata;
SS_HDR_METADATA sunshineHdrMetadata;
// Sunshine will have HDR metadata but GFE will not
if (!LiGetHdrMetadata(&sunshineHdrMetadata)) {
RtlZeroMemory(&sunshineHdrMetadata, sizeof(sunshineHdrMetadata));
}
hdr10Metadata.RedPrimary[0] = sunshineHdrMetadata.displayPrimaries[0].x;
hdr10Metadata.RedPrimary[1] = sunshineHdrMetadata.displayPrimaries[0].y;
hdr10Metadata.GreenPrimary[0] = sunshineHdrMetadata.displayPrimaries[1].x;
hdr10Metadata.GreenPrimary[1] = sunshineHdrMetadata.displayPrimaries[1].y;
hdr10Metadata.BluePrimary[0] = sunshineHdrMetadata.displayPrimaries[2].x;
hdr10Metadata.BluePrimary[1] = sunshineHdrMetadata.displayPrimaries[2].y;
hdr10Metadata.WhitePoint[0] = sunshineHdrMetadata.whitePoint.x;
hdr10Metadata.WhitePoint[1] = sunshineHdrMetadata.whitePoint.y;
hdr10Metadata.MaxMasteringLuminance = sunshineHdrMetadata.maxDisplayLuminance;
hdr10Metadata.MinMasteringLuminance = sunshineHdrMetadata.minDisplayLuminance;
hdr10Metadata.MaxContentLightLevel = sunshineHdrMetadata.maxContentLightLevel;
hdr10Metadata.MaxFrameAverageLightLevel = sunshineHdrMetadata.maxFrameAverageLightLevel;
hr = m_SwapChain->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(hdr10Metadata), &hdr10Metadata);
if (SUCCEEDED(hr)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Set display HDR mode: enabled");
}
else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to enter HDR mode: %x",
hr);
}
// Switch to Rec 2020 PQ (SMPTE ST 2084) colorspace for HDR10 rendering
hr = m_SwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
if (FAILED(hr)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"IDXGISwapChain::SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) failed: %x",
hr);
}
}
else {
// Restore default sRGB colorspace
hr = m_SwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
if (FAILED(hr)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"IDXGISwapChain::SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) failed: %x",
hr);
}
hr = m_SwapChain->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_NONE, 0, nullptr);
if (SUCCEEDED(hr)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Set display HDR mode: disabled");
}
else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to exit HDR mode: %x",
hr);
}
}
unlockContext(this);
}
void D3D11VARenderer::renderFrame(AVFrame* frame) void D3D11VARenderer::renderFrame(AVFrame* frame)
{ {
// Acquire the context lock for rendering to prevent concurrent // Acquire the context lock for rendering to prevent concurrent
@@ -518,33 +629,8 @@ void D3D11VARenderer::renderFrame(AVFrame* frame)
flags = 0; flags = 0;
} }
HRESULT hr;
if (frame->color_trc != m_LastColorTrc) {
if (frame->color_trc == AVCOL_TRC_SMPTE2084) {
// Switch to Rec 2020 PQ (SMPTE ST 2084) colorspace for HDR10 rendering
hr = m_SwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
if (FAILED(hr)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"IDXGISwapChain::SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) failed: %x",
hr);
}
}
else {
// Restore default sRGB colorspace
hr = m_SwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
if (FAILED(hr)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"IDXGISwapChain::SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) failed: %x",
hr);
}
}
m_LastColorTrc = frame->color_trc;
}
// Present according to the decoder parameters // Present according to the decoder parameters
hr = m_SwapChain->Present(0, flags); HRESULT hr = m_SwapChain->Present(0, flags);
// Release the context lock // Release the context lock
unlockContext(this); unlockContext(this);
@@ -727,8 +813,7 @@ void D3D11VARenderer::notifyOverlayUpdated(Overlay::OverlayType type)
HRESULT hr; HRESULT hr;
SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type); SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type);
bool overlayEnabled = Session::get()->getOverlayManager().isOverlayEnabled(type); if (newSurface == nullptr && Session::get()->getOverlayManager().isOverlayEnabled(type)) {
if (newSurface == nullptr && overlayEnabled) {
// The overlay is enabled and there is no new surface. Leave the old texture alone. // The overlay is enabled and there is no new surface. Leave the old texture alone.
return; return;
} }
@@ -749,7 +834,7 @@ void D3D11VARenderer::notifyOverlayUpdated(Overlay::OverlayType type)
SAFE_COM_RELEASE(oldVertexBuffer); SAFE_COM_RELEASE(oldVertexBuffer);
// If the overlay is disabled, we're done // If the overlay is disabled, we're done
if (!overlayEnabled) { if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) {
SDL_FreeSurface(newSurface); SDL_FreeSurface(newSurface);
return; return;
} }
@@ -1009,29 +1094,6 @@ bool D3D11VARenderer::needsTestFrame()
return true; return true;
} }
IFFmpegRenderer::InitFailureReason D3D11VARenderer::getInitFailureReason()
{
// In the specific case where we found at least one D3D11 hardware device but none of the
// enumerated devices have support for the specified codec, tell the FFmpeg decoder not to
// bother trying other hwaccels. We don't want to try loading D3D9 if the device doesn't
// even have hardware support for the codec.
//
// NB: We use feature level 11.0 support as a gate here because we want to avoid returning
// this failure reason in cases where we might have an extremely old GPU with support for
// DXVA2 on D3D9 but not D3D11VA on D3D11. I'm unsure if any such drivers/hardware exists,
// but better be safe than sorry.
//
// NB2: We're also assuming that no GPU exists which lacks any D3D11 driver but has drivers
// for non-DX APIs like Vulkan. I believe this is a Windows Logo requirement so it should be
// safe to assume.
if (m_DevicesWithFL11Support != 0 && m_DevicesWithCodecSupport == 0) {
return InitFailureReason::NoHardwareSupport;
}
else {
return InitFailureReason::Unknown;
}
}
void D3D11VARenderer::lockContext(void *lock_ctx) void D3D11VARenderer::lockContext(void *lock_ctx)
{ {
auto me = (D3D11VARenderer*)lock_ctx; auto me = (D3D11VARenderer*)lock_ctx;
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "renderer.h" #include "renderer.h"
#include "pacer/pacer.h"
#include <d3d11_1.h> #include <d3d11_1.h>
#include <dxgi1_5.h> #include <dxgi1_5.h>
@@ -16,12 +17,13 @@ public:
virtual ~D3D11VARenderer() override; virtual ~D3D11VARenderer() override;
virtual bool initialize(PDECODER_PARAMETERS params) override; virtual bool initialize(PDECODER_PARAMETERS params) override;
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary**) override; virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary**) override;
virtual bool prepareDecoderContextInGetFormat(AVCodecContext* context, AVPixelFormat pixelFormat) override;
virtual void renderFrame(AVFrame* frame) override; virtual void renderFrame(AVFrame* frame) override;
virtual void notifyOverlayUpdated(Overlay::OverlayType) override; virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
virtual void setHdrMode(bool enabled) override;
virtual int getRendererAttributes() override; virtual int getRendererAttributes() override;
virtual int getDecoderCapabilities() override; virtual int getDecoderCapabilities() override;
virtual bool needsTestFrame() override; virtual bool needsTestFrame() override;
virtual InitFailureReason getInitFailureReason() override;
private: private:
static void lockContext(void* lock_ctx); static void lockContext(void* lock_ctx);
@@ -36,8 +38,6 @@ private:
bool createDeviceByAdapterIndex(int adapterIndex, bool* adapterNotFound = nullptr); bool createDeviceByAdapterIndex(int adapterIndex, bool* adapterNotFound = nullptr);
int m_DecoderSelectionPass; int m_DecoderSelectionPass;
int m_DevicesWithFL11Support;
int m_DevicesWithCodecSupport;
IDXGIFactory5* m_Factory; IDXGIFactory5* m_Factory;
ID3D11Device* m_Device; ID3D11Device* m_Device;
@@ -47,11 +47,11 @@ private:
SDL_mutex* m_ContextLock; SDL_mutex* m_ContextLock;
DECODER_PARAMETERS m_DecoderParams; DECODER_PARAMETERS m_DecoderParams;
int m_TextureAlignment;
int m_DisplayWidth; int m_DisplayWidth;
int m_DisplayHeight; int m_DisplayHeight;
int m_LastColorSpace; int m_LastColorSpace;
bool m_LastFullRange; bool m_LastFullRange;
AVColorTransferCharacteristic m_LastColorTrc;
bool m_AllowTearing; bool m_AllowTearing;
@@ -70,5 +70,6 @@ private:
ID3D11PixelShader* m_OverlayPixelShader; ID3D11PixelShader* m_OverlayPixelShader;
AVBufferRef* m_HwDeviceContext; AVBufferRef* m_HwDeviceContext;
AVBufferRef* m_HwFramesContext;
}; };
+3 -4
View File
@@ -4,7 +4,6 @@
#endif #endif
#include "drm.h" #include "drm.h"
#include "string.h"
extern "C" { extern "C" {
#include <libavutil/hwcontext_drm.h> #include <libavutil/hwcontext_drm.h>
@@ -12,6 +11,7 @@ extern "C" {
#include <libdrm/drm_fourcc.h> #include <libdrm/drm_fourcc.h>
#include <linux/dma-buf.h> #include <linux/dma-buf.h>
#include <sys/ioctl.h>
// Special Rockchip type // Special Rockchip type
#ifndef DRM_FORMAT_NA12 #ifndef DRM_FORMAT_NA12
@@ -158,7 +158,6 @@ bool DrmRenderer::prepareDecoderContext(AVCodecContext* context, AVDictionary**
{ {
// The out-of-tree LibreELEC patches use this option to control the type of the V4L2 // The out-of-tree LibreELEC patches use this option to control the type of the V4L2
// buffers that we get back. We only support NV12 buffers now. // buffers that we get back. We only support NV12 buffers now.
if(strstr(context->codec->name, "_v4l2") != NULL)
av_dict_set_int(options, "pixel_format", AV_PIX_FMT_NV12, 0); av_dict_set_int(options, "pixel_format", AV_PIX_FMT_NV12, 0);
// This option controls the pixel format for the h264_omx and hevc_omx decoders // This option controls the pixel format for the h264_omx and hevc_omx decoders
@@ -845,7 +844,7 @@ bool DrmRenderer::mapSoftwareFrame(AVFrame *frame, AVDRMFrameDescriptor *mappedF
// Prepare to write to the dumb buffer from the CPU // Prepare to write to the dumb buffer from the CPU
struct dma_buf_sync sync; struct dma_buf_sync sync;
sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE; sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE;
drmIoctl(drmFrame->primeFd, DMA_BUF_IOCTL_SYNC, &sync); ioctl(drmFrame->primeFd, DMA_BUF_IOCTL_SYNC, &sync);
int lastPlaneSize = 0; int lastPlaneSize = 0;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
@@ -899,7 +898,7 @@ bool DrmRenderer::mapSoftwareFrame(AVFrame *frame, AVDRMFrameDescriptor *mappedF
// End the CPU write to the dumb buffer // End the CPU write to the dumb buffer
sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE; sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE;
drmIoctl(drmFrame->primeFd, DMA_BUF_IOCTL_SYNC, &sync); ioctl(drmFrame->primeFd, DMA_BUF_IOCTL_SYNC, &sync);
} }
ret = true; ret = true;
+52 -8
View File
@@ -21,11 +21,6 @@
DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0xD2,0x5C,0x15,0xB3,0xD6); DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0xD2,0x5C,0x15,0xB3,0xD6);
DEFINE_GUID(DXVA2_ModeAV1_VLD_Profile0,0xb8be4ccb,0xcf53,0x46ba,0x8d,0x59,0xd6,0xb8,0xa6,0xda,0x5d,0x2a); DEFINE_GUID(DXVA2_ModeAV1_VLD_Profile0,0xb8be4ccb,0xcf53,0x46ba,0x8d,0x59,0xd6,0xb8,0xa6,0xda,0x5d,0x2a);
// This was incorrectly removed from public headers in FFmpeg 7.0
#ifndef FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO
#define FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO 2
#endif
#define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); } #define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); }
typedef struct _VERTEX typedef struct _VERTEX
@@ -452,6 +447,13 @@ bool DXVA2Renderer::initializeQuirksForAdapter(IDirect3D9Ex* d3d9ex, int adapter
// For other GPUs, we'll avoid populating it as was our previous behavior. // For other GPUs, we'll avoid populating it as was our previous behavior.
m_DeviceQuirks |= DXVA2_QUIRK_SET_DEST_FORMAT; m_DeviceQuirks |= DXVA2_QUIRK_SET_DEST_FORMAT;
} }
// Tag this display device if it has a WDDM 2.0+ driver for the decoder selection logic
if (HIWORD(id.DriverVersion.HighPart) >= 20) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected WDDM 2.0 or later display driver");
m_DeviceQuirks |= DXVA2_QUIRK_WDDM_20_PLUS;
}
} }
return true; return true;
@@ -566,6 +568,49 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
return false; return false;
} }
// If we have a WDDM 2.0 or later display driver, prefer the D3D11VA renderer
// in all of the following cases:
// - Multi-GPU systems
// - Windowed and borderless windowed modes
// - Full-screen exclusive with V-sync off
//
// D3D11VA is better in this case because it can enable tearing in non-FSE
// modes when the user has V-Sync disabled. In non-FSE V-Sync cases, D3D11VA
// provides lower display latency on systems that support Independent Flip
// in windowed mode. When using D3D9, DWM will not promote us to IFlip unless
// we're full-screen (exclusive or not).
//
// We prefer D3D11VA in FSE multi-GPU cases due to a plethora of issues with
// D3D9Ex PresentEx()/D3DPRESENT_DONOTWAIT on hybrid graphics systems (See
// issues #235, #240, #386, and #951 on GitHub). Clearly this codepath is not
// well tested by Microsoft or GPU vendors, so stick to the more common
// D3D11-based renderer which is much more likely to behave.
//
// NB: The reason we only do this for WDDM 2.0 and later is because older
// AMD drivers (such as those for the HD 5570) render garbage when using
// the D3D11VA renderer.
if (m_DecoderSelectionPass == 0 &&
(m_DeviceQuirks & DXVA2_QUIRK_WDDM_20_PLUS)) {
if (!((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Defaulting to D3D11VA for non-FSE mode");
d3d9ex->Release();
return false;
}
else if (m_DeviceQuirks & DXVA2_QUIRK_MULTI_GPU) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Defaulting to D3D11VA for multi-GPU FSE mode");
d3d9ex->Release();
return false;
}
else if (!enableVsync) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Defaulting to D3D11VA for FSE V-Sync Off mode");
d3d9ex->Release();
return false;
}
}
D3DCAPS9 deviceCaps; D3DCAPS9 deviceCaps;
d3d9ex->GetDeviceCaps(adapterIndex, D3DDEVTYPE_HAL, &deviceCaps); d3d9ex->GetDeviceCaps(adapterIndex, D3DDEVTYPE_HAL, &deviceCaps);
@@ -799,8 +844,7 @@ void DXVA2Renderer::notifyOverlayUpdated(Overlay::OverlayType type)
HRESULT hr; HRESULT hr;
SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type); SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type);
bool overlayEnabled = Session::get()->getOverlayManager().isOverlayEnabled(type); if (newSurface == nullptr && Session::get()->getOverlayManager().isOverlayEnabled(type)) {
if (newSurface == nullptr && overlayEnabled) {
// The overlay is enabled and there is no new surface. Leave the old texture alone. // The overlay is enabled and there is no new surface. Leave the old texture alone.
return; return;
} }
@@ -817,7 +861,7 @@ void DXVA2Renderer::notifyOverlayUpdated(Overlay::OverlayType type)
SAFE_COM_RELEASE(oldVertexBuffer); SAFE_COM_RELEASE(oldVertexBuffer);
// If the overlay is disabled, we're done // If the overlay is disabled, we're done
if (!overlayEnabled) { if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) {
SDL_FreeSurface(newSurface); SDL_FreeSurface(newSurface);
return; return;
} }
+1 -1
View File
@@ -80,7 +80,7 @@ private:
#define DXVA2_QUIRK_NO_VP 0x01 #define DXVA2_QUIRK_NO_VP 0x01
#define DXVA2_QUIRK_SET_DEST_FORMAT 0x02 #define DXVA2_QUIRK_SET_DEST_FORMAT 0x02
#define DXVA2_QUIRK_WDDM_20_PLUS 0x04 // Unused #define DXVA2_QUIRK_WDDM_20_PLUS 0x04
#define DXVA2_QUIRK_MULTI_GPU 0x08 #define DXVA2_QUIRK_MULTI_GPU 0x08
int m_DeviceQuirks; int m_DeviceQuirks;
}; };
+22 -35
View File
@@ -168,12 +168,6 @@ void EGLRenderer::notifyOverlayUpdated(Overlay::OverlayType type)
} }
} }
bool EGLRenderer::notifyWindowChanged(PWINDOW_STATE_CHANGE_INFO info)
{
// We can transparently handle size and display changes
return !(info->stateChangeFlags & ~(WINDOW_STATE_CHANGE_SIZE | WINDOW_STATE_CHANGE_DISPLAY));
}
bool EGLRenderer::isPixelFormatSupported(int videoFormat, AVPixelFormat pixelFormat) bool EGLRenderer::isPixelFormatSupported(int videoFormat, AVPixelFormat pixelFormat)
{ {
// Pixel format support should be determined by the backend renderer // Pixel format support should be determined by the backend renderer
@@ -186,7 +180,7 @@ AVPixelFormat EGLRenderer::getPreferredPixelFormat(int videoFormat)
return m_Backend->getPreferredPixelFormat(videoFormat); return m_Backend->getPreferredPixelFormat(videoFormat);
} }
void EGLRenderer::renderOverlay(Overlay::OverlayType type, int viewportWidth, int viewportHeight) void EGLRenderer::renderOverlay(Overlay::OverlayType type)
{ {
// Do nothing if this overlay is disabled // Do nothing if this overlay is disabled
if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) { if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) {
@@ -240,7 +234,7 @@ void EGLRenderer::renderOverlay(Overlay::OverlayType type, int viewportWidth, in
else if (type == Overlay::OverlayDebug) { else if (type == Overlay::OverlayDebug) {
// Top left // Top left
overlayRect.x = 0; overlayRect.x = 0;
overlayRect.y = viewportHeight - newSurface->h; overlayRect.y = m_ViewportHeight - newSurface->h;
} else { } else {
SDL_assert(false); SDL_assert(false);
} }
@@ -251,7 +245,7 @@ void EGLRenderer::renderOverlay(Overlay::OverlayType type, int viewportWidth, in
SDL_FreeSurface(newSurface); SDL_FreeSurface(newSurface);
// Convert screen space to normalized device coordinates // Convert screen space to normalized device coordinates
StreamUtils::screenSpaceToNormalizedDeviceCoords(&overlayRect, viewportWidth, viewportHeight); StreamUtils::screenSpaceToNormalizedDeviceCoords(&overlayRect, m_ViewportWidth, m_ViewportHeight);
OVERLAY_VERTEX verts[] = OVERLAY_VERTEX verts[] =
{ {
@@ -458,19 +452,13 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
return false; return false;
} }
// This renderer doesn't support HDR, so pick a different one.
// HACK: This avoids a deadlock in SDL_CreateRenderer() if
// Vulkan was used before and SDL is trying to load EGL.
if (params->videoFormat & VIDEO_FORMAT_MASK_10BIT) {
EGL_LOG(Info, "EGL doesn't support HDR rendering");
return false;
}
// HACK: Work around bug where renderer will repeatedly fail with: // HACK: Work around bug where renderer will repeatedly fail with:
// SDL_CreateRenderer() failed: Could not create GLES window surface // SDL_CreateRenderer() failed: Could not create GLES window surface
// Don't retry if we've already failed to create a renderer for this // Don't retry if we've already failed to create a renderer for this
// window *unless* the format has changed from 10-bit to 8-bit. // window *unless* the format has changed from 10-bit to 8-bit.
if (m_Window == s_LastFailedWindow) { if (m_Window == s_LastFailedWindow &&
!!(params->videoFormat & VIDEO_FORMAT_MASK_10BIT) ==
!!(s_LastFailedVideoFormat & VIDEO_FORMAT_MASK_10BIT)) {
EGL_LOG(Error, "SDL_CreateRenderer() already failed on this window!"); EGL_LOG(Error, "SDL_CreateRenderer() already failed on this window!");
return false; return false;
} }
@@ -659,6 +647,21 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
m_eglClientWaitSync = nullptr; m_eglClientWaitSync = nullptr;
} }
/* Compute the video region size in order to keep the aspect ratio of the
* video stream.
*/
SDL_Rect src, dst;
src.x = src.y = dst.x = dst.y = 0;
src.w = params->width;
src.h = params->height;
SDL_GL_GetDrawableSize(m_Window, &dst.w, &dst.h);
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
glViewport(dst.x, dst.y, dst.w, dst.h);
m_ViewportWidth = dst.w;
m_ViewportHeight = dst.h;
// SDL always uses swap interval 0 under the hood on Wayland systems, // SDL always uses swap interval 0 under the hood on Wayland systems,
// because the compositor guarantees tear-free rendering. In this // because the compositor guarantees tear-free rendering. In this
// situation, swap interval > 0 behaves as a frame pacing option // situation, swap interval > 0 behaves as a frame pacing option
@@ -922,20 +925,6 @@ void EGLRenderer::renderFrame(AVFrame* frame)
} }
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
int drawableWidth, drawableHeight;
SDL_GL_GetDrawableSize(m_Window, &drawableWidth, &drawableHeight);
// Set the viewport to the size of the aspect-ratio-scaled video
SDL_Rect src, dst;
src.x = src.y = dst.x = dst.y = 0;
src.w = frame->width;
src.h = frame->height;
dst.w = drawableWidth;
dst.h = drawableHeight;
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
glViewport(dst.x, dst.y, dst.w, dst.h);
glUseProgram(m_ShaderProgram); glUseProgram(m_ShaderProgram);
m_glBindVertexArrayOES(m_VAO); m_glBindVertexArrayOES(m_VAO);
@@ -954,10 +943,8 @@ void EGLRenderer::renderFrame(AVFrame* frame)
m_glBindVertexArrayOES(0); m_glBindVertexArrayOES(0);
// Adjust the viewport to the whole window before rendering the overlays
glViewport(0, 0, drawableWidth, drawableHeight);
for (int i = 0; i < Overlay::OverlayMax; i++) { for (int i = 0; i < Overlay::OverlayMax; i++) {
renderOverlay((Overlay::OverlayType)i, drawableWidth, drawableHeight); renderOverlay((Overlay::OverlayType)i);
} }
SDL_GL_SwapWindow(m_Window); SDL_GL_SwapWindow(m_Window);
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff. Show more