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 5301 additions and 12690 deletions
+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]
- Moonlight Version: [e.g. v0.9.0]
- GPU: [e.g. Intel HD Graphics 520]
- Linux package type (if applicable): [e.g. Flatpak]
**Server PC details (please complete the following information)**
- OS: [e.g. Windows 10 1809]
- Sunshine or GeForce Experience version: [e.g. Sunshine v0.21.0]
- GPU: [e.g. AMD Radeon RX 7900 XT]
- GPU driver: [e.g. 24.1.1]
- GeForce Experience version: [e.g. 3.16.0.140]
- Nvidia GPU driver: [e.g. 417.35]
- Antivirus and firewall software: [e.g. Windows Defender and Windows Firewall]
**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.
+11
View File
@@ -158,6 +158,17 @@ private:
L"EZFRD32.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.
// 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.
* GCC or Clang
* 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`
* 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 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 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.
* 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
1. Fork us
+2
View File
@@ -31,6 +31,8 @@
</array>
<key>GCSupportsControllerUserInteraction</key>
<true/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.games</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
+5 -34
View File
@@ -120,13 +120,6 @@ unix:!macx {
CONFIG += cuda
}
}
!disable-libplacebo {
packagesExist(libplacebo) {
PKGCONFIG += libplacebo
CONFIG += libplacebo
}
}
}
!disable-wayland {
@@ -152,8 +145,8 @@ win32:!winrt {
CONFIG += soundio discord-rpc
}
macx {
LIBS += -lssl -lcrypto -lavcodec.61 -lavutil.59 -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 += -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
# For libsoundio
LIBS += -framework CoreAudio -framework AudioUnit
@@ -298,7 +291,7 @@ mmal {
# significantly better performance than EGL on the Pi. Setting
# 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.
allow-egl-with-mmal {
message(Allowing EGL usage with MMAL enabled)
@@ -329,16 +322,6 @@ cuda {
# ffnvcodec uses libdl in cuda_load_functions()/cuda_free_functions()
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 {
message(EGL renderer selected)
@@ -385,8 +368,7 @@ macx {
message(VideoToolbox renderer selected)
SOURCES += \
streaming/video/ffmpeg-renderers/vt_avsamplelayer.mm \
streaming/video/ffmpeg-renderers/vt_metal.mm
streaming/video/ffmpeg-renderers/vt.mm
HEADERS += \
streaming/video/ffmpeg-renderers/vt.h
@@ -414,16 +396,6 @@ glslow {
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 {
message(Wayland extensions enabled)
@@ -460,8 +432,7 @@ TRANSLATIONS += \
languages/qml_pt_BR.ts \
languages/qml_pl.ts \
languages/qml_cs.ts \
languages/qml_he.ts \
languages/qml_ckb.ts
languages/qml_he.ts
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
+9 -6
View File
@@ -1,7 +1,7 @@
#include "computermanager.h"
#include "boxartmanager.h"
#include "nvhttp.h"
#include "nvpairingmanager.h"
#include "settings/streamingpreferences.h"
#include <Limelight.h>
#include <QtEndian>
@@ -144,8 +144,8 @@ private:
NvComputer* m_Computer;
};
ComputerManager::ComputerManager(StreamingPreferences* prefs)
: m_Prefs(prefs),
ComputerManager::ComputerManager(QObject *parent)
: QObject(parent),
m_PollingRef(0),
m_MdnsBrowser(nullptr),
m_CompatFetcher(nullptr),
@@ -352,7 +352,9 @@ void ComputerManager::startPolling()
return;
}
if (m_Prefs->enableMdns) {
StreamingPreferences prefs;
if (prefs.enableMdns) {
// Start an MDNS query for GameStream hosts
m_MdnsServer.reset(new QMdnsEngine::Server());
m_MdnsBrowser = new QMdnsEngine::Browser(m_MdnsServer.data(), "_nvstream._tcp.local.");
@@ -782,9 +784,10 @@ private:
return serverInfo;
} catch (...) {
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
// isn't blocking Moonlight, so we can tell the user about it.
portTestResult = LiTestClientConnectivity("qt.conntest.moonlight-stream.org", 443,
+6 -20
View File
@@ -1,7 +1,7 @@
#pragma once
#include "nvcomputer.h"
#include "settings/streamingpreferences.h"
#include "nvpairingmanager.h"
#include "settings/compatfetcher.h"
#include <qmdnsengine/server.h>
@@ -72,15 +72,9 @@ private slots:
void handleResolvedTimeout()
{
if (m_Addresses.isEmpty()) {
if (m_Retries-- > 0) {
// Try again
qInfo() << "Resolving" << hostname() << "timed out. Retrying...";
resolve();
}
else {
qWarning() << "Giving up on resolving" << hostname() << "after repeated failures";
cleanup();
}
// Try again
qInfo() << "Resolving" << hostname() << "timed out. Retrying...";
resolve();
}
else {
Q_ASSERT(!m_Addresses.isEmpty());
@@ -98,7 +92,7 @@ signals:
void resolvedHost(MdnsPendingComputer*,QVector<QHostAddress>&);
private:
void cleanup()
void resolve()
{
// Delete our resolver, so we're guaranteed that nothing is referencing m_Server.
delete m_Resolver;
@@ -107,12 +101,6 @@ private:
// Now delete our strong reference that we held on behalf of m_Resolver.
// The server may be destroyed after we make this call.
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.
m_Server = m_ServerWeak.toStrongRef();
@@ -132,7 +120,6 @@ private:
QSharedPointer<QMdnsEngine::Server> m_Server;
QMdnsEngine::Resolver* m_Resolver;
QVector<QHostAddress> m_Addresses;
int m_Retries = 10;
};
class ComputerPollingEntry
@@ -218,7 +205,7 @@ class ComputerManager : public QObject
friend class DelayedFlushThread;
public:
explicit ComputerManager(StreamingPreferences* prefs);
explicit ComputerManager(QObject *parent = nullptr);
virtual ~ComputerManager();
@@ -270,7 +257,6 @@ private:
void startPollingComputer(NvComputer* computer);
StreamingPreferences* m_Prefs;
int m_PollingRef;
QReadWriteLock m_Lock;
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),
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);
connect(m_TimeoutTimer, &QTimer::timeout,
this, &ComputerSeeker::onTimeout);
+2 -16
View File
@@ -373,7 +373,6 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
s.connectToHost(copyOfActiveAddress.address(), copyOfActiveAddress.port());
if (s.waitForConnected(3000)) {
Q_ASSERT(!s.localAddress().isNull());
Q_ASSERT(!s.peerAddress().isNull());
for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) {
// Ensure the interface is up
@@ -407,19 +406,6 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
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)) {
// OpenVPN TAP interfaces have a MAC address starting with 00:FF on Windows
return ReachabilityType::RI_VPN;
@@ -435,9 +421,9 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
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);
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;
}
+1 -2
View File
@@ -223,8 +223,7 @@ NvHTTP::startApp(QString verb,
"&surroundAudioInfo="+QString::number(SURROUNDAUDIOINFO_FROM_AUDIO_CONFIGURATION(streamConfig->audioConfiguration))+
"&remoteControllersBitmap="+QString::number(gamepadMask)+
"&gcmap="+QString::number(gamepadMask)+
"&gcpersist="+QString::number(persistGameControllersOnDisconnect ? 1 : 0)+
LiGetLaunchUrlQueryParameters(),
"&gcpersist="+QString::number(persistGameControllersOnDisconnect ? 1 : 0),
LAUNCH_TIMEOUT_MS);
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 serverSecret = pairingSecret.left(16);
QByteArray serverSignature = pairingSecret.mid(16);
QByteArray serverSecret = QByteArray(pairingSecret.data(), 16);
QByteArray serverSignature = QByteArray(&pairingSecret.data()[16], 256);
if (!verifySignature(serverSecret,
serverSignature,
+1 -10
View File
@@ -74,7 +74,6 @@ SystemProperties::SystemProperties()
Q_ASSERT(!monitorRefreshRates.isEmpty());
Q_ASSERT(!monitorNativeResolutions.isEmpty());
Q_ASSERT(!monitorSafeAreaResolutions.isEmpty());
}
QRect SystemProperties::getNativeResolution(int displayIndex)
@@ -83,12 +82,6 @@ QRect SystemProperties::getNativeResolution(int 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)
{
// Returns 0 if out of bounds
@@ -206,12 +199,10 @@ void SystemProperties::refreshDisplaysInternal()
SDL_DisplayMode bestMode;
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
SDL_DisplayMode desktopMode;
SDL_Rect safeArea;
if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode, &safeArea)) {
if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode)) {
if (desktopMode.w <= 8192 && desktopMode.h <= 8192) {
monitorNativeResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
monitorSafeAreaResolutions.insert(displayIndex, QRect(0, 0, safeArea.w, safeArea.h));
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
-2
View File
@@ -30,7 +30,6 @@ public:
Q_INVOKABLE void refreshDisplays();
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
Q_INVOKABLE QRect getSafeAreaResolution(int displayIndex);
Q_INVOKABLE int getRefreshRate(int displayIndex);
signals:
@@ -53,7 +52,6 @@ private:
QString unmappedGamepads;
QSize maximumResolution;
QList<QRect> monitorNativeResolutions;
QList<QRect> monitorSafeAreaResolutions;
QList<int> monitorRefreshRates;
QString versionString;
bool supportsHdr;
-9
View File
@@ -316,7 +316,6 @@ StreamCommandLineParser::StreamCommandLineParser()
{"auto", StreamingPreferences::VCC_AUTO},
{"H.264", StreamingPreferences::VCC_FORCE_H264},
{"HEVC", StreamingPreferences::VCC_FORCE_HEVC},
{"AV1", StreamingPreferences::VCC_FORCE_AV1},
};
m_VideoDecoderMap = {
{"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("swap-gamepad-buttons", "swap A/B and X/Y gamepad buttons (Nintendo-style)");
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("video-codec", "video codec", m_VideoCodecMap.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
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
if (parser.isSet("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>
<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">
<description>
<p>Bugfixes:</p>
-4
View File
@@ -5,10 +5,6 @@
# pairing data will be lost each reboot.
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 -10 -p $(pidof PE_Single_CPU)
+2 -2
View File
@@ -256,7 +256,7 @@ CenteredGridView {
anchors.fill: parent
acceptedButtons: Qt.RightButton;
onClicked: {
parent.pressAndHold()
parent.onPressAndHold()
}
}
@@ -350,7 +350,7 @@ CenteredGridView {
function quitApp() {
var component = Qt.createComponent("QuitSegue.qml")
var params = {"appName": appName, "quitRunningAppFn": () => appModel.quitRunningApp()}
var params = {"appName": appName, "quitRunningAppFn": appModel.quitRunningApp}
if (segueToStream) {
// Store the session and app name if we're going to stream after
// successfully quitting the old app.
+1 -1
View File
@@ -85,7 +85,7 @@ Item {
function quitApp() {
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))
}
+1 -18
View File
@@ -6,7 +6,6 @@ import ComputerModel 1.0
import ComputerManager 1.0
import StreamingPreferences 1.0
import SystemProperties 1.0
import SdlGamepadKeyNavigation 1.0
CenteredGridView {
@@ -216,14 +215,6 @@ CenteredGridView {
deletePcDialog.open()
}
}
NavigableMenuItem {
parentMenu: pcContextMenu
text: qsTr("View Details")
onTriggered: {
showPcDetailsDialog.pcDetails = model.details
showPcDetailsDialog.open()
}
}
}
}
@@ -271,7 +262,7 @@ CenteredGridView {
anchors.fill: parent
acceptedButtons: Qt.RightButton;
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 {}
}
+51 -59
View File
@@ -133,45 +133,15 @@ Flickable {
AutoResizingComboBox {
property int lastIndexValue
function addDetectedResolution(friendlyNamePrefix, rect) {
var indexToAdd = 0
for (var j = 0; j < resolutionComboBox.count; j++) {
var existing_width = parseInt(resolutionListModel.get(j).video_width);
var existing_height = parseInt(resolutionListModel.get(j).video_height);
if (rect.width === existing_width && rect.height === existing_height) {
// Duplicate entry, skip
indexToAdd = -1
break
}
else if (rect.width * rect.height > existing_width * existing_height) {
// Candidate entrypoint after this entry
indexToAdd = j + 1
}
}
// Insert this display's resolution if it's not a duplicate
if (indexToAdd >= 0) {
resolutionListModel.insert(indexToAdd,
{
"text": friendlyNamePrefix+" ("+rect.width+"x"+rect.height+")",
"video_width": ""+rect.width,
"video_height": ""+rect.height,
"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
// Add native 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
@@ -179,8 +149,32 @@ Flickable {
break
}
addDetectedResolution(qsTr("Native"), screenRect)
addDetectedResolution(qsTr("Native (Excluding Notch)"), safeAreaRect)
var indexToAdd = 0
for (var j = 0; j < resolutionComboBox.count; j++) {
var existing_width = parseInt(resolutionListModel.get(j).video_width);
var existing_height = parseInt(resolutionListModel.get(j).video_height);
if (screenRect.width === existing_width && screenRect.height === existing_height) {
// Duplicate entry, skip
indexToAdd = -1
break
}
else if (screenRect.width * screenRect.height > existing_width * existing_height) {
// Candidate entrypoint after this entry
indexToAdd = j + 1
}
}
// Insert this display's resolution if it's not a duplicate
if (indexToAdd >= 0) {
resolutionListModel.insert(indexToAdd,
{
"text": "Native ("+screenRect.width+"x"+screenRect.height+")",
"video_width": ""+screenRect.width,
"video_height": ""+screenRect.height,
"is_custom": false
})
}
}
// Prune resolutions that are over the decoder's maximum
@@ -216,7 +210,7 @@ Flickable {
if (!index_set) {
// We did not find a match. This must be a custom resolution.
resolutionListModel.append({
"text": qsTr("Custom")+" ("+StreamingPreferences.width+"x"+StreamingPreferences.height+")",
"text": "Custom ("+StreamingPreferences.width+"x"+StreamingPreferences.height+")",
"video_width": ""+StreamingPreferences.width,
"video_height": ""+StreamingPreferences.height,
"is_custom": true
@@ -225,7 +219,7 @@ Flickable {
}
else {
resolutionListModel.append({
"text": qsTr("Custom"),
"text": "Custom",
"video_width": "",
"video_height": "",
"is_custom": true
@@ -695,7 +689,7 @@ Flickable {
Component.onCompleted: {
// Refresh the text after translations change
languageChanged.connect(valueChanged)
languageChanged.connect(onValueChanged)
}
}
@@ -1058,10 +1052,6 @@ Flickable {
text: "עִבְרִית" // Hebrew
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 : {
@@ -1359,7 +1349,15 @@ Flickable {
font.pointSize: 12
checked: StreamingPreferences.swapFaceButtons
onCheckedChanged: {
StreamingPreferences.swapFaceButtons = checked
// Check if the value changed (this is called on init too)
if (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
@@ -1623,6 +1621,10 @@ Flickable {
if (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
if (window.pollingActive) {
ComputerManager.stopPollingAsync()
@@ -1639,26 +1641,16 @@ Flickable {
font.pointSize: 12
checked: StreamingPreferences.detectNetworkBlocking
onCheckedChanged: {
StreamingPreferences.detectNetworkBlocking = checked
}
}
// This is called on init, so only do the work if we've
// actually changed the value.
if (StreamingPreferences.detectNetworkBlocking != checked) {
StreamingPreferences.detectNetworkBlocking = checked
CheckBox {
id: showPerformanceOverlay
width: parent.width
text: qsTr("Show performance stats while streaming")
font.pointSize: 12
checked: StreamingPreferences.showPerformanceOverlay
onCheckedChanged: {
StreamingPreferences.showPerformanceOverlay = checked
// We must save the updated preference to ensure
// ComputerManager can observe the change internally.
StreamingPreferences.save()
}
}
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()
// Run the streaming session to completion
session.exec(Window.window)
session.exec(Screen.virtualX, Screen.virtualY)
}
sourceComponent: Item {}
-41
View File
@@ -42,46 +42,6 @@ QVariant ComputerModel::data(const QModelIndex& index, int role) const
return computer->state == NvComputer::CS_UNKNOWN;
case ServerSupportedRole:
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:
return QVariant();
}
@@ -109,7 +69,6 @@ QHash<int, QByteArray> ComputerModel::roleNames() const
names[WakeableRole] = "wakeable";
names[StatusUnknownRole] = "statusUnknown";
names[ServerSupportedRole] = "serverSupported";
names[DetailsRole] = "details";
return names;
}
+1 -2
View File
@@ -15,8 +15,7 @@ class ComputerModel : public QAbstractListModel
BusyRole,
WakeableRole,
StatusUnknownRole,
ServerSupportedRole,
DetailsRole
ServerSupportedRole
};
public:
+46 -33
View File
@@ -19,48 +19,26 @@ ApplicationWindow {
property bool clearOnBack: false
id: window
visible: true
width: 1280
height: 600
// Override the background color to Material 2 colors for Qt 6.5+
// in order to improve contrast between GFE's placeholder box art
// and the background of the app grid.
Component.onCompleted: {
// Override the background color to Material 2 colors for Qt 6.5+
// in order to improve contrast between GFE's placeholder box art
// and the background of the app grid.
if (SystemProperties.usesMaterial3Theme) {
Material.background = "#303030"
}
}
// Show the window according to the user's preferences
visibility: {
if (SystemProperties.hasDesktopEnvironment) {
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) {
window.showMaximized()
}
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_FULLSCREEN) {
window.showFullScreen()
}
else {
window.show()
}
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_WINDOWED) return "Windowed"
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) return "Maximized"
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_FULLSCREEN) return "FullScreen"
} else {
window.showFullScreen()
}
// 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()
return "FullScreen"
}
}
@@ -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.
//
// 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 &&
qmltypeof(stackView.currentItem, "SettingsView")
iconSource: "qrc:/res/discord.svg"
iconSource: "qrc:/res/Discord-Logo-White.svg"
ToolTip.delay: 1000
ToolTip.timeout: 3000
+5 -4
View File
@@ -4,13 +4,13 @@
#include <QGuiApplication>
#include <QWindow>
#include "settings/streamingpreferences.h"
#include "settings/mappingmanager.h"
#define AXIS_NAVIGATION_REPEAT_DELAY 150
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation(StreamingPreferences* prefs)
: m_Prefs(prefs),
m_Enabled(false),
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation()
: m_Enabled(false),
m_UiNavMode(false),
m_FirstPoll(false),
m_LastAxisNavigationEventTime(0)
@@ -93,6 +93,7 @@ void SdlGamepadKeyNavigation::disable()
void SdlGamepadKeyNavigation::onPollingTimerFired()
{
SDL_Event event;
StreamingPreferences prefs;
// Discard any pending button events on the first poll to avoid picking up
// stale input data from the stream session (like the quit combo).
@@ -120,7 +121,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
QEvent::Type::KeyPress : QEvent::Type::KeyRelease;
// Swap face buttons if needed
if (m_Prefs->swapFaceButtons) {
if (prefs.swapFaceButtons) {
switch (event.cbutton.button) {
case SDL_CONTROLLER_BUTTON_A:
event.cbutton.button = SDL_CONTROLLER_BUTTON_B;
+1 -4
View File
@@ -5,14 +5,12 @@
#include <SDL.h>
#include "settings/streamingpreferences.h"
class SdlGamepadKeyNavigation : public QObject
{
Q_OBJECT
public:
SdlGamepadKeyNavigation(StreamingPreferences* prefs);
SdlGamepadKeyNavigation();
~SdlGamepadKeyNavigation();
@@ -31,7 +29,6 @@ private slots:
void onPollingTimerFired();
private:
StreamingPreferences* m_Prefs;
QTimer* m_PollingTimer;
QList<SDL_GameController*> m_Gamepads;
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
+37 -101
View File
@@ -10,8 +10,7 @@
#include <QFont>
#include <QCursor>
#include <QElapsedTimer>
#include <QTemporaryFile>
#include <QRegularExpression>
#include <QFile>
// Don't let SDL hook our main function, since Qt is already
// doing the same thing. This needs to be before any headers
@@ -45,24 +44,26 @@
#include "settings/streamingpreferences.h"
#include "gui/sdlgamepadkeynavigation.h"
#if defined(Q_OS_WIN32)
#define IS_UNSPECIFIED_HANDLE(x) ((x) == INVALID_HANDLE_VALUE || (x) == NULL)
// Log to file or console dynamically for Windows builds
#if !defined(QT_DEBUG) && defined(Q_OS_WIN32)
// Log to file for release Windows builds
#define USE_CUSTOM_LOGGER
#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)
// Log to file for release Mac builds
#define USE_CUSTOM_LOGGER
#define LOG_TO_FILE
#else
// Log to console for debug Mac builds
// For debug Windows and Mac builds, use default logger
#endif
#ifdef USE_CUSTOM_LOGGER
static QElapsedTimer s_LoggerTime;
static QTextStream s_LoggerStream(stderr);
static QTextStream s_LoggerStream(stdout);
static QMutex s_LoggerLock;
static bool s_SuppressVerboseOutput;
static QRegularExpression k_RikeyRegex("&rikey=\\w+");
static QRegularExpression k_RikeyIdRegex("&rikeyid=[\\d-]+");
#ifdef LOG_TO_FILE
// Max log file size of 10 MB
#define MAX_LOG_SIZE_BYTES (10 * 1024 * 1024)
@@ -75,10 +76,6 @@ void logToLoggerStream(QString& message)
{
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
if (s_LogLimitReached) {
return;
@@ -219,6 +216,8 @@ void ffmpegLogToDiskHandler(void* ptr, int level, const char* fmt, va_list vl)
#endif
#endif
#ifdef Q_OS_WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
@@ -307,25 +306,13 @@ int main(int argc, char *argv[])
qputenv("QML_DISK_CACHE_PATH", Path::getQmlCacheDir().toUtf8());
}
#ifdef Q_OS_WIN32
// 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 USE_CUSTOM_LOGGER
#ifdef LOG_TO_FILE
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())));
if (s_LoggerFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream(stderr) << "Redirecting log output to " << s_LoggerFile->fileName() << Qt::endl;
s_LoggerStream.setDevice(s_LoggerFile);
}
s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch())));
if (s_LoggerFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream(stderr) << "Redirecting log output to " << s_LoggerFile->fileName() << Qt::endl;
s_LoggerStream.setDevice(s_LoggerFile);
}
#endif
@@ -337,6 +324,8 @@ int main(int argc, char *argv[])
av_log_set_callback(ffmpegLogToDiskHandler);
#endif
#endif
#ifdef Q_OS_WIN32
// Create a crash dump when we crash on Windows
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
@@ -362,11 +351,6 @@ int main(int argc, char *argv[])
SSL_free(nullptr);
#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.
// 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() << "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
@@ -420,18 +393,9 @@ int main(int argc, char *argv[])
#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
// password prompts on macOS.
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
#endif
#if defined(Q_OS_WIN32) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
if (!qEnvironmentVariableIsSet("QT_OPENGL")) {
@@ -454,12 +418,6 @@ int main(int argc, char *argv[])
qputenv("QSG_RENDER_LOOP", "basic");
#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
QNetworkProxyFactory::setUseSystemConfiguration(false);
@@ -538,13 +496,6 @@ int main(int argc, char *argv[])
// SDL doing it for us behind our backs.
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
// 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
@@ -554,34 +505,25 @@ int main(int argc, char *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::ParseResult commandLineParserResult = parser.parse(app.arguments());
switch (commandLineParserResult) {
case GlobalCommandLineParser::ListRequested:
#ifdef USE_CUSTOM_LOGGER
// Don't log to the console since it will jumble the command output
s_SuppressVerboseOutput = true;
#endif
#ifdef Q_OS_WIN32
// If we don't have stdout or stderr handles (which will normally be the case
// since we're a /SUBSYSTEM:WINDOWS app), attach to our parent console and use
// 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.
// Attach to the console to be able to print output.
// Since we're a /SUBSYSTEM:WINDOWS app, we won't be attached by default.
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
// If we didn't have an old stdout/stderr handle, use the new CONOUT$ handle
if (IS_UNSPECIFIED_HANDLE(oldConOut)) {
HANDLE conOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (conOut != INVALID_HANDLE_VALUE && conOut != NULL) {
freopen("CONOUT$", "w", stdout);
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);
setvbuf(stderr, NULL, _IONBF, 0);
}
@@ -605,7 +547,8 @@ int main(int argc, char *argv[])
runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch);
// Apply the initial translation based on user preference
StreamingPreferences::get()->retranslate();
StreamingPreferences prefs;
prefs.retranslate();
// Trickily declare the translation for dialog buttons
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");
qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0,
"ComputerManager",
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
return new ComputerManager(StreamingPreferences::get(qmlEngine));
[](QQmlEngine*, QJSEngine*) -> QObject* {
return new ComputerManager();
});
qmlRegisterSingletonType<AutoUpdateChecker>("AutoUpdateChecker", 1, 0,
"AutoUpdateChecker",
@@ -685,13 +628,13 @@ int main(int argc, char *argv[])
});
qmlRegisterSingletonType<SdlGamepadKeyNavigation>("SdlGamepadKeyNavigation", 1, 0,
"SdlGamepadKeyNavigation",
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
return new SdlGamepadKeyNavigation(StreamingPreferences::get(qmlEngine));
[](QQmlEngine*, QJSEngine*) -> QObject* {
return new SdlGamepadKeyNavigation();
});
qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0,
"StreamingPreferences",
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
return StreamingPreferences::get(qmlEngine);
return new StreamingPreferences(qmlEngine);
});
// Create the identity manager on the main thread
@@ -722,7 +665,7 @@ int main(int argc, char *argv[])
case GlobalCommandLineParser::StreamRequested:
{
initialView = "qrc:/gui/CliStartStreamSegue.qml";
StreamingPreferences* preferences = StreamingPreferences::get();
StreamingPreferences* preferences = new StreamingPreferences(&app);
StreamCommandLineParser streamParser;
streamParser.parse(app.arguments(), preferences);
QString host = streamParser.getHost();
@@ -754,7 +697,7 @@ int main(int argc, char *argv[])
ListCommandLineParser listParser;
listParser.parse(app.arguments());
auto launcher = new CliListApps::Launcher(listParser.getHost(), listParser, &app);
launcher->execute(new ComputerManager(StreamingPreferences::get()));
launcher->execute(new ComputerManager(&app));
hasGUI = false;
break;
}
@@ -775,12 +718,5 @@ int main(int argc, char *argv[])
// sometimes freezing and blocking process exit.
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;
}
+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-error_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.qm</file>
<file>languages/qml_fr.ts</file>
@@ -64,8 +64,6 @@
<file>languages/qml_cs.qm</file>
<file>languages/qml_he.ts</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 -->
<!--file>languages/qml_pt_BR.qm</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_bt601lim_pixel.fxc">shaders/d3d11_bt601lim_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>
</RCC>
+7 -45
View File
@@ -5,7 +5,6 @@
#include <QTranslator>
#include <QCoreApplication>
#include <QLocale>
#include <QReadWriteLock>
#include <QtMath>
#include <QtDebug>
@@ -38,7 +37,6 @@
#define SER_DEFAULTVER "defaultver"
#define SER_PACKETSIZE "packetsize"
#define SER_DETECTNETBLOCKING "detectnetblocking"
#define SER_SHOWPERFOVERLAY "showperfoverlay"
#define SER_SWAPMOUSEBUTTONS "swapmousebuttons"
#define SER_MUTEONFOCUSLOSS "muteonfocusloss"
#define SER_BACKGROUNDGAMEPAD "backgroundgamepad"
@@ -50,50 +48,18 @@
#define CURRENT_DEFAULT_VER 2
static StreamingPreferences* s_GlobalPrefs;
static QReadWriteLock s_GlobalPrefsLock;
StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine)
: m_QmlEngine(qmlEngine)
StreamingPreferences::StreamingPreferences(QObject *parent)
: QObject(parent),
m_QmlEngine(nullptr)
{
reload();
}
StreamingPreferences* StreamingPreferences::get(QQmlEngine *qmlEngine)
StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent)
: QObject(parent),
m_QmlEngine(qmlEngine)
{
{
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;
}
reload();
}
void StreamingPreferences::reload()
@@ -131,7 +97,6 @@ void StreamingPreferences::reload()
richPresence = settings.value(SER_RICHPRESENCE, true).toBool();
gamepadMouse = settings.value(SER_GAMEPADMOUSE, true).toBool();
detectNetworkBlocking = settings.value(SER_DETECTNETBLOCKING, true).toBool();
showPerformanceOverlay = settings.value(SER_SHOWPERFOVERLAY, false).toBool();
packetSize = settings.value(SER_PACKETSIZE, 0).toInt();
swapMouseButtons = settings.value(SER_SWAPMOUSEBUTTONS, false).toBool();
muteOnFocusLoss = settings.value(SER_MUTEONFOCUSLOSS, false).toBool();
@@ -287,8 +252,6 @@ QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Langua
return "cs";
case LANG_HE:
return "he";
case LANG_CKB:
return "ckb";
case LANG_AUTO:
default:
return QLocale::system().name();
@@ -317,7 +280,6 @@ void StreamingPreferences::save()
settings.setValue(SER_GAMEPADMOUSE, gamepadMouse);
settings.setValue(SER_PACKETSIZE, packetSize);
settings.setValue(SER_DETECTNETBLOCKING, detectNetworkBlocking);
settings.setValue(SER_SHOWPERFOVERLAY, showPerformanceOverlay);
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
settings.setValue(SER_HDR, enableHdr);
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
+2 -7
View File
@@ -9,7 +9,8 @@ class StreamingPreferences : public QObject
Q_OBJECT
public:
static StreamingPreferences* get(QQmlEngine *qmlEngine = nullptr);
StreamingPreferences(QObject *parent = nullptr);
StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent = nullptr);
Q_INVOKABLE static int
getDefaultBitrate(int width, int height, int fps);
@@ -91,7 +92,6 @@ public:
LANG_PL,
LANG_CS,
LANG_HE,
LANG_CKB,
};
Q_ENUM(Language);
@@ -120,7 +120,6 @@ public:
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged)
Q_PROPERTY(bool gamepadMouse MEMBER gamepadMouse NOTIFY gamepadMouseChanged)
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(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
Q_PROPERTY(bool enableHdr MEMBER enableHdr NOTIFY enableHdrChanged)
@@ -157,7 +156,6 @@ public:
bool richPresence;
bool gamepadMouse;
bool detectNetworkBlocking;
bool showPerformanceOverlay;
bool swapMouseButtons;
bool muteOnFocusLoss;
bool backgroundGamepad;
@@ -198,7 +196,6 @@ signals:
void richPresenceChanged();
void gamepadMouseChanged();
void detectNetworkBlockingChanged();
void showPerformanceOverlayChanged();
void mouseButtonsChanged();
void muteOnFocusLossChanged();
void backgroundGamepadChanged();
@@ -209,8 +206,6 @@ signals:
void languageChanged();
private:
explicit StreamingPreferences(QQmlEngine *qmlEngine);
QString getSuffixFromLanguage(Language lang);
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);
}
+45 -58
View File
@@ -64,48 +64,6 @@ IAudioRenderer* Session::createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATI
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)
{
// Build a fake OPUS_MULTISTREAM_CONFIGURATION to give
@@ -150,8 +108,39 @@ int Session::arInit(int /* audioConfiguration */,
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
void* /* arContext */, int /* arFlags */)
{
SDL_memcpy(&s_ActiveSession->m_OriginalAudioConfig, opusConfig, sizeof(*opusConfig));
s_ActiveSession->initializeAudioRenderer();
int error;
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;
}
@@ -205,7 +194,7 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
}
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);
if (buffer == nullptr) {
return;
@@ -215,13 +204,13 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
(unsigned char*)sampleData,
sampleLength,
(short*)buffer,
desiredSize / sizeof(short) / s_ActiveSession->m_ActiveAudioConfig.channelCount,
desiredSize / sizeof(short) / s_ActiveSession->m_AudioConfig.channelCount,
0);
// Update desiredSize with the number of bytes actually populated by the decoding operation
if (samplesDecoded > 0) {
SDL_assert(desiredSize >= (int)(sizeof(short) * samplesDecoded * s_ActiveSession->m_ActiveAudioConfig.channelCount));
desiredSize = 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_AudioConfig.channelCount;
}
else {
desiredSize = 0;
@@ -231,9 +220,6 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Reinitializing audio renderer after failure");
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
s_ActiveSession->m_OpusDecoder = nullptr;
delete s_ActiveSession->m_AudioRenderer;
s_ActiveSession->m_AudioRenderer = nullptr;
}
@@ -248,13 +234,14 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
// 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.
Uint32 audioReinitStartTime = SDL_GetTicks();
if (s_ActiveSession->initializeAudioRenderer()) {
Uint32 audioReinitStopTime = SDL_GetTicks();
s_ActiveSession->m_DropAudioEndTime = audioReinitStopTime + (audioReinitStopTime - audioReinitStartTime);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Audio reinitialization took %d ms - starting drop window",
audioReinitStopTime - audioReinitStartTime);
}
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(&s_ActiveSession->m_AudioConfig);
Uint32 audioReinitStopTime = SDL_GetTicks();
s_ActiveSession->m_DropAudioEndTime = audioReinitStopTime + (audioReinitStopTime - audioReinitStartTime);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Audio reinitialization took %d ms - starting drop window",
audioReinitStopTime - audioReinitStartTime);
}
}
+8 -14
View File
@@ -100,6 +100,12 @@ void* SdlAudioRenderer::getAudioBuffer(int*)
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) {
// Nothing to do
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
// in SDL's audio queue, but don't wait forever to avoid a deadlock if the
// audio device fails.
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;
}
// in SDL's audio queue.
while (SDL_GetQueuedAudioSize(m_AudioDevice) / m_FrameSize > 10) {
SDL_Delay(1);
}
+8 -35
View File
@@ -112,43 +112,16 @@ void SdlInputHandler::handleAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
pointerId = (uint32_t)event->fingerId;
}
// Try to send it as a native pen/touch event, otherwise fall back to our touch emulation
if (LiGetHostFeatureFlags() & LI_FF_PEN_TOUCH_EVENTS) {
#if SDL_VERSION_ATLEAST(2, 0, 22)
bool isPen = false;
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;
}
}
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
disableTouchFeedback();
m_DisabledTouchFeedback = true;
}
}
else {
// Try to send it as a native touch event, otherwise fall back to our touch emulation
if (LiSendTouchEvent(eventType, pointerId, vidrelx / dst.w, vidrely / dst.h, event->pressure,
0.0f, 0.0f, LI_ROT_UNKNOWN) == LI_ERR_UNSUPPORTED) {
emulateAbsoluteFingerEvent(event);
}
else if (!m_DisabledTouchFeedback) {
// Disable touch feedback when passing touch natively
disableTouchFeedback();
m_DisabledTouchFeedback = true;
}
}
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,
m_GamepadMask,
buttons,
lt,
rt,
lsX,
lsY,
rsX,
rsY);
state->lt,
state->rt,
state->lsX,
state->lsY,
state->rsX,
state->rsY);
}
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);
int rawX;
int rawY;
short rawX;
short rawY;
// 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;
rawY = -gamepad->lsY;
}
@@ -955,10 +922,6 @@ QString SdlInputHandler::getUnmappedGamepads()
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;
}
+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
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)
// For older versions of SDL (2.0.14 and earlier), use 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
LiSendUtf8TextEvent(text, (unsigned int)strlen(text));
LiSendUtf8TextEvent(text, strlen(text));
// SDL_GetClipboardText() allocates, so we must free
SDL_free((void*)text);
+8 -23
View File
@@ -79,24 +79,6 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
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) {
int 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
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
mouseInVideoRegion = isMouseInVideoRegion(x, y, windowWidth, windowHeight);
mouseInVideoRegion = isMouseInVideoRegion(event->x,
event->y,
windowWidth,
windowHeight);
// Clamp motion to the video region
x = qMin(qMax(x - dst.x, 0), dst.w);
y = qMin(qMax(y - dst.y, 0), dst.h);
short x = qMin(qMax(event->x - dst.x, 0), dst.w);
short y = qMin(qMax(event->y - dst.y, 0), dst.h);
// Send the mouse position update if one of the following is true:
// a) it is in the video region now
@@ -134,7 +119,7 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
}
}
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
@@ -150,7 +135,7 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
m_MouseWasInVideoRegion = mouseInVideoRegion;
}
else {
LiSendMouseMoveEvent(xrel, yrel);
LiSendMouseMoveEvent(event->xrel, event->yrel);
}
}
+61 -201
View File
@@ -52,7 +52,6 @@
#include <QGuiApplication>
#include <QCursor>
#include <QWindow>
#include <QScreen>
#define CONN_TEST_SERVER "qt.conntest.moonlight-stream.org"
@@ -133,11 +132,7 @@ void Session::clConnectionTerminated(int errorCode)
default:
s_ActiveSession->m_UnexpectedTermination = true;
// 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')));
emit s_ActiveSession->displayLaunchError(tr("Connection terminated"));
break;
}
@@ -541,15 +536,17 @@ bool Session::populateDecoderProperties(SDL_Window* window)
}
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_Computer(computer),
m_App(app),
m_Window(nullptr),
m_VideoDecoder(nullptr),
m_DecoderLock(0),
m_AudioDisabled(false),
m_AudioMuted(false),
m_QtWindow(nullptr),
m_DisplayOriginX(0),
m_DisplayOriginY(0),
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
m_InputHandler(nullptr),
m_MouseEmulationRefCount(0),
@@ -565,40 +562,6 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
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) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
@@ -635,18 +598,13 @@ bool Session::initialize()
m_StreamConfig.height = m_Preferences->height;
m_StreamConfig.fps = m_Preferences->fps;
m_StreamConfig.bitrate = m_Preferences->bitrateKbps;
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
m_StreamConfig.av1BitratePercentageMultiplier = 75;
#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.
// That hardware can hardly handle Opus decoding at all.
m_StreamConfig.encryptionFlags = ENCFLG_AUDIO;
}
// Enable audio encryption as long as we're not on Steam Link.
// That hardware can hardly handle Opus decoding at all.
m_StreamConfig.encryptionFlags = ENCFLG_AUDIO;
#endif
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
@@ -760,15 +718,6 @@ bool Session::initialize()
if (m_Preferences->enableHdr) {
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;
}
@@ -783,12 +732,7 @@ bool Session::initialize()
}
// Fall-through
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;
#endif
break;
}
@@ -870,13 +814,21 @@ bool Session::validateLaunch(SDL_Window* testWindow)
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
// to set this explicitly here so we can do our hardware acceleration
// check below.
m_StreamConfig.supportedVideoFormats &= ~VIDEO_FORMAT_MASK_AV1;
}
else if (!m_Preferences->enableHdr && // HDR is checked below
m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
else if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Auto VCC is already checked in initialize()
!isHardwareDecodeAvailable(testWindow,
m_Preferences->videoDecoderSelection,
@@ -899,8 +851,7 @@ bool Session::validateLaunch(SDL_Window* testWindow)
// check below.
m_StreamConfig.supportedVideoFormats &= ~VIDEO_FORMAT_MASK_H265;
}
else if (!m_Preferences->enableHdr && // HDR is checked below
m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
else if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Auto VCC is already checked in initialize()
!isHardwareDecodeAvailable(testWindow,
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 (!audioTestPassed) {
m_AudioDisabled = !audioTestPassed;
if (m_AudioDisabled) {
emitLaunchWarning(tr("Failed to open audio device. Audio will be unavailable during this session."));
}
@@ -1134,38 +1086,26 @@ void Session::getWindowDimensions(int& x, int& y,
// Create our window on the same display that Qt's UI
// was being displayed on.
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,
"Qt UI screen is at (%d,%d)",
m_DisplayOriginX, m_DisplayOriginY);
for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
SDL_Rect displayBounds;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Qt UI screen is at (%d,%d)",
displayRect.x(), displayRect.y());
for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
SDL_Rect displayBounds;
if (SDL_GetDisplayBounds(i, &displayBounds) == 0) {
if (displayBounds.x == displayRect.x() &&
displayBounds.y == displayRect.y()) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"SDL found matching display %d",
i);
displayIndex = i;
break;
}
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"SDL_GetDisplayBounds(%d) failed: %s",
i, SDL_GetError());
}
if (SDL_GetDisplayBounds(i, &displayBounds) == 0) {
if (displayBounds.x == m_DisplayOriginX &&
displayBounds.y == m_DisplayOriginY) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"SDL found matching display %d",
i);
displayIndex = i;
break;
}
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Qt window is not associated with a QScreen!");
"SDL_GetDisplayBounds(%d) failed: %s",
i, SDL_GetError());
}
}
}
@@ -1219,8 +1159,7 @@ void Session::updateOptimalWindowDisplayMode()
// If this doesn't fit the selected resolution, use the native
// resolution of the panel (unscaled).
if (desktopMode.w < m_ActiveVideoWidth || desktopMode.h < m_ActiveVideoHeight) {
SDL_Rect safeArea;
if (!StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode, &safeArea)) {
if (!StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode)) {
return;
}
}
@@ -1485,7 +1424,8 @@ bool Session::startConnectionAsync()
}
int err = LiStartConnection(&hostInfo, &m_StreamConfig, &k_ConnCallbacks,
&m_VideoCallbacks, &m_AudioCallbacks,
&m_VideoCallbacks,
m_AudioDisabled ? nullptr : &m_AudioCallbacks,
NULL, 0, NULL, 0);
if (err != 0) {
// We already displayed an error dialog in the stage failure
@@ -1532,9 +1472,10 @@ public:
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
// 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
Uint32 defaultWindowFlags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE;
// If we're starting in windowed mode and the Moonlight GUI is maximized or
// minimized, match that with the streaming window.
if (!m_IsFullScreen && m_QtWindow != nullptr) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
// Qt 5.10+ can propagate multiple states together
if (m_QtWindow->windowStates() & Qt::WindowMaximized) {
defaultWindowFlags |= SDL_WINDOW_MAXIMIZED;
// If we're starting in windowed mode and the Moonlight GUI is maximized,
// match that with the streaming window.
if (!m_IsFullScreen) {
QWindowList windows = QGuiApplication::topLevelWindows();
for (const QWindow* window : windows) {
if (window->windowState() & Qt::WindowMaximized) {
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
@@ -1700,13 +1631,16 @@ void Session::execInternal()
// HACK: Remove once proper Dark Mode support lands in SDL
#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)
if (FAILED(DwmGetWindowAttribute((HWND)m_QtWindow->winId(), DWMWA_USE_IMMERSIVE_DARK_MODE, &darkModeEnabled, sizeof(darkModeEnabled))) &&
FAILED(DwmGetWindowAttribute((HWND)m_QtWindow->winId(), DWMWA_USE_IMMERSIVE_DARK_MODE_OLD, &darkModeEnabled, sizeof(darkModeEnabled)))) {
darkModeEnabled = FALSE;
QWindowList windows = QGuiApplication::topLevelWindows();
for (const QWindow* window : windows) {
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;
@@ -1838,9 +1772,6 @@ void Session::execInternal()
// Start rich presence to indicate we're in game
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
// because we want to suspend all Qt processing until the stream is over.
SDL_Event event;
@@ -1984,52 +1915,6 @@ void Session::execInternal()
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,
"Recreating renderer for window event: %d (%d %d)",
event.window.event,
@@ -2202,31 +2087,6 @@ DispatchDeferredCleanup:
m_VideoDecoder = nullptr;
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
// the renderer may want to interact with the window
SDL_DestroyWindow(m_Window);
+4 -7
View File
@@ -1,7 +1,6 @@
#pragma once
#include <QSemaphore>
#include <QWindow>
#include <Limelight.h>
#include <opus_multistream.h>
@@ -27,7 +26,7 @@ public:
// Use Session::exec() or DeferredSessionCleanupTask instead.
virtual ~Session() {};
Q_INVOKABLE void exec(QWindow* qtWindow);
Q_INVOKABLE void exec(int displayOriginX, int displayOriginY);
static
void getDecoderInfo(SDL_Window* window,
@@ -79,8 +78,6 @@ private:
IAudioRenderer* createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATION opusConfig);
bool initializeAudioRenderer();
bool testAudio(int audioConfiguration);
int getAudioRendererCapabilities(int audioConfiguration);
@@ -169,7 +166,8 @@ private:
bool m_AudioDisabled;
bool m_AudioMuted;
Uint32 m_FullScreenFlag;
QWindow* m_QtWindow;
int m_DisplayOriginX;
int m_DisplayOriginY;
bool m_ThreadedExec;
bool m_UnexpectedTermination;
SdlInputHandler* m_InputHandler;
@@ -186,8 +184,7 @@ private:
OpusMSDecoder* m_OpusDecoder;
IAudioRenderer* m_AudioRenderer;
OPUS_MULTISTREAM_CONFIGURATION m_ActiveAudioConfig;
OPUS_MULTISTREAM_CONFIGURATION m_OriginalAudioConfig;
OPUS_MULTISTREAM_CONFIGURATION m_AudioConfig;
int m_AudioSampleCount;
Uint32 m_DropAudioEndTime;
+12 -157
View File
@@ -6,67 +6,10 @@
#include <ApplicationServices/ApplicationServices.h>
#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()
{
#if defined(Q_OS_DARWIN)
#ifdef Q_OS_DARWIN
return SDL_WINDOW_METAL;
#elif defined(HAVE_LIBPLACEBO_VULKAN)
// We'll fall back to GL if Vulkan fails
return SDL_WINDOW_VULKAN;
#else
return 0;
#endif
@@ -149,51 +92,7 @@ int StreamUtils::getDisplayRefreshRate(SDL_Window* window)
return mode.refresh_rate;
}
bool StreamUtils::hasFastAes()
{
#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)
bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode)
{
#ifdef Q_OS_DARWIN
#define MAX_DISPLAYS 16
@@ -201,6 +100,9 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode,
uint32_t displayCount = 0;
CGGetActiveDisplayList(MAX_DISPLAYS, displayIds, &displayCount);
if (displayIndex >= displayCount) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Too many displays: %d vs %d",
displayIndex, displayCount);
return false;
}
@@ -220,61 +122,20 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode,
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);
// 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
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
SDL_DisplayMode thisMode;
if (SDL_GetDisplayMode(displayIndex, i, &thisMode) == 0) {
if (thisMode.w == mode->w && thisMode.h == mode->h &&
// Now find the SDL mode that matches the CG native mode
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
SDL_DisplayMode thisMode;
if (SDL_GetDisplayMode(displayIndex, i, &thisMode) == 0) {
if (thisMode.w == mode->w && thisMode.h == mode->h &&
thisMode.refresh_rate >= mode->refresh_rate) {
*mode = thisMode;
break;
}
*mode = thisMode;
break;
}
}
}
#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).
// 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
@@ -296,13 +157,7 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode,
return false;
}
}
safeArea->x = 0;
safeArea->y = 0;
safeArea->w = mode->w;
safeArea->h = mode->h;
#endif
return true;
}
+1 -4
View File
@@ -29,11 +29,8 @@ public:
void screenSpaceToNormalizedDeviceCoords(SDL_Rect* src, SDL_FRect* dst, int viewportWidth, int viewportHeight);
static
bool getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode, SDL_Rect* safeArea);
bool getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode);
static
int getDisplayRefreshRate(SDL_Window* window);
static
bool hasFastAes();
};
-16
View File
@@ -45,21 +45,6 @@ typedef struct _DECODER_PARAMETERS {
bool testOnly;
} 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 {
public:
virtual ~IVideoDecoder() {}
@@ -74,5 +59,4 @@ public:
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
virtual void renderFrameOnMainThread() = 0;
virtual void setHdrMode(bool enabled) = 0;
virtual bool notifyWindowChanged(PWINDOW_STATE_CHANGE_INFO info) = 0;
};
+132 -70
View File
@@ -75,8 +75,6 @@ static_assert(sizeof(CSC_CONST_BUF) % 16 == 0, "Constant buffer sizes must be a
D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
: m_DecoderSelectionPass(decoderSelectionPass),
m_DevicesWithFL11Support(0),
m_DevicesWithCodecSupport(0),
m_Factory(nullptr),
m_Device(nullptr),
m_SwapChain(nullptr),
@@ -84,7 +82,6 @@ D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
m_RenderTargetView(nullptr),
m_LastColorSpace(-1),
m_LastFullRange(false),
m_LastColorTrc(AVCOL_TRC_UNSPECIFIED),
m_AllowTearing(false),
m_VideoGenericPixelShader(nullptr),
m_VideoBt601LimPixelShader(nullptr),
@@ -93,7 +90,8 @@ D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
m_VideoTexture(nullptr),
m_OverlayLock(0),
m_OverlayPixelShader(nullptr),
m_HwDeviceContext(nullptr)
m_HwDeviceContext(nullptr),
m_HwFramesContext(nullptr)
{
RtlZeroMemory(m_OverlayVertexBuffers, sizeof(m_OverlayVertexBuffers));
RtlZeroMemory(m_OverlayTextures, sizeof(m_OverlayTextures));
@@ -139,6 +137,10 @@ D3D11VARenderer::~D3D11VARenderer()
SAFE_COM_RELEASE(m_RenderTargetView);
SAFE_COM_RELEASE(m_SwapChain);
if (m_HwFramesContext != nullptr) {
av_buffer_unref(&m_HwFramesContext);
}
// Force destruction of the swapchain immediately
if (m_DeviceContext != nullptr) {
m_DeviceContext->ClearState();
@@ -162,7 +164,6 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter
bool success = false;
IDXGIAdapter1* adapter = nullptr;
DXGI_ADAPTER_DESC1 adapterDesc;
D3D_FEATURE_LEVEL featureLevel;
HRESULT hr;
SDL_assert(m_Device == nullptr);
@@ -212,7 +213,7 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter
0,
D3D11_SDK_VERSION,
&m_Device,
&featureLevel,
nullptr,
&m_DeviceContext);
if (FAILED(hr)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@@ -220,11 +221,6 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter
hr);
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)) {
m_DeviceContext->Release();
@@ -234,10 +230,6 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter
goto Exit;
}
else {
// Remember that we found a device with support for decoding this codec
m_DevicesWithCodecSupport++;
}
success = true;
@@ -429,6 +421,10 @@ bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
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()) {
return false;
}
@@ -462,9 +458,43 @@ bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
}
}
// Create our video texture and SRVs
if (!setupVideoTexture()) {
return false;
{
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
if (!setupVideoTexture()) {
return false;
}
}
return true;
@@ -480,6 +510,87 @@ bool D3D11VARenderer::prepareDecoderContext(AVCodecContext* context, AVDictionar
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)
{
// Acquire the context lock for rendering to prevent concurrent
@@ -518,33 +629,8 @@ void D3D11VARenderer::renderFrame(AVFrame* frame)
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
hr = m_SwapChain->Present(0, flags);
HRESULT hr = m_SwapChain->Present(0, flags);
// Release the context lock
unlockContext(this);
@@ -727,8 +813,7 @@ void D3D11VARenderer::notifyOverlayUpdated(Overlay::OverlayType type)
HRESULT hr;
SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type);
bool overlayEnabled = Session::get()->getOverlayManager().isOverlayEnabled(type);
if (newSurface == nullptr && overlayEnabled) {
if (newSurface == nullptr && Session::get()->getOverlayManager().isOverlayEnabled(type)) {
// The overlay is enabled and there is no new surface. Leave the old texture alone.
return;
}
@@ -749,7 +834,7 @@ void D3D11VARenderer::notifyOverlayUpdated(Overlay::OverlayType type)
SAFE_COM_RELEASE(oldVertexBuffer);
// If the overlay is disabled, we're done
if (!overlayEnabled) {
if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) {
SDL_FreeSurface(newSurface);
return;
}
@@ -1009,29 +1094,6 @@ bool D3D11VARenderer::needsTestFrame()
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)
{
auto me = (D3D11VARenderer*)lock_ctx;
@@ -1,6 +1,7 @@
#pragma once
#include "renderer.h"
#include "pacer/pacer.h"
#include <d3d11_1.h>
#include <dxgi1_5.h>
@@ -16,12 +17,13 @@ public:
virtual ~D3D11VARenderer() override;
virtual bool initialize(PDECODER_PARAMETERS params) override;
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary**) override;
virtual bool prepareDecoderContextInGetFormat(AVCodecContext* context, AVPixelFormat pixelFormat) override;
virtual void renderFrame(AVFrame* frame) override;
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
virtual void setHdrMode(bool enabled) override;
virtual int getRendererAttributes() override;
virtual int getDecoderCapabilities() override;
virtual bool needsTestFrame() override;
virtual InitFailureReason getInitFailureReason() override;
private:
static void lockContext(void* lock_ctx);
@@ -36,8 +38,6 @@ private:
bool createDeviceByAdapterIndex(int adapterIndex, bool* adapterNotFound = nullptr);
int m_DecoderSelectionPass;
int m_DevicesWithFL11Support;
int m_DevicesWithCodecSupport;
IDXGIFactory5* m_Factory;
ID3D11Device* m_Device;
@@ -47,11 +47,11 @@ private:
SDL_mutex* m_ContextLock;
DECODER_PARAMETERS m_DecoderParams;
int m_TextureAlignment;
int m_DisplayWidth;
int m_DisplayHeight;
int m_LastColorSpace;
bool m_LastFullRange;
AVColorTransferCharacteristic m_LastColorTrc;
bool m_AllowTearing;
@@ -70,5 +70,6 @@ private:
ID3D11PixelShader* m_OverlayPixelShader;
AVBufferRef* m_HwDeviceContext;
AVBufferRef* m_HwFramesContext;
};
+4 -5
View File
@@ -4,7 +4,6 @@
#endif
#include "drm.h"
#include "string.h"
extern "C" {
#include <libavutil/hwcontext_drm.h>
@@ -12,6 +11,7 @@ extern "C" {
#include <libdrm/drm_fourcc.h>
#include <linux/dma-buf.h>
#include <sys/ioctl.h>
// Special Rockchip type
#ifndef DRM_FORMAT_NA12
@@ -158,8 +158,7 @@ bool DrmRenderer::prepareDecoderContext(AVCodecContext* context, AVDictionary**
{
// 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.
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
// used by the JH7110 multimedia stack. This decoder gives us software frames,
@@ -845,7 +844,7 @@ bool DrmRenderer::mapSoftwareFrame(AVFrame *frame, AVDRMFrameDescriptor *mappedF
// Prepare to write to the dumb buffer from the CPU
struct dma_buf_sync sync;
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;
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
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;
+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(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(); }
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.
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;
@@ -566,6 +568,49 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
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;
d3d9ex->GetDeviceCaps(adapterIndex, D3DDEVTYPE_HAL, &deviceCaps);
@@ -799,8 +844,7 @@ void DXVA2Renderer::notifyOverlayUpdated(Overlay::OverlayType type)
HRESULT hr;
SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type);
bool overlayEnabled = Session::get()->getOverlayManager().isOverlayEnabled(type);
if (newSurface == nullptr && overlayEnabled) {
if (newSurface == nullptr && Session::get()->getOverlayManager().isOverlayEnabled(type)) {
// The overlay is enabled and there is no new surface. Leave the old texture alone.
return;
}
@@ -817,7 +861,7 @@ void DXVA2Renderer::notifyOverlayUpdated(Overlay::OverlayType type)
SAFE_COM_RELEASE(oldVertexBuffer);
// If the overlay is disabled, we're done
if (!overlayEnabled) {
if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) {
SDL_FreeSurface(newSurface);
return;
}
+1 -1
View File
@@ -80,7 +80,7 @@ private:
#define DXVA2_QUIRK_NO_VP 0x01
#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
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)
{
// Pixel format support should be determined by the backend renderer
@@ -186,7 +180,7 @@ AVPixelFormat EGLRenderer::getPreferredPixelFormat(int 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
if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) {
@@ -240,7 +234,7 @@ void EGLRenderer::renderOverlay(Overlay::OverlayType type, int viewportWidth, in
else if (type == Overlay::OverlayDebug) {
// Top left
overlayRect.x = 0;
overlayRect.y = viewportHeight - newSurface->h;
overlayRect.y = m_ViewportHeight - newSurface->h;
} else {
SDL_assert(false);
}
@@ -251,7 +245,7 @@ void EGLRenderer::renderOverlay(Overlay::OverlayType type, int viewportWidth, in
SDL_FreeSurface(newSurface);
// Convert screen space to normalized device coordinates
StreamUtils::screenSpaceToNormalizedDeviceCoords(&overlayRect, viewportWidth, viewportHeight);
StreamUtils::screenSpaceToNormalizedDeviceCoords(&overlayRect, m_ViewportWidth, m_ViewportHeight);
OVERLAY_VERTEX verts[] =
{
@@ -458,19 +452,13 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
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:
// SDL_CreateRenderer() failed: Could not create GLES window surface
// 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.
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!");
return false;
}
@@ -659,6 +647,21 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
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,
// because the compositor guarantees tear-free rendering. In this
// situation, swap interval > 0 behaves as a frame pacing option
@@ -922,20 +925,6 @@ void EGLRenderer::renderFrame(AVFrame* frame)
}
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);
m_glBindVertexArrayOES(m_VAO);
@@ -954,10 +943,8 @@ void EGLRenderer::renderFrame(AVFrame* frame)
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++) {
renderOverlay((Overlay::OverlayType)i, drawableWidth, drawableHeight);
renderOverlay((Overlay::OverlayType)i);
}
SDL_GL_SwapWindow(m_Window);

Some files were not shown because too many files have changed in this diff Show More