mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-22 08:07:22 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b23a36908c |
No files matched your search
@@ -56,7 +56,7 @@ Hosting for Moonlight's Raspberry Pi and L4T package repositories is graciously
|
|||||||
* Qt 5.9 SDK or later
|
* Qt 5.9 SDK or later
|
||||||
* GCC or Clang
|
* GCC or Clang
|
||||||
* Install the required packages:
|
* Install the required packages:
|
||||||
* Debian/Ubuntu: `libegl1-mesa-dev libgl1-mesa-dev libopus-dev libqt5svg5-dev libsdl2-dev libsdl2-ttf-dev libssl-dev libavcodec-dev 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 qt5-default qt5-qmake qtbase5-dev qtdeclarative5-dev qtquickcontrols2-5-dev wayland-protocols qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtquick-window2 qml-module-qtquick2`
|
||||||
* RedHat/Fedora: `openssl-devel SDL2-devel SDL2_ttf-devel ffmpeg-devel qt5-qtsvg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel`
|
* RedHat/Fedora: `openssl-devel SDL2-devel SDL2_ttf-devel ffmpeg-devel qt5-qtsvg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel`
|
||||||
* FFmpeg 4.0+ is required to build. If your distro doesn't package FFmpeg 4.0 or later, you can build and install it from source on https://ffmpeg.org/
|
* FFmpeg 4.0+ 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/
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule app/SDL_GameControllerDB updated: ff26eb04d0...2ab959b3f9.
+2
-13
@@ -54,11 +54,6 @@ macx {
|
|||||||
INCLUDEPATH += $$PWD/../libs/mac/Frameworks/SDL2.framework/Versions/A/Headers
|
INCLUDEPATH += $$PWD/../libs/mac/Frameworks/SDL2.framework/Versions/A/Headers
|
||||||
INCLUDEPATH += $$PWD/../libs/mac/Frameworks/SDL2_ttf.framework/Versions/A/Headers
|
INCLUDEPATH += $$PWD/../libs/mac/Frameworks/SDL2_ttf.framework/Versions/A/Headers
|
||||||
LIBS += -L$$PWD/../libs/mac/lib -F$$PWD/../libs/mac/Frameworks
|
LIBS += -L$$PWD/../libs/mac/lib -F$$PWD/../libs/mac/Frameworks
|
||||||
|
|
||||||
# QMake doesn't handle framework-style includes correctly on its own
|
|
||||||
QMAKE_CFLAGS += -F$$PWD/../libs/mac/Frameworks
|
|
||||||
QMAKE_CXXFLAGS += -F$$PWD/../libs/mac/Frameworks
|
|
||||||
QMAKE_OBJECTIVE_CFLAGS += -F$$PWD/../libs/mac/Frameworks
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unix:!macx {
|
unix:!macx {
|
||||||
@@ -132,7 +127,6 @@ macx {
|
|||||||
SOURCES += \
|
SOURCES += \
|
||||||
backend/nvaddress.cpp \
|
backend/nvaddress.cpp \
|
||||||
backend/nvapp.cpp \
|
backend/nvapp.cpp \
|
||||||
cli/pair.cpp \
|
|
||||||
main.cpp \
|
main.cpp \
|
||||||
backend/computerseeker.cpp \
|
backend/computerseeker.cpp \
|
||||||
backend/identitymanager.cpp \
|
backend/identitymanager.cpp \
|
||||||
@@ -143,7 +137,6 @@ SOURCES += \
|
|||||||
backend/boxartmanager.cpp \
|
backend/boxartmanager.cpp \
|
||||||
backend/richpresencemanager.cpp \
|
backend/richpresencemanager.cpp \
|
||||||
cli/commandlineparser.cpp \
|
cli/commandlineparser.cpp \
|
||||||
cli/listapps.cpp \
|
|
||||||
cli/quitstream.cpp \
|
cli/quitstream.cpp \
|
||||||
cli/startstream.cpp \
|
cli/startstream.cpp \
|
||||||
settings/compatfetcher.cpp \
|
settings/compatfetcher.cpp \
|
||||||
@@ -172,7 +165,6 @@ SOURCES += \
|
|||||||
HEADERS += \
|
HEADERS += \
|
||||||
backend/nvaddress.h \
|
backend/nvaddress.h \
|
||||||
backend/nvapp.h \
|
backend/nvapp.h \
|
||||||
cli/pair.h \
|
|
||||||
settings/compatfetcher.h \
|
settings/compatfetcher.h \
|
||||||
settings/mappingfetcher.h \
|
settings/mappingfetcher.h \
|
||||||
utils.h \
|
utils.h \
|
||||||
@@ -185,7 +177,6 @@ HEADERS += \
|
|||||||
backend/boxartmanager.h \
|
backend/boxartmanager.h \
|
||||||
backend/richpresencemanager.h \
|
backend/richpresencemanager.h \
|
||||||
cli/commandlineparser.h \
|
cli/commandlineparser.h \
|
||||||
cli/listapps.h \
|
|
||||||
cli/quitstream.h \
|
cli/quitstream.h \
|
||||||
cli/startstream.h \
|
cli/startstream.h \
|
||||||
settings/streamingpreferences.h \
|
settings/streamingpreferences.h \
|
||||||
@@ -271,7 +262,7 @@ libdrm {
|
|||||||
|
|
||||||
linux {
|
linux {
|
||||||
message(Master hooks enabled)
|
message(Master hooks enabled)
|
||||||
SOURCES += masterhook.c masterhook_internal.c
|
SOURCES += masterhook.c
|
||||||
LIBS += -ldl
|
LIBS += -ldl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,9 +365,7 @@ TRANSLATIONS += \
|
|||||||
languages/qml_hi.ts \
|
languages/qml_hi.ts \
|
||||||
languages/qml_it.ts \
|
languages/qml_it.ts \
|
||||||
languages/qml_pt.ts \
|
languages/qml_pt.ts \
|
||||||
languages/qml_pt_BR.ts \
|
languages/qml_pt_BR.ts
|
||||||
languages/qml_pl.ts \
|
|
||||||
languages/qml_cs.ts
|
|
||||||
|
|
||||||
# Additional import path used to resolve QML modules in Qt Creator's code model
|
# Additional import path used to resolve QML modules in Qt Creator's code model
|
||||||
QML_IMPORT_PATH =
|
QML_IMPORT_PATH =
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
|
|||||||
<< QSysInfo::buildCpuArchitecture() << getPlatform() << QSysInfo::kernelVersion();
|
<< QSysInfo::buildCpuArchitecture() << getPlatform() << QSysInfo::kernelVersion();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qWarning() << "Update checking failed with error:" << reply->error();
|
qWarning() << "Update checking failed with error: " << reply->error();
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+26
-130
@@ -10,8 +10,6 @@
|
|||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
|
||||||
#include <random>
|
|
||||||
|
|
||||||
#define SER_HOSTS "hosts"
|
#define SER_HOSTS "hosts"
|
||||||
|
|
||||||
class PcMonitorThread : public QThread
|
class PcMonitorThread : public QThread
|
||||||
@@ -44,7 +42,7 @@ private:
|
|||||||
|
|
||||||
// Ensure the machine that responded is the one we intended to contact
|
// Ensure the machine that responded is the one we intended to contact
|
||||||
if (m_Computer->uuid != newState.uuid) {
|
if (m_Computer->uuid != newState.uuid) {
|
||||||
qInfo() << "Found unexpected PC" << newState.name << "looking for" << m_Computer->name;
|
qInfo() << "Found unexpected PC " << newState.name << " looking for " << m_Computer->name;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,8 +145,7 @@ ComputerManager::ComputerManager(QObject *parent)
|
|||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
m_PollingRef(0),
|
m_PollingRef(0),
|
||||||
m_MdnsBrowser(nullptr),
|
m_MdnsBrowser(nullptr),
|
||||||
m_CompatFetcher(nullptr),
|
m_CompatFetcher(nullptr)
|
||||||
m_NeedsDelayedFlush(false)
|
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
|
||||||
@@ -164,10 +161,6 @@ ComputerManager::ComputerManager(QObject *parent)
|
|||||||
// Fetch latest compatibility data asynchronously
|
// Fetch latest compatibility data asynchronously
|
||||||
m_CompatFetcher.start();
|
m_CompatFetcher.start();
|
||||||
|
|
||||||
// Start the delayed flush thread to handle saveHosts() calls
|
|
||||||
m_DelayedFlushThread = new DelayedFlushThread(this);
|
|
||||||
m_DelayedFlushThread->start();
|
|
||||||
|
|
||||||
// To quit in a timely manner, we must block additional requests
|
// To quit in a timely manner, we must block additional requests
|
||||||
// after we receive the aboutToQuit() signal. This is neccessary
|
// after we receive the aboutToQuit() signal. This is neccessary
|
||||||
// because NvHTTP uses aboutToQuit() to abort requests in progres
|
// because NvHTTP uses aboutToQuit() to abort requests in progres
|
||||||
@@ -178,21 +171,6 @@ ComputerManager::ComputerManager(QObject *parent)
|
|||||||
|
|
||||||
ComputerManager::~ComputerManager()
|
ComputerManager::~ComputerManager()
|
||||||
{
|
{
|
||||||
// Stop the delayed flush thread before acquiring the lock in write mode
|
|
||||||
// to avoid deadlocking with a flush that needs the lock in read mode.
|
|
||||||
{
|
|
||||||
// Wake the delayed flush thread
|
|
||||||
m_DelayedFlushThread->requestInterruption();
|
|
||||||
m_DelayedFlushCondition.wakeOne();
|
|
||||||
|
|
||||||
// Wait for it to terminate (and finish any pending flush)
|
|
||||||
m_DelayedFlushThread->wait();
|
|
||||||
delete m_DelayedFlushThread;
|
|
||||||
|
|
||||||
// Delayed flushes should have completed by now
|
|
||||||
Q_ASSERT(!m_NeedsDelayedFlush);
|
|
||||||
}
|
|
||||||
|
|
||||||
QWriteLocker lock(&m_Lock);
|
QWriteLocker lock(&m_Lock);
|
||||||
|
|
||||||
// Delete machines that haven't been resolved yet
|
// Delete machines that haven't been resolved yet
|
||||||
@@ -222,53 +200,19 @@ ComputerManager::~ComputerManager()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DelayedFlushThread::run() {
|
|
||||||
for (;;) {
|
|
||||||
// Wait for a delayed flush request or an interruption
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_ComputerManager->m_DelayedFlushMutex);
|
|
||||||
|
|
||||||
while (!QThread::currentThread()->isInterruptionRequested() && !m_ComputerManager->m_NeedsDelayedFlush) {
|
|
||||||
m_ComputerManager->m_DelayedFlushCondition.wait(&m_ComputerManager->m_DelayedFlushMutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bail without flushing if we woke up for an interruption alone.
|
|
||||||
// If we have both an interruption and a flush request, do the flush.
|
|
||||||
if (!m_ComputerManager->m_NeedsDelayedFlush) {
|
|
||||||
Q_ASSERT(QThread::currentThread()->isInterruptionRequested());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the delayed flush flag to ensure any racing saveHosts() call will set it again
|
|
||||||
m_ComputerManager->m_NeedsDelayedFlush = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the flush
|
|
||||||
{
|
|
||||||
QReadLocker lock(&m_ComputerManager->m_Lock);
|
|
||||||
|
|
||||||
QSettings settings;
|
|
||||||
settings.remove(SER_HOSTS);
|
|
||||||
settings.beginWriteArray(SER_HOSTS);
|
|
||||||
int i = 0;
|
|
||||||
for (const NvComputer* computer : m_ComputerManager->m_KnownHosts) {
|
|
||||||
settings.setArrayIndex(i++);
|
|
||||||
computer->serialize(settings);
|
|
||||||
}
|
|
||||||
settings.endArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ComputerManager::saveHosts()
|
void ComputerManager::saveHosts()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_DelayedFlushThread != nullptr && m_DelayedFlushThread->isRunning());
|
QSettings settings;
|
||||||
|
QReadLocker lock(&m_Lock);
|
||||||
|
|
||||||
// Punt to a worker thread because QSettings on macOS can take ages (> 500 ms)
|
settings.remove(SER_HOSTS);
|
||||||
// to persist our host list to disk (especially when a host has a bunch of apps).
|
settings.beginWriteArray(SER_HOSTS);
|
||||||
QMutexLocker locker(&m_DelayedFlushMutex);
|
int i = 0;
|
||||||
m_NeedsDelayedFlush = true;
|
for (const NvComputer* computer : m_KnownHosts) {
|
||||||
m_DelayedFlushCondition.wakeOne();
|
settings.setArrayIndex(i++);
|
||||||
|
computer->serialize(settings);
|
||||||
|
}
|
||||||
|
settings.endArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
QHostAddress ComputerManager::getBestGlobalAddressV6(QVector<QHostAddress> &addresses)
|
QHostAddress ComputerManager::getBestGlobalAddressV6(QVector<QHostAddress> &addresses)
|
||||||
@@ -496,7 +440,7 @@ void ComputerManager::clientSideAttributeUpdated(NvComputer* computer)
|
|||||||
|
|
||||||
void ComputerManager::handleAboutToQuit()
|
void ComputerManager::handleAboutToQuit()
|
||||||
{
|
{
|
||||||
QReadLocker lock(&m_Lock);
|
QWriteLocker lock(&m_Lock);
|
||||||
|
|
||||||
// Interrupt polling threads immediately, so they
|
// Interrupt polling threads immediately, so they
|
||||||
// avoid making additional requests while quitting
|
// avoid making additional requests while quitting
|
||||||
@@ -511,8 +455,7 @@ class PendingPairingTask : public QObject, public QRunnable
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
PendingPairingTask(ComputerManager* computerManager, NvComputer* computer, QString pin)
|
PendingPairingTask(ComputerManager* computerManager, NvComputer* computer, QString pin)
|
||||||
: m_ComputerManager(computerManager),
|
: m_Computer(computer),
|
||||||
m_Computer(computer),
|
|
||||||
m_Pin(pin)
|
m_Pin(pin)
|
||||||
{
|
{
|
||||||
connect(this, &PendingPairingTask::pairingCompleted,
|
connect(this, &PendingPairingTask::pairingCompleted,
|
||||||
@@ -532,34 +475,25 @@ private:
|
|||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case NvPairingManager::PairState::PIN_WRONG:
|
case NvPairingManager::PairState::PIN_WRONG:
|
||||||
emit pairingCompleted(m_Computer, tr("The PIN from the PC didn't match. Please try again."));
|
emit pairingCompleted(m_Computer, "The PIN from the PC didn't match. Please try again.");
|
||||||
break;
|
break;
|
||||||
case NvPairingManager::PairState::FAILED:
|
case NvPairingManager::PairState::FAILED:
|
||||||
if (m_Computer->currentGameId != 0) {
|
emit pairingCompleted(m_Computer, "Pairing failed. Please try again.");
|
||||||
emit pairingCompleted(m_Computer, tr("You cannot pair while a previous session is still running on the host PC. Quit any running games or reboot the host PC, then try pairing again."));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
emit pairingCompleted(m_Computer, tr("Pairing failed. Please try again."));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case NvPairingManager::PairState::ALREADY_IN_PROGRESS:
|
case NvPairingManager::PairState::ALREADY_IN_PROGRESS:
|
||||||
emit pairingCompleted(m_Computer, tr("Another pairing attempt is already in progress."));
|
emit pairingCompleted(m_Computer, "Another pairing attempt is already in progress.");
|
||||||
break;
|
break;
|
||||||
case NvPairingManager::PairState::PAIRED:
|
case NvPairingManager::PairState::PAIRED:
|
||||||
// Persist the newly pinned server certificate for this host
|
|
||||||
m_ComputerManager->saveHosts();
|
|
||||||
|
|
||||||
emit pairingCompleted(m_Computer, nullptr);
|
emit pairingCompleted(m_Computer, nullptr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (const GfeHttpResponseException& e) {
|
} catch (const GfeHttpResponseException& e) {
|
||||||
emit pairingCompleted(m_Computer, tr("GeForce Experience returned error: %1").arg(e.toQString()));
|
emit pairingCompleted(m_Computer, "GeForce Experience returned error: " + e.toQString());
|
||||||
} catch (const QtNetworkReplyException& e) {
|
} catch (const QtNetworkReplyException& e) {
|
||||||
emit pairingCompleted(m_Computer, e.toQString());
|
emit pairingCompleted(m_Computer, e.toQString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ComputerManager* m_ComputerManager;
|
|
||||||
NvComputer* m_Computer;
|
NvComputer* m_Computer;
|
||||||
QString m_Pin;
|
QString m_Pin;
|
||||||
};
|
};
|
||||||
@@ -677,15 +611,12 @@ public:
|
|||||||
: m_ComputerManager(computerManager),
|
: m_ComputerManager(computerManager),
|
||||||
m_Address(address),
|
m_Address(address),
|
||||||
m_MdnsIpv6Address(mdnsIpv6Address),
|
m_MdnsIpv6Address(mdnsIpv6Address),
|
||||||
m_Mdns(mdns),
|
m_Mdns(mdns)
|
||||||
m_AboutToQuit(false)
|
|
||||||
{
|
{
|
||||||
connect(this, &PendingAddTask::computerAddCompleted,
|
connect(this, &PendingAddTask::computerAddCompleted,
|
||||||
computerManager, &ComputerManager::computerAddCompleted);
|
computerManager, &ComputerManager::computerAddCompleted);
|
||||||
connect(this, &PendingAddTask::computerStateChanged,
|
connect(this, &PendingAddTask::computerStateChanged,
|
||||||
computerManager, &ComputerManager::handleComputerStateChanged);
|
computerManager, &ComputerManager::handleComputerStateChanged);
|
||||||
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
|
|
||||||
this, &PendingAddTask::handleAboutToQuit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -694,20 +625,9 @@ signals:
|
|||||||
void computerStateChanged(NvComputer* computer);
|
void computerStateChanged(NvComputer* computer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleAboutToQuit()
|
|
||||||
{
|
|
||||||
m_AboutToQuit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString fetchServerInfo(NvHTTP& http)
|
QString fetchServerInfo(NvHTTP& http)
|
||||||
{
|
{
|
||||||
QString serverInfo;
|
QString serverInfo;
|
||||||
|
|
||||||
// Do nothing if we're quitting
|
|
||||||
if (m_AboutToQuit) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// There's a race condition between GameStream servers reporting presence over
|
// There's a race condition between GameStream servers reporting presence over
|
||||||
// mDNS and the HTTPS server being ready to respond to our queries. To work
|
// mDNS and the HTTPS server being ready to respond to our queries. To work
|
||||||
@@ -829,31 +749,16 @@ private:
|
|||||||
hostAddress.isInSubnet(QHostAddress("192.168.0.0"), 16);
|
hostAddress.isInSubnet(QHostAddress("192.168.0.0"), 16);
|
||||||
|
|
||||||
{
|
{
|
||||||
// Check if this PC already exists using opportunistic read lock
|
// Check if this PC already exists
|
||||||
m_ComputerManager->m_Lock.lockForRead();
|
QWriteLocker lock(&m_ComputerManager->m_Lock);
|
||||||
NvComputer* existingComputer = m_ComputerManager->m_KnownHosts.value(newComputer->uuid);
|
NvComputer* existingComputer = m_ComputerManager->m_KnownHosts.value(newComputer->uuid);
|
||||||
|
|
||||||
// If it doesn't already exist, convert to a write lock in preparation for updating.
|
|
||||||
//
|
|
||||||
// NB: ComputerManager's lock protects the host map itself, not the elements inside.
|
|
||||||
// Those are protected by their individual locks. Since we only mutate the map itself
|
|
||||||
// when the PC doesn't exist, we need the lock in write-mode for that case only.
|
|
||||||
if (existingComputer == nullptr) {
|
|
||||||
m_ComputerManager->m_Lock.unlock();
|
|
||||||
m_ComputerManager->m_Lock.lockForWrite();
|
|
||||||
|
|
||||||
// Since we had to unlock to lock for write, someone could have raced and added
|
|
||||||
// this PC before us. We have to check again whether it already exists.
|
|
||||||
existingComputer = m_ComputerManager->m_KnownHosts.value(newComputer->uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingComputer != nullptr) {
|
if (existingComputer != nullptr) {
|
||||||
// Fold it into the existing PC
|
// Fold it into the existing PC
|
||||||
bool changed = existingComputer->update(*newComputer);
|
bool changed = existingComputer->update(*newComputer);
|
||||||
delete newComputer;
|
delete newComputer;
|
||||||
|
|
||||||
// Drop the lock before notifying
|
// Drop the lock before notifying
|
||||||
m_ComputerManager->m_Lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
// For non-mDNS clients, let them know it succeeded
|
// For non-mDNS clients, let them know it succeeded
|
||||||
if (!m_Mdns) {
|
if (!m_Mdns) {
|
||||||
@@ -874,15 +779,17 @@ private:
|
|||||||
m_ComputerManager->startPollingComputer(newComputer);
|
m_ComputerManager->startPollingComputer(newComputer);
|
||||||
|
|
||||||
// Drop the lock before notifying
|
// Drop the lock before notifying
|
||||||
m_ComputerManager->m_Lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
// If this wasn't added via mDNS but it is a RFC 1918 IPv4 address and not a VPN,
|
// If this wasn't added via mDNS but it is a RFC 1918 IPv4 address and not a VPN,
|
||||||
// go ahead and do the STUN request now to populate an external address.
|
// go ahead and do the STUN request now to populate an external address.
|
||||||
if (!m_Mdns && addressIsSiteLocalV4 && newComputer->getActiveAddressReachability() != NvComputer::RI_VPN) {
|
if (!m_Mdns && addressIsSiteLocalV4 && !newComputer->isReachableOverVpn()) {
|
||||||
quint32 addr;
|
quint32 addr;
|
||||||
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
|
int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr);
|
||||||
if (err == 0) {
|
if (err == 0) {
|
||||||
|
lock.relock();
|
||||||
newComputer->setRemoteAddress(QHostAddress(qFromBigEndian(addr)));
|
newComputer->setRemoteAddress(QHostAddress(qFromBigEndian(addr)));
|
||||||
|
lock.unlock();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qWarning() << "STUN failed to get WAN address:" << err;
|
qWarning() << "STUN failed to get WAN address:" << err;
|
||||||
@@ -904,7 +811,6 @@ private:
|
|||||||
NvAddress m_Address;
|
NvAddress m_Address;
|
||||||
NvAddress m_MdnsIpv6Address;
|
NvAddress m_MdnsIpv6Address;
|
||||||
bool m_Mdns;
|
bool m_Mdns;
|
||||||
bool m_AboutToQuit;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void ComputerManager::addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address)
|
void ComputerManager::addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address)
|
||||||
@@ -915,14 +821,4 @@ void ComputerManager::addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv
|
|||||||
QThreadPool::globalInstance()->start(addTask);
|
QThreadPool::globalInstance()->start(addTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use QRandomGenerator when we drop Qt 5.9 support
|
|
||||||
QString ComputerManager::generatePinString()
|
|
||||||
{
|
|
||||||
std::uniform_int_distribution<int> dist(0, 9999);
|
|
||||||
std::random_device rd;
|
|
||||||
std::mt19937 engine(rd());
|
|
||||||
|
|
||||||
return QString::asprintf("%04u", dist(engine));
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "computermanager.moc"
|
#include "computermanager.moc"
|
||||||
@@ -15,27 +15,6 @@
|
|||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QRunnable>
|
#include <QRunnable>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QMutex>
|
|
||||||
#include <QWaitCondition>
|
|
||||||
|
|
||||||
class ComputerManager;
|
|
||||||
|
|
||||||
class DelayedFlushThread : public QThread
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
DelayedFlushThread(ComputerManager* cm)
|
|
||||||
: m_ComputerManager(cm)
|
|
||||||
{
|
|
||||||
setObjectName("CM Delayed Flush Thread");
|
|
||||||
}
|
|
||||||
|
|
||||||
void run();
|
|
||||||
|
|
||||||
private:
|
|
||||||
ComputerManager* m_ComputerManager;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MdnsPendingComputer : public QObject
|
class MdnsPendingComputer : public QObject
|
||||||
{
|
{
|
||||||
@@ -187,8 +166,6 @@ class ComputerManager : public QObject
|
|||||||
|
|
||||||
friend class DeferredHostDeletionTask;
|
friend class DeferredHostDeletionTask;
|
||||||
friend class PendingAddTask;
|
friend class PendingAddTask;
|
||||||
friend class PendingPairingTask;
|
|
||||||
friend class DelayedFlushThread;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ComputerManager(QObject *parent = nullptr);
|
explicit ComputerManager(QObject *parent = nullptr);
|
||||||
@@ -203,8 +180,6 @@ public:
|
|||||||
|
|
||||||
void addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address = NvAddress());
|
void addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address = NvAddress());
|
||||||
|
|
||||||
QString generatePinString();
|
|
||||||
|
|
||||||
void pairHost(NvComputer* computer, QString pin);
|
void pairHost(NvComputer* computer, QString pin);
|
||||||
|
|
||||||
void quitRunningApp(NvComputer* computer);
|
void quitRunningApp(NvComputer* computer);
|
||||||
@@ -250,8 +225,4 @@ private:
|
|||||||
QMdnsEngine::Cache m_MdnsCache;
|
QMdnsEngine::Cache m_MdnsCache;
|
||||||
QVector<MdnsPendingComputer*> m_PendingResolution;
|
QVector<MdnsPendingComputer*> m_PendingResolution;
|
||||||
CompatFetcher m_CompatFetcher;
|
CompatFetcher m_CompatFetcher;
|
||||||
DelayedFlushThread* m_DelayedFlushThread;
|
|
||||||
QMutex m_DelayedFlushMutex;
|
|
||||||
QWaitCondition m_DelayedFlushCondition;
|
|
||||||
bool m_NeedsDelayedFlush;
|
|
||||||
};
|
};
|
||||||
+33
-66
@@ -21,7 +21,6 @@
|
|||||||
#define SER_APPLIST "apps"
|
#define SER_APPLIST "apps"
|
||||||
#define SER_SRVCERT "srvcert"
|
#define SER_SRVCERT "srvcert"
|
||||||
#define SER_CUSTOMNAME "customname"
|
#define SER_CUSTOMNAME "customname"
|
||||||
#define SER_NVIDIASOFTWARE "nvidiasw"
|
|
||||||
|
|
||||||
NvComputer::NvComputer(QSettings& settings)
|
NvComputer::NvComputer(QSettings& settings)
|
||||||
{
|
{
|
||||||
@@ -38,7 +37,6 @@ NvComputer::NvComputer(QSettings& settings)
|
|||||||
this->manualAddress = NvAddress(settings.value(SER_MANUALADDR).toString(),
|
this->manualAddress = NvAddress(settings.value(SER_MANUALADDR).toString(),
|
||||||
settings.value(SER_MANUALPORT, QVariant(DEFAULT_HTTP_PORT)).toUInt());
|
settings.value(SER_MANUALPORT, QVariant(DEFAULT_HTTP_PORT)).toUInt());
|
||||||
this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray());
|
this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray());
|
||||||
this->isNvidiaServerSoftware = settings.value(SER_NVIDIASOFTWARE).toBool();
|
|
||||||
|
|
||||||
int appCount = settings.beginReadArray(SER_APPLIST);
|
int appCount = settings.beginReadArray(SER_APPLIST);
|
||||||
this->appList.reserve(appCount);
|
this->appList.reserve(appCount);
|
||||||
@@ -67,8 +65,6 @@ NvComputer::NvComputer(QSettings& settings)
|
|||||||
|
|
||||||
void NvComputer::setRemoteAddress(QHostAddress address)
|
void NvComputer::setRemoteAddress(QHostAddress address)
|
||||||
{
|
{
|
||||||
QWriteLocker lock(&this->lock);
|
|
||||||
|
|
||||||
Q_ASSERT(this->externalPort != 0);
|
Q_ASSERT(this->externalPort != 0);
|
||||||
|
|
||||||
this->remoteAddress = NvAddress(address, this->externalPort);
|
this->remoteAddress = NvAddress(address, this->externalPort);
|
||||||
@@ -91,7 +87,6 @@ void NvComputer::serialize(QSettings& settings) const
|
|||||||
settings.setValue(SER_MANUALADDR, manualAddress.address());
|
settings.setValue(SER_MANUALADDR, manualAddress.address());
|
||||||
settings.setValue(SER_MANUALPORT, manualAddress.port());
|
settings.setValue(SER_MANUALPORT, manualAddress.port());
|
||||||
settings.setValue(SER_SRVCERT, serverCert.toPem());
|
settings.setValue(SER_SRVCERT, serverCert.toPem());
|
||||||
settings.setValue(SER_NVIDIASOFTWARE, isNvidiaServerSoftware);
|
|
||||||
|
|
||||||
// Avoid deleting an existing applist if we couldn't get one
|
// Avoid deleting an existing applist if we couldn't get one
|
||||||
if (!appList.isEmpty()) {
|
if (!appList.isEmpty()) {
|
||||||
@@ -180,11 +175,6 @@ NvComputer::NvComputer(NvHTTP& http, QString serverInfo)
|
|||||||
this->remoteAddress = NvAddress();
|
this->remoteAddress = NvAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Real Nvidia host software (GeForce Experience and RTX Experience) both use the 'Mjolnir'
|
|
||||||
// codename in the state field and no version of Sunshine does. We can use this to bypass
|
|
||||||
// some assumptions about Nvidia hardware that don't apply to Sunshine hosts.
|
|
||||||
this->isNvidiaServerSoftware = NvHTTP::getXmlString(serverInfo, "state").contains("MJOLNIR");
|
|
||||||
|
|
||||||
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
|
this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ?
|
||||||
PS_PAIRED : PS_NOT_PAIRED;
|
PS_PAIRED : PS_NOT_PAIRED;
|
||||||
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
|
this->currentGameId = NvHTTP::getCurrentGame(serverInfo);
|
||||||
@@ -197,29 +187,16 @@ NvComputer::NvComputer(NvHTTP& http, QString serverInfo)
|
|||||||
this->isSupportedServerVersion = CompatFetcher::isGfeVersionSupported(this->gfeVersion);
|
this->isSupportedServerVersion = CompatFetcher::isGfeVersionSupported(this->gfeVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NvComputer::wake() const
|
bool NvComputer::wake()
|
||||||
{
|
{
|
||||||
QByteArray wolPayload;
|
if (state == NvComputer::CS_ONLINE) {
|
||||||
|
qWarning() << name << "is already online";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
if (macAddress.isEmpty()) {
|
||||||
QReadLocker readLocker(&lock);
|
qWarning() << name << "has no MAC address stored";
|
||||||
|
return false;
|
||||||
if (state == NvComputer::CS_ONLINE) {
|
|
||||||
qWarning() << name << "is already online";
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (macAddress.isEmpty()) {
|
|
||||||
qWarning() << name << "has no MAC address stored";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the WoL payload
|
|
||||||
wolPayload.append(QByteArray::fromHex("FFFFFFFFFFFF"));
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
wolPayload.append(macAddress);
|
|
||||||
}
|
|
||||||
Q_ASSERT(wolPayload.count() == 102);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const quint16 WOL_PORTS[] = {
|
const quint16 WOL_PORTS[] = {
|
||||||
@@ -228,6 +205,14 @@ bool NvComputer::wake() const
|
|||||||
47009, // Port opened by Moonlight Internet Hosting Tool for WoL (non-privileged port)
|
47009, // Port opened by Moonlight Internet Hosting Tool for WoL (non-privileged port)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create the WoL payload
|
||||||
|
QByteArray wolPayload;
|
||||||
|
wolPayload.append(QByteArray::fromHex("FFFFFFFFFFFF"));
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
wolPayload.append(macAddress);
|
||||||
|
}
|
||||||
|
Q_ASSERT(wolPayload.count() == 102);
|
||||||
|
|
||||||
// Add the addresses that we know this host to be
|
// Add the addresses that we know this host to be
|
||||||
// and broadcast addresses for this link just in
|
// and broadcast addresses for this link just in
|
||||||
// case the host has timed out in ARP entries.
|
// case the host has timed out in ARP entries.
|
||||||
@@ -293,25 +278,16 @@ bool NvComputer::wake() const
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
|
bool NvComputer::isReachableOverVpn()
|
||||||
{
|
{
|
||||||
NvAddress copyOfActiveAddress;
|
if (activeAddress.isNull()) {
|
||||||
|
return false;
|
||||||
{
|
|
||||||
QReadLocker readLocker(&lock);
|
|
||||||
|
|
||||||
if (activeAddress.isNull()) {
|
|
||||||
return ReachabilityType::RI_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab a copy of the active address to avoid having to hold
|
|
||||||
// the computer lock while doing socket operations
|
|
||||||
copyOfActiveAddress = activeAddress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QTcpSocket s;
|
QTcpSocket s;
|
||||||
|
|
||||||
s.setProxy(QNetworkProxy::NoProxy);
|
s.setProxy(QNetworkProxy::NoProxy);
|
||||||
s.connectToHost(copyOfActiveAddress.address(), copyOfActiveAddress.port());
|
s.connectToHost(activeAddress.address(), activeAddress.port());
|
||||||
if (s.waitForConnected(3000)) {
|
if (s.waitForConnected(3000)) {
|
||||||
Q_ASSERT(!s.localAddress().isNull());
|
Q_ASSERT(!s.localAddress().isNull());
|
||||||
|
|
||||||
@@ -332,55 +308,48 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
|
|||||||
if (nic.type() == QNetworkInterface::Virtual ||
|
if (nic.type() == QNetworkInterface::Virtual ||
|
||||||
nic.type() == QNetworkInterface::Ppp) {
|
nic.type() == QNetworkInterface::Ppp) {
|
||||||
// Treat PPP and virtual interfaces as likely VPNs
|
// Treat PPP and virtual interfaces as likely VPNs
|
||||||
return ReachabilityType::RI_VPN;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nic.maximumTransmissionUnit() != 0 && nic.maximumTransmissionUnit() < 1500) {
|
if (nic.maximumTransmissionUnit() != 0 && nic.maximumTransmissionUnit() < 1500) {
|
||||||
// Treat MTUs under 1500 as likely VPNs
|
// Treat MTUs under 1500 as likely VPNs
|
||||||
return ReachabilityType::RI_VPN;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (nic.flags() & QNetworkInterface::IsPointToPoint) {
|
if (nic.flags() & QNetworkInterface::IsPointToPoint) {
|
||||||
// Treat point-to-point links as likely VPNs.
|
// Treat point-to-point links as likely VPNs.
|
||||||
// This check detects OpenVPN on Unix-like OSes.
|
// This check detects OpenVPN on Unix-like OSes.
|
||||||
return ReachabilityType::RI_VPN;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nic.hardwareAddress().startsWith("00:FF", Qt::CaseInsensitive)) {
|
if (nic.hardwareAddress().startsWith("00:FF", Qt::CaseInsensitive)) {
|
||||||
// OpenVPN TAP interfaces have a MAC address starting with 00:FF on Windows
|
// OpenVPN TAP interfaces have a MAC address starting with 00:FF on Windows
|
||||||
return ReachabilityType::RI_VPN;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nic.humanReadableName().startsWith("ZeroTier")) {
|
if (nic.humanReadableName().startsWith("ZeroTier")) {
|
||||||
// ZeroTier interfaces always start with "ZeroTier"
|
// ZeroTier interfaces always start with "ZeroTier"
|
||||||
return ReachabilityType::RI_VPN;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nic.humanReadableName().contains("VPN")) {
|
if (nic.humanReadableName().contains("VPN")) {
|
||||||
// This one is just a final VPN heuristic if all else fails
|
// This one is just a final heuristic if all else fails
|
||||||
return ReachabilityType::RI_VPN;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Didn't meet any of our VPN heuristics. Let's see if it's on-link.
|
// Didn't meet any of our VPN heuristics
|
||||||
Q_ASSERT(addr.prefixLength() >= 0);
|
return false;
|
||||||
if (addr.prefixLength() >= 0 && s.localAddress().isInSubnet(addr.ip(), addr.prefixLength())) {
|
|
||||||
return ReachabilityType::RI_LAN;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to unknown if nothing else matched
|
|
||||||
return ReachabilityType::RI_UNKNOWN;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qWarning() << "No match found for address:" << s.localAddress();
|
qWarning() << "No match found for address:" << s.localAddress();
|
||||||
return ReachabilityType::RI_UNKNOWN;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// If we fail to connect, just pretend that it's not a VPN
|
// If we fail to connect, just pretend that it's not a VPN
|
||||||
qWarning() << "Unable to check for reachability within 3 seconds";
|
return false;
|
||||||
return ReachabilityType::RI_UNKNOWN;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +375,6 @@ bool NvComputer::updateAppList(QVector<NvApp> newAppList) {
|
|||||||
|
|
||||||
QVector<NvAddress> NvComputer::uniqueAddresses() const
|
QVector<NvAddress> NvComputer::uniqueAddresses() const
|
||||||
{
|
{
|
||||||
QReadLocker readLocker(&lock);
|
|
||||||
QVector<NvAddress> uniqueAddressList;
|
QVector<NvAddress> uniqueAddressList;
|
||||||
|
|
||||||
// Start with addresses correctly ordered
|
// Start with addresses correctly ordered
|
||||||
@@ -438,7 +406,7 @@ QVector<NvAddress> NvComputer::uniqueAddresses() const
|
|||||||
return uniqueAddressList;
|
return uniqueAddressList;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NvComputer::update(const NvComputer& that)
|
bool NvComputer::update(NvComputer& that)
|
||||||
{
|
{
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
|
||||||
@@ -488,7 +456,6 @@ bool NvComputer::update(const NvComputer& that)
|
|||||||
ASSIGN_IF_CHANGED(gfeVersion);
|
ASSIGN_IF_CHANGED(gfeVersion);
|
||||||
ASSIGN_IF_CHANGED(appVersion);
|
ASSIGN_IF_CHANGED(appVersion);
|
||||||
ASSIGN_IF_CHANGED(isSupportedServerVersion);
|
ASSIGN_IF_CHANGED(isSupportedServerVersion);
|
||||||
ASSIGN_IF_CHANGED(isNvidiaServerSoftware);
|
|
||||||
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
|
ASSIGN_IF_CHANGED(maxLumaPixelsHEVC);
|
||||||
ASSIGN_IF_CHANGED(gpuModel);
|
ASSIGN_IF_CHANGED(gpuModel);
|
||||||
ASSIGN_IF_CHANGED_AND_NONNULL(serverCert);
|
ASSIGN_IF_CHANGED_AND_NONNULL(serverCert);
|
||||||
|
|||||||
@@ -30,20 +30,13 @@ public:
|
|||||||
setRemoteAddress(QHostAddress);
|
setRemoteAddress(QHostAddress);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
update(const NvComputer& that);
|
update(NvComputer& that);
|
||||||
|
|
||||||
bool
|
bool
|
||||||
wake() const;
|
wake();
|
||||||
|
|
||||||
enum ReachabilityType
|
bool
|
||||||
{
|
isReachableOverVpn();
|
||||||
RI_UNKNOWN,
|
|
||||||
RI_LAN,
|
|
||||||
RI_VPN,
|
|
||||||
};
|
|
||||||
|
|
||||||
ReachabilityType
|
|
||||||
getActiveAddressReachability() const;
|
|
||||||
|
|
||||||
QVector<NvAddress>
|
QVector<NvAddress>
|
||||||
uniqueAddresses() const;
|
uniqueAddresses() const;
|
||||||
@@ -90,7 +83,6 @@ public:
|
|||||||
QString uuid;
|
QString uuid;
|
||||||
QSslCertificate serverCert;
|
QSslCertificate serverCert;
|
||||||
QVector<NvApp> appList;
|
QVector<NvApp> appList;
|
||||||
bool isNvidiaServerSoftware;
|
|
||||||
|
|
||||||
// Synchronization
|
// Synchronization
|
||||||
mutable QReadWriteLock lock;
|
mutable QReadWriteLock lock;
|
||||||
|
|||||||
@@ -551,7 +551,7 @@ NvHTTP::openConnection(QUrl baseUrl,
|
|||||||
if (reply->error() != QNetworkReply::NoError)
|
if (reply->error() != QNetworkReply::NoError)
|
||||||
{
|
{
|
||||||
if (logLevel >= NvLogLevel::NVLL_ERROR) {
|
if (logLevel >= NvLogLevel::NVLL_ERROR) {
|
||||||
qWarning() << command << "request failed with error:" << reply->error();
|
qWarning() << command << " request failed with error " << reply->error();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::SslHandshakeFailedError) {
|
if (reply->error() == QNetworkReply::SslHandshakeFailedError) {
|
||||||
|
|||||||
@@ -71,9 +71,16 @@ SystemProperties::SystemProperties()
|
|||||||
querySdlVideoInfo();
|
querySdlVideoInfo();
|
||||||
|
|
||||||
Q_ASSERT(maximumStreamingFrameRate >= 60);
|
Q_ASSERT(maximumStreamingFrameRate >= 60);
|
||||||
|
Q_ASSERT(!monitorDesktopResolutions.isEmpty());
|
||||||
Q_ASSERT(!monitorNativeResolutions.isEmpty());
|
Q_ASSERT(!monitorNativeResolutions.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QRect SystemProperties::getDesktopResolution(int displayIndex)
|
||||||
|
{
|
||||||
|
// Returns default constructed QRect if out of bounds
|
||||||
|
return monitorDesktopResolutions.value(displayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
QRect SystemProperties::getNativeResolution(int displayIndex)
|
QRect SystemProperties::getNativeResolution(int displayIndex)
|
||||||
{
|
{
|
||||||
// Returns default constructed QRect if out of bounds
|
// Returns default constructed QRect if out of bounds
|
||||||
@@ -186,6 +193,7 @@ void SystemProperties::refreshDisplaysInternal()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
monitorDesktopResolutions.clear();
|
||||||
monitorNativeResolutions.clear();
|
monitorNativeResolutions.clear();
|
||||||
|
|
||||||
// Never let the maximum drop below 60 FPS
|
// Never let the maximum drop below 60 FPS
|
||||||
@@ -194,8 +202,26 @@ void SystemProperties::refreshDisplaysInternal()
|
|||||||
SDL_DisplayMode bestMode;
|
SDL_DisplayMode bestMode;
|
||||||
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
|
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
|
||||||
SDL_DisplayMode desktopMode;
|
SDL_DisplayMode desktopMode;
|
||||||
|
int err;
|
||||||
|
|
||||||
if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode)) {
|
err = SDL_GetDesktopDisplayMode(displayIndex, &desktopMode);
|
||||||
|
if (err == 0) {
|
||||||
|
if (desktopMode.w <= 8192 && desktopMode.h <= 8192) {
|
||||||
|
monitorDesktopResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Skipping resolution over 8K: %dx%d",
|
||||||
|
desktopMode.w, desktopMode.h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL_GetDesktopDisplayMode() failed: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StreamUtils::getRealDesktopMode(displayIndex, &desktopMode)) {
|
||||||
if (desktopMode.w <= 8192 && desktopMode.h <= 8192) {
|
if (desktopMode.w <= 8192 && desktopMode.h <= 8192) {
|
||||||
monitorNativeResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
|
monitorNativeResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public:
|
|||||||
Q_PROPERTY(bool supportsHdr MEMBER supportsHdr CONSTANT)
|
Q_PROPERTY(bool supportsHdr MEMBER supportsHdr CONSTANT)
|
||||||
|
|
||||||
Q_INVOKABLE void refreshDisplays();
|
Q_INVOKABLE void refreshDisplays();
|
||||||
|
Q_INVOKABLE QRect getDesktopResolution(int displayIndex);
|
||||||
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
|
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -51,6 +52,7 @@ private:
|
|||||||
QString unmappedGamepads;
|
QString unmappedGamepads;
|
||||||
int maximumStreamingFrameRate;
|
int maximumStreamingFrameRate;
|
||||||
QSize maximumResolution;
|
QSize maximumResolution;
|
||||||
|
QList<QRect> monitorDesktopResolutions;
|
||||||
QList<QRect> monitorNativeResolutions;
|
QList<QRect> monitorNativeResolutions;
|
||||||
QString versionString;
|
QString versionString;
|
||||||
bool supportsHdr;
|
bool supportsHdr;
|
||||||
|
|||||||
@@ -164,10 +164,8 @@ GlobalCommandLineParser::ParseResult GlobalCommandLineParser::parse(const QStrin
|
|||||||
"Starts Moonlight normally if no arguments are given.\n"
|
"Starts Moonlight normally if no arguments are given.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Available actions:\n"
|
"Available actions:\n"
|
||||||
" list List the available apps on a host\n"
|
|
||||||
" quit Quit the currently running app\n"
|
" quit Quit the currently running app\n"
|
||||||
" stream Start streaming an app\n"
|
" stream Start streaming an app\n"
|
||||||
" pair Pair a new host\n"
|
|
||||||
"\n"
|
"\n"
|
||||||
"See 'moonlight <action> --help' for help of specific action."
|
"See 'moonlight <action> --help' for help of specific action."
|
||||||
);
|
);
|
||||||
@@ -195,10 +193,6 @@ GlobalCommandLineParser::ParseResult GlobalCommandLineParser::parse(const QStrin
|
|||||||
return QuitRequested;
|
return QuitRequested;
|
||||||
} else if (action == "stream") {
|
} else if (action == "stream") {
|
||||||
return StreamRequested;
|
return StreamRequested;
|
||||||
} else if (action == "pair") {
|
|
||||||
return PairRequested;
|
|
||||||
} else if (action == "list") {
|
|
||||||
return ListRequested;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,58 +242,6 @@ QString QuitCommandLineParser::getHost() const
|
|||||||
return m_Host;
|
return m_Host;
|
||||||
}
|
}
|
||||||
|
|
||||||
PairCommandLineParser::PairCommandLineParser()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
PairCommandLineParser::~PairCommandLineParser()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void PairCommandLineParser::parse(const QStringList &args)
|
|
||||||
{
|
|
||||||
CommandLineParser parser;
|
|
||||||
parser.setupCommonOptions();
|
|
||||||
parser.setApplicationDescription(
|
|
||||||
"\n"
|
|
||||||
"Pair with the specified host."
|
|
||||||
);
|
|
||||||
parser.addPositionalArgument("pair", "pair host");
|
|
||||||
parser.addPositionalArgument("host", "Host computer name, UUID, or IP address", "<host>");
|
|
||||||
parser.addValueOption("pin", "4 digit pairing PIN");
|
|
||||||
|
|
||||||
if (!parser.parse(args)) {
|
|
||||||
parser.showError(parser.errorText());
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.handleUnknownOptions();
|
|
||||||
|
|
||||||
// This method will not return and terminates the process if --version or
|
|
||||||
// --help is specified
|
|
||||||
parser.handleHelpAndVersionOptions();
|
|
||||||
|
|
||||||
// Verify that host has been provided
|
|
||||||
auto posArgs = parser.positionalArguments();
|
|
||||||
if (posArgs.length() < 2) {
|
|
||||||
parser.showError("Host not provided");
|
|
||||||
}
|
|
||||||
m_Host = parser.positionalArguments().at(1);
|
|
||||||
m_PredefinedPin = parser.value("pin");
|
|
||||||
if (!m_PredefinedPin.isEmpty() && m_PredefinedPin.length() != 4) {
|
|
||||||
parser.showError("PIN must be 4 digits");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString PairCommandLineParser::getHost() const
|
|
||||||
{
|
|
||||||
return m_Host;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString PairCommandLineParser::getPredefinedPin() const
|
|
||||||
{
|
|
||||||
return m_PredefinedPin;
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamCommandLineParser::StreamCommandLineParser()
|
StreamCommandLineParser::StreamCommandLineParser()
|
||||||
{
|
{
|
||||||
m_WindowModeMap = {
|
m_WindowModeMap = {
|
||||||
@@ -526,62 +468,3 @@ QString StreamCommandLineParser::getAppName() const
|
|||||||
{
|
{
|
||||||
return m_AppName;
|
return m_AppName;
|
||||||
}
|
}
|
||||||
|
|
||||||
ListCommandLineParser::ListCommandLineParser()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ListCommandLineParser::~ListCommandLineParser()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ListCommandLineParser::parse(const QStringList &args)
|
|
||||||
{
|
|
||||||
CommandLineParser parser;
|
|
||||||
parser.setupCommonOptions();
|
|
||||||
parser.setApplicationDescription(
|
|
||||||
"\n"
|
|
||||||
"List the available apps on the given host."
|
|
||||||
);
|
|
||||||
parser.addPositionalArgument("list", "list available apps");
|
|
||||||
parser.addPositionalArgument("host", "Host computer name, UUID, or IP address", "<host>");
|
|
||||||
|
|
||||||
parser.addFlagOption("csv", "Print as CSV with additional information");
|
|
||||||
parser.addFlagOption("verbose", "Displays additional information");
|
|
||||||
|
|
||||||
if (!parser.parse(args)) {
|
|
||||||
parser.showError(parser.errorText());
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.handleUnknownOptions();
|
|
||||||
|
|
||||||
|
|
||||||
m_PrintCSV = parser.isSet("csv");
|
|
||||||
m_Verbose = parser.isSet("verbose");
|
|
||||||
|
|
||||||
// This method will not return and terminates the process if --version or
|
|
||||||
// --help is specified
|
|
||||||
parser.handleHelpAndVersionOptions();
|
|
||||||
|
|
||||||
// Verify that host has been provided
|
|
||||||
auto posArgs = parser.positionalArguments();
|
|
||||||
if (posArgs.length() < 2) {
|
|
||||||
parser.showError("Host not provided");
|
|
||||||
}
|
|
||||||
m_Host = parser.positionalArguments().at(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ListCommandLineParser::getHost() const
|
|
||||||
{
|
|
||||||
return m_Host;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ListCommandLineParser::isPrintCSV() const
|
|
||||||
{
|
|
||||||
return m_PrintCSV;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ListCommandLineParser::isVerbose() const
|
|
||||||
{
|
|
||||||
return m_Verbose;
|
|
||||||
}
|
|
||||||
@@ -12,8 +12,6 @@ public:
|
|||||||
NormalStartRequested,
|
NormalStartRequested,
|
||||||
StreamRequested,
|
StreamRequested,
|
||||||
QuitRequested,
|
QuitRequested,
|
||||||
PairRequested,
|
|
||||||
ListRequested,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GlobalCommandLineParser();
|
GlobalCommandLineParser();
|
||||||
@@ -37,22 +35,6 @@ private:
|
|||||||
QString m_Host;
|
QString m_Host;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PairCommandLineParser
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PairCommandLineParser();
|
|
||||||
virtual ~PairCommandLineParser();
|
|
||||||
|
|
||||||
void parse(const QStringList &args);
|
|
||||||
|
|
||||||
QString getHost() const;
|
|
||||||
QString getPredefinedPin() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString m_Host;
|
|
||||||
QString m_PredefinedPin;
|
|
||||||
};
|
|
||||||
|
|
||||||
class StreamCommandLineParser
|
class StreamCommandLineParser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -73,21 +55,3 @@ private:
|
|||||||
QMap<QString, StreamingPreferences::VideoDecoderSelection> m_VideoDecoderMap;
|
QMap<QString, StreamingPreferences::VideoDecoderSelection> m_VideoDecoderMap;
|
||||||
QMap<QString, StreamingPreferences::CaptureSysKeysMode> m_CaptureSysKeysModeMap;
|
QMap<QString, StreamingPreferences::CaptureSysKeysMode> m_CaptureSysKeysModeMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ListCommandLineParser
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ListCommandLineParser();
|
|
||||||
virtual ~ListCommandLineParser();
|
|
||||||
|
|
||||||
void parse(const QStringList &args);
|
|
||||||
|
|
||||||
QString getHost() const;
|
|
||||||
bool isPrintCSV() const;
|
|
||||||
bool isVerbose() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString m_Host;
|
|
||||||
bool m_PrintCSV;
|
|
||||||
bool m_Verbose;
|
|
||||||
};
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
#include "listapps.h"
|
|
||||||
|
|
||||||
#include "backend/boxartmanager.h"
|
|
||||||
#include "backend/computermanager.h"
|
|
||||||
#include "backend/computerseeker.h"
|
|
||||||
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#define COMPUTER_SEEK_TIMEOUT 10000
|
|
||||||
#define APP_SEEK_TIMEOUT 10000
|
|
||||||
|
|
||||||
namespace CliListApps
|
|
||||||
{
|
|
||||||
|
|
||||||
enum State {
|
|
||||||
StateInit,
|
|
||||||
StateSeekComputer,
|
|
||||||
StateListApp,
|
|
||||||
StateSeekApp,
|
|
||||||
StateFailure,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Event
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum Type {
|
|
||||||
ComputerFound,
|
|
||||||
ComputerUpdated,
|
|
||||||
ComputerSeekTimedout,
|
|
||||||
Executed,
|
|
||||||
};
|
|
||||||
|
|
||||||
Event(Type type)
|
|
||||||
: type(type), computerManager(nullptr), computer(nullptr) {}
|
|
||||||
|
|
||||||
Type type;
|
|
||||||
ComputerManager *computerManager;
|
|
||||||
NvComputer *computer;
|
|
||||||
QString errorMessage;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LauncherPrivate
|
|
||||||
{
|
|
||||||
Q_DECLARE_PUBLIC(Launcher)
|
|
||||||
|
|
||||||
public:
|
|
||||||
LauncherPrivate(Launcher *q) : q_ptr(q) {}
|
|
||||||
|
|
||||||
void handleEvent(Event event)
|
|
||||||
{
|
|
||||||
Q_Q(Launcher);
|
|
||||||
NvApp app;
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
// Occurs when CLI main calls execute
|
|
||||||
case Event::Executed:
|
|
||||||
if (m_State == StateInit) {
|
|
||||||
m_State = StateSeekComputer;
|
|
||||||
m_ComputerManager = event.computerManager;
|
|
||||||
|
|
||||||
m_ComputerSeeker = new ComputerSeeker(m_ComputerManager, m_ComputerName, q);
|
|
||||||
q->connect(m_ComputerSeeker, &ComputerSeeker::computerFound,
|
|
||||||
q, &Launcher::onComputerFound);
|
|
||||||
q->connect(m_ComputerSeeker, &ComputerSeeker::errorTimeout,
|
|
||||||
q, &Launcher::onComputerSeekTimeout);
|
|
||||||
m_ComputerSeeker->start(COMPUTER_SEEK_TIMEOUT);
|
|
||||||
|
|
||||||
q->connect(m_ComputerManager, &ComputerManager::computerStateChanged,
|
|
||||||
q, &Launcher::onComputerUpdated);
|
|
||||||
|
|
||||||
m_BoxArtManager = new BoxArtManager(q);
|
|
||||||
|
|
||||||
if (m_Arguments.isVerbose()) {
|
|
||||||
fprintf(stdout, "Establishing connection to PC...\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// Occurs when computer search timed out
|
|
||||||
case Event::ComputerSeekTimedout:
|
|
||||||
if (m_State == StateSeekComputer) {
|
|
||||||
fprintf(stderr, "%s\n", qPrintable(QString("Failed to connect to %1").arg(m_ComputerName)));
|
|
||||||
|
|
||||||
QCoreApplication::exit(-1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// Occurs when searched computer is found
|
|
||||||
case Event::ComputerFound:
|
|
||||||
if (m_State == StateSeekComputer) {
|
|
||||||
if (event.computer->pairState == NvComputer::PS_PAIRED) {
|
|
||||||
m_State = StateSeekApp;
|
|
||||||
m_Computer = event.computer;
|
|
||||||
m_TimeoutTimer->start(APP_SEEK_TIMEOUT);
|
|
||||||
if (m_Arguments.isVerbose()) {
|
|
||||||
fprintf(stdout, "Loading app list...\n");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m_State = StateFailure;
|
|
||||||
fprintf(stderr, "%s\n", qPrintable(QObject::tr("Computer %1 has not been paired. "
|
|
||||||
"Please open Moonlight to pair before retrieving games list.")
|
|
||||||
.arg(event.computer->name)));
|
|
||||||
|
|
||||||
QCoreApplication::exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// Occurs when a computer is updated
|
|
||||||
case Event::ComputerUpdated:
|
|
||||||
if (m_State == StateSeekApp) {
|
|
||||||
m_Arguments.isPrintCSV() ? printAppsCSV(m_Computer->appList) : printApps(m_Computer->appList);
|
|
||||||
|
|
||||||
QCoreApplication::exit(0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void printApps(QVector<NvApp> apps) {
|
|
||||||
for (int i = 0; i < apps.length(); i++) {
|
|
||||||
fprintf(stdout, "%s\n", qPrintable(apps[i].name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void printAppsCSV(QVector<NvApp> apps) {
|
|
||||||
fprintf(stdout, "Name, ID, HDR Support, App Collection Game, Hidden, Direct Launch, Boxart URL\n");
|
|
||||||
for (int i = 0; i < apps.length(); i++) {
|
|
||||||
printAppCSV(apps[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void printAppCSV(NvApp app) const
|
|
||||||
{
|
|
||||||
fprintf(stdout, "\"%s\",%d,%s,%s,%s,%s,\"%s\"\n", qPrintable(app.name),
|
|
||||||
app.id,
|
|
||||||
app.hdrSupported ? "true" : "false",
|
|
||||||
app.isAppCollectorGame ? "true" : "false",
|
|
||||||
app.hidden ? "true" : "false",
|
|
||||||
app.directLaunch ? "true" : "false",
|
|
||||||
qPrintable(m_BoxArtManager->loadBoxArt(m_Computer, app).toDisplayString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Launcher *q_ptr;
|
|
||||||
ComputerManager *m_ComputerManager;
|
|
||||||
QString m_ComputerName;
|
|
||||||
ComputerSeeker *m_ComputerSeeker;
|
|
||||||
BoxArtManager *m_BoxArtManager;
|
|
||||||
NvComputer *m_Computer;
|
|
||||||
State m_State;
|
|
||||||
QTimer *m_TimeoutTimer;
|
|
||||||
ListCommandLineParser m_Arguments;
|
|
||||||
};
|
|
||||||
|
|
||||||
Launcher::Launcher(QString computer, ListCommandLineParser arguments, QObject *parent)
|
|
||||||
: QObject(parent),
|
|
||||||
m_DPtr(new LauncherPrivate(this))
|
|
||||||
{
|
|
||||||
Q_D(Launcher);
|
|
||||||
d->m_ComputerName = computer;
|
|
||||||
d->m_State = StateInit;
|
|
||||||
d->m_TimeoutTimer = new QTimer(this);
|
|
||||||
d->m_TimeoutTimer->setSingleShot(true);
|
|
||||||
d->m_Arguments = arguments;
|
|
||||||
connect(d->m_TimeoutTimer, &QTimer::timeout,
|
|
||||||
this, &Launcher::onComputerSeekTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
Launcher::~Launcher()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::execute(ComputerManager *manager)
|
|
||||||
{
|
|
||||||
Q_D(Launcher);
|
|
||||||
Event event(Event::Executed);
|
|
||||||
event.computerManager = manager;
|
|
||||||
d->handleEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Launcher::isExecuted() const
|
|
||||||
{
|
|
||||||
Q_D(const Launcher);
|
|
||||||
return d->m_State != StateInit;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::onComputerFound(NvComputer *computer)
|
|
||||||
{
|
|
||||||
Q_D(Launcher);
|
|
||||||
Event event(Event::ComputerFound);
|
|
||||||
event.computer = computer;
|
|
||||||
d->handleEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::onComputerSeekTimeout()
|
|
||||||
{
|
|
||||||
Q_D(Launcher);
|
|
||||||
Event event(Event::ComputerSeekTimedout);
|
|
||||||
d->handleEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::onComputerUpdated(NvComputer *computer)
|
|
||||||
{
|
|
||||||
Q_D(Launcher);
|
|
||||||
Event event(Event::ComputerUpdated);
|
|
||||||
event.computer = computer;
|
|
||||||
d->handleEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "commandlineparser.h"
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QVariant>
|
|
||||||
|
|
||||||
class ComputerManager;
|
|
||||||
class NvComputer;
|
|
||||||
|
|
||||||
namespace CliListApps
|
|
||||||
{
|
|
||||||
|
|
||||||
class LauncherPrivate;
|
|
||||||
|
|
||||||
class Launcher : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_DECLARE_PRIVATE_D(m_DPtr, Launcher)
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Launcher(QString computer, ListCommandLineParser arguments, QObject *parent = nullptr);
|
|
||||||
~Launcher();
|
|
||||||
|
|
||||||
Q_INVOKABLE void execute(ComputerManager *manager);
|
|
||||||
Q_INVOKABLE bool isExecuted() const;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onComputerFound(NvComputer *computer);
|
|
||||||
void onComputerUpdated(NvComputer *computer);
|
|
||||||
void onComputerSeekTimeout();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QScopedPointer<LauncherPrivate> m_DPtr;
|
|
||||||
ListCommandLineParser m_Arguments;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
#include "pair.h"
|
|
||||||
|
|
||||||
#include "backend/computermanager.h"
|
|
||||||
#include "backend/computerseeker.h"
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#define COMPUTER_SEEK_TIMEOUT 10000
|
|
||||||
|
|
||||||
namespace CliPair
|
|
||||||
{
|
|
||||||
|
|
||||||
enum State {
|
|
||||||
StateInit,
|
|
||||||
StateSeekComputer,
|
|
||||||
StatePairing,
|
|
||||||
StateFailure,
|
|
||||||
StateComplete,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Event
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum Type {
|
|
||||||
ComputerFound,
|
|
||||||
Executed,
|
|
||||||
PairingCompleted,
|
|
||||||
Timedout,
|
|
||||||
};
|
|
||||||
|
|
||||||
Event(Type type)
|
|
||||||
: type(type), computerManager(nullptr), computer(nullptr) {}
|
|
||||||
|
|
||||||
Type type;
|
|
||||||
ComputerManager *computerManager;
|
|
||||||
NvComputer *computer;
|
|
||||||
QString errorMessage;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LauncherPrivate
|
|
||||||
{
|
|
||||||
Q_DECLARE_PUBLIC(Launcher)
|
|
||||||
|
|
||||||
public:
|
|
||||||
LauncherPrivate(Launcher *q) : q_ptr(q) {}
|
|
||||||
|
|
||||||
void handleEvent(Event event)
|
|
||||||
{
|
|
||||||
Q_Q(Launcher);
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
// Occurs when CliPair becomes visible and the UI calls launcher's execute()
|
|
||||||
case Event::Executed:
|
|
||||||
if (m_State == StateInit) {
|
|
||||||
m_State = StateSeekComputer;
|
|
||||||
m_ComputerManager = event.computerManager;
|
|
||||||
q->connect(m_ComputerManager, &ComputerManager::pairingCompleted,
|
|
||||||
q, &Launcher::onPairingCompleted);
|
|
||||||
|
|
||||||
// If we weren't provided a predefined PIN, generate one now
|
|
||||||
if (m_PredefinedPin.isEmpty()) {
|
|
||||||
m_PredefinedPin = m_ComputerManager->generatePinString();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ComputerSeeker = new ComputerSeeker(m_ComputerManager, m_ComputerName, q);
|
|
||||||
q->connect(m_ComputerSeeker, &ComputerSeeker::computerFound,
|
|
||||||
q, &Launcher::onComputerFound);
|
|
||||||
q->connect(m_ComputerSeeker, &ComputerSeeker::errorTimeout,
|
|
||||||
q, &Launcher::onTimeout);
|
|
||||||
m_ComputerSeeker->start(COMPUTER_SEEK_TIMEOUT);
|
|
||||||
|
|
||||||
emit q->searchingComputer();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// Occurs when searched computer is found
|
|
||||||
case Event::ComputerFound:
|
|
||||||
if (m_State == StateSeekComputer) {
|
|
||||||
if (event.computer->pairState == NvComputer::PS_PAIRED) {
|
|
||||||
m_State = StateFailure;
|
|
||||||
QString msg = QObject::tr("%1 is already paired").arg(event.computer->name);
|
|
||||||
emit q->failed(msg);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Q_ASSERT(!m_PredefinedPin.isEmpty());
|
|
||||||
|
|
||||||
m_State = StatePairing;
|
|
||||||
m_ComputerManager->pairHost(event.computer, m_PredefinedPin);
|
|
||||||
emit q->pairing(event.computer->name, m_PredefinedPin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// Occurs when pairing operation completes
|
|
||||||
case Event::PairingCompleted:
|
|
||||||
if (m_State == StatePairing) {
|
|
||||||
if (event.errorMessage.isEmpty()) {
|
|
||||||
m_State = StateComplete;
|
|
||||||
emit q->success();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_State = StateFailure;
|
|
||||||
emit q->failed(event.errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
// Occurs when computer search timed out
|
|
||||||
case Event::Timedout:
|
|
||||||
if (m_State == StateSeekComputer) {
|
|
||||||
m_State = StateFailure;
|
|
||||||
emit q->failed(QObject::tr("Failed to connect to %1").arg(m_ComputerName));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Launcher *q_ptr;
|
|
||||||
QString m_ComputerName;
|
|
||||||
QString m_PredefinedPin;
|
|
||||||
ComputerManager *m_ComputerManager;
|
|
||||||
ComputerSeeker *m_ComputerSeeker;
|
|
||||||
NvComputer *m_Computer;
|
|
||||||
State m_State;
|
|
||||||
QTimer *m_TimeoutTimer;
|
|
||||||
};
|
|
||||||
|
|
||||||
Launcher::Launcher(QString computer, QString predefinedPin, QObject *parent)
|
|
||||||
: QObject(parent),
|
|
||||||
m_DPtr(new LauncherPrivate(this))
|
|
||||||
{
|
|
||||||
Q_D(Launcher);
|
|
||||||
d->m_ComputerName = computer;
|
|
||||||
d->m_PredefinedPin = predefinedPin;
|
|
||||||
d->m_State = StateInit;
|
|
||||||
d->m_TimeoutTimer = new QTimer(this);
|
|
||||||
d->m_TimeoutTimer->setSingleShot(true);
|
|
||||||
connect(d->m_TimeoutTimer, &QTimer::timeout,
|
|
||||||
this, &Launcher::onTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
Launcher::~Launcher()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::execute(ComputerManager *manager)
|
|
||||||
{
|
|
||||||
Q_D(Launcher);
|
|
||||||
Event event(Event::Executed);
|
|
||||||
event.computerManager = manager;
|
|
||||||
d->handleEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Launcher::isExecuted() const
|
|
||||||
{
|
|
||||||
Q_D(const Launcher);
|
|
||||||
return d->m_State != StateInit;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::onComputerFound(NvComputer *computer)
|
|
||||||
{
|
|
||||||
Q_D(Launcher);
|
|
||||||
Event event(Event::ComputerFound);
|
|
||||||
event.computer = computer;
|
|
||||||
d->handleEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::onTimeout()
|
|
||||||
{
|
|
||||||
Q_D(Launcher);
|
|
||||||
Event event(Event::Timedout);
|
|
||||||
d->handleEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Launcher::onPairingCompleted(NvComputer* computer, QString error)
|
|
||||||
{
|
|
||||||
Q_D(Launcher);
|
|
||||||
Event event(Event::PairingCompleted);
|
|
||||||
event.computer = computer;
|
|
||||||
event.errorMessage = error;
|
|
||||||
d->handleEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QVariant>
|
|
||||||
|
|
||||||
class ComputerManager;
|
|
||||||
class NvComputer;
|
|
||||||
class Session;
|
|
||||||
class StreamingPreferences;
|
|
||||||
|
|
||||||
namespace CliPair
|
|
||||||
{
|
|
||||||
|
|
||||||
class Event;
|
|
||||||
class LauncherPrivate;
|
|
||||||
|
|
||||||
class Launcher : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_DECLARE_PRIVATE_D(m_DPtr, Launcher)
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Launcher(QString computer, QString predefinedPin,
|
|
||||||
QObject *parent = nullptr);
|
|
||||||
~Launcher();
|
|
||||||
Q_INVOKABLE void execute(ComputerManager *manager);
|
|
||||||
Q_INVOKABLE bool isExecuted() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void searchingComputer();
|
|
||||||
void pairing(QString pcName, QString pin);
|
|
||||||
void failed(QString text);
|
|
||||||
void success();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onComputerFound(NvComputer *computer);
|
|
||||||
void onPairingCompleted(NvComputer *computer, QString error);
|
|
||||||
void onTimeout();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QScopedPointer<LauncherPrivate> m_DPtr;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -162,7 +162,7 @@ public:
|
|||||||
|
|
||||||
QString getCurrentAppName() const
|
QString getCurrentAppName() const
|
||||||
{
|
{
|
||||||
for (const NvApp& app : m_Computer->appList) {
|
for (NvApp app : m_Computer->appList) {
|
||||||
if (m_Computer->currentGameId == app.id) {
|
if (m_Computer->currentGameId == app.id) {
|
||||||
return app.name;
|
return app.name;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,91 +36,6 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
<release version="4.3.1" date="2022-11-03">
|
|
||||||
<description>
|
|
||||||
<p>Changes:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Fixed crash during specific patterns of audio packet loss</li>
|
|
||||||
<li>Fixed detection of local streaming when using IPv6 addresses</li>
|
|
||||||
<li>Windowed mode streaming now starts maximized if the GUI window was maximized</li>
|
|
||||||
<li>Updated community-contributed translations from Weblate</li>
|
|
||||||
</ul>
|
|
||||||
</description>
|
|
||||||
</release>
|
|
||||||
<release version="4.3.0" date="2022-10-28">
|
|
||||||
<description>
|
|
||||||
<p>New features:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Improved support for GeForce Experience 3.26</li>
|
|
||||||
<li>Improved image quality and performance when recovering from video packet loss using HEVC</li>
|
|
||||||
<li>Improved error concealment after audio packet loss</li>
|
|
||||||
<li>Improved UI performance when paired with many hosts or hosts with large numbers of apps</li>
|
|
||||||
<li>Pairing with Sunshine hosts is now supported even if the host is in a streaming session</li>
|
|
||||||
</ul>
|
|
||||||
<p>Bugfixes:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Fixed streaming at resolutions below 720x480 with GeForce Experience 3.26</li>
|
|
||||||
<li>Fixed window position and size being lost when toggling between full-screen and windowed mode</li>
|
|
||||||
<li>Fixed initial window size not always respecting the stream aspect ratio</li>
|
|
||||||
<li>Fixed unexpected error when streaming at resolutions above 4096x4096 using Sunshine</li>
|
|
||||||
<li>Updated included gamepad mappings</li>
|
|
||||||
<li>Updated community-contributed translations from Weblate</li>
|
|
||||||
</ul>
|
|
||||||
</description>
|
|
||||||
</release>
|
|
||||||
<release version="4.2.1" date="2022-09-12">
|
|
||||||
<description>
|
|
||||||
<p>Bugfixes:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Fixed streaming from hosts running Sunshine</li>
|
|
||||||
<li>Updated community-contributed translations from Weblate</li>
|
|
||||||
</ul>
|
|
||||||
</description>
|
|
||||||
</release>
|
|
||||||
<release version="4.2.0" date="2022-09-10">
|
|
||||||
<description>
|
|
||||||
<p>New features:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Added support for pairing via the terminal</li>
|
|
||||||
<li>Added support for listing installed apps on a host via the terminal</li>
|
|
||||||
<li>Improved handling of V-Sync on Wayland systems to reduce latency</li>
|
|
||||||
<li>Improved performance using the legacy non-EGL VAAPI rendering code</li>
|
|
||||||
</ul>
|
|
||||||
<p>Bugfixes:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Fixed native resolution detection on high DPI Wayland systems</li>
|
|
||||||
<li>Fixed audio crackling with SDL 2.24.0 on systems using PulseAudio</li>
|
|
||||||
<li>Updated included gamepad mappings</li>
|
|
||||||
<li>Updated community-contributed translations from Weblate</li>
|
|
||||||
</ul>
|
|
||||||
</description>
|
|
||||||
</release>
|
|
||||||
<release version="4.1.0" date="2022-05-23">
|
|
||||||
<description>
|
|
||||||
<p>New features:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Overlays are now supported on the non-EGL X11 VAAPI renderer (requires RGBA subpicture support in the VAAPI driver)</li>
|
|
||||||
<li>Cursor lock can now be disabled when in full-screen mode</li>
|
|
||||||
</ul>
|
|
||||||
<p>Behavior Changes:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Moonlight can now reduce the client display resolution in full-screen mode if the current display mode doesn't have a high enough refresh rate for the selected stream frame rate</li>
|
|
||||||
<li>When choosing a new client display resolution in full-screen mode, Moonlight will pick a display mode with the closest aspect ratio to the selected stream resolution</li>
|
|
||||||
<li>Moonlight now defaults to borderless windowed mode on Wayland systems</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
<p>Bugfixes:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Fixed missing toolbar buttons in certain languages</li>
|
|
||||||
<li>Fixed some text on the settings page not updating when the language is changed</li>
|
|
||||||
<li>Fixed a frame pacing bug that could cause excessive frame drops</li>
|
|
||||||
<li>Fixed the "keep display awake" setting not being saved when Moonlight restarts</li>
|
|
||||||
<li>Fixed black screen after unmaximizing the Moonlight window on some Linux systems</li>
|
|
||||||
<li>Updated included gamepad mappings</li>
|
|
||||||
<li>Updated community-contributed translations from Weblate</li>
|
|
||||||
</ul>
|
|
||||||
</description>
|
|
||||||
</release>
|
|
||||||
<release version="4.0.0" date="2022-04-02">
|
<release version="4.0.0" date="2022-04-02">
|
||||||
<description>
|
<description>
|
||||||
<p>New features:</p>
|
<p>New features:</p>
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
import QtQuick 2.0
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
|
|
||||||
import ComputerManager 1.0
|
|
||||||
import SdlGamepadKeyNavigation 1.0
|
|
||||||
|
|
||||||
Item {
|
|
||||||
function onSearchingComputer() {
|
|
||||||
stageLabel.text = qsTr("Establishing connection to PC...")
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPairing(pcName, pin) {
|
|
||||||
stageLabel.text = qsTr("Pairing... Please enter '%1' on %2.").arg(pin).arg(pcName)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onFailed(message) {
|
|
||||||
stageIndicator.visible = false
|
|
||||||
errorDialog.text = message
|
|
||||||
errorDialog.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSuccess(appName) {
|
|
||||||
stageIndicator.visible = false
|
|
||||||
pairCompleteDialog.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow user to back out of pairing
|
|
||||||
Keys.onEscapePressed: {
|
|
||||||
Qt.quit()
|
|
||||||
}
|
|
||||||
Keys.onBackPressed: {
|
|
||||||
Qt.quit()
|
|
||||||
}
|
|
||||||
Keys.onCancelPressed: {
|
|
||||||
Qt.quit()
|
|
||||||
}
|
|
||||||
|
|
||||||
StackView.onActivated: {
|
|
||||||
if (!launcher.isExecuted()) {
|
|
||||||
toolBar.visible = false
|
|
||||||
|
|
||||||
// Normally this is enabled by PcView, but we will won't
|
|
||||||
// load PcView when streaming from the command-line.
|
|
||||||
SdlGamepadKeyNavigation.enable()
|
|
||||||
|
|
||||||
launcher.searchingComputer.connect(onSearchingComputer)
|
|
||||||
launcher.pairing.connect(onPairing)
|
|
||||||
launcher.failed.connect(onFailed)
|
|
||||||
launcher.success.connect(onSuccess)
|
|
||||||
launcher.execute(ComputerManager)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
spacing: 5
|
|
||||||
id: stageIndicator
|
|
||||||
|
|
||||||
BusyIndicator {
|
|
||||||
id: stageSpinner
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: stageLabel
|
|
||||||
height: stageSpinner.height
|
|
||||||
font.pointSize: 20
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorMessageDialog {
|
|
||||||
id: errorDialog
|
|
||||||
|
|
||||||
onClosed: {
|
|
||||||
Qt.quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NavigableMessageDialog {
|
|
||||||
id: pairCompleteDialog
|
|
||||||
closePolicy: Popup.CloseOnEscape
|
|
||||||
|
|
||||||
text:qsTr("Pairing completed successfully")
|
|
||||||
standardButtons: Dialog.Ok
|
|
||||||
onClosed: {
|
|
||||||
Qt.quit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+14
-6
@@ -232,14 +232,22 @@ CenteredGridView {
|
|||||||
stackView.push(appView)
|
stackView.push(appView)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var pin = computerModel.generatePinString()
|
if (!model.busy) {
|
||||||
|
var pin = computerModel.generatePinString()
|
||||||
|
|
||||||
// Kick off pairing in the background
|
// Kick off pairing in the background
|
||||||
computerModel.pairComputer(index, pin)
|
computerModel.pairComputer(index, pin)
|
||||||
|
|
||||||
// Display the pairing dialog
|
// Display the pairing dialog
|
||||||
pairDialog.pin = pin
|
pairDialog.pin = pin
|
||||||
pairDialog.open()
|
pairDialog.open()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// cannot pair while something is streaming or attempting to pair
|
||||||
|
errorDialog.text = qsTr("You cannot pair while a previous session is still running on the host PC. Quit any running games or reboot the host PC, then try pairing again.")
|
||||||
|
errorDialog.helpText = ""
|
||||||
|
errorDialog.open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (!model.online) {
|
} else if (!model.online) {
|
||||||
// Using open() here because it may be activated by keyboard
|
// Using open() here because it may be activated by keyboard
|
||||||
|
|||||||
+69
-87
@@ -11,8 +11,6 @@ Flickable {
|
|||||||
id: settingsPage
|
id: settingsPage
|
||||||
objectName: qsTr("Settings")
|
objectName: qsTr("Settings")
|
||||||
|
|
||||||
signal languageChanged()
|
|
||||||
|
|
||||||
boundsBehavior: Flickable.OvershootBounds
|
boundsBehavior: Flickable.OvershootBounds
|
||||||
|
|
||||||
contentWidth: settingsColumn1.width > settingsColumn2.width ? settingsColumn1.width : settingsColumn2.width
|
contentWidth: settingsColumn1.width > settingsColumn2.width ? settingsColumn1.width : settingsColumn2.width
|
||||||
@@ -97,39 +95,50 @@ Flickable {
|
|||||||
// Add native resolutions for all attached displays
|
// Add native resolutions for all attached displays
|
||||||
var done = false
|
var done = false
|
||||||
for (var displayIndex = 0; !done; displayIndex++) {
|
for (var displayIndex = 0; !done; displayIndex++) {
|
||||||
var screenRect = SystemProperties.getNativeResolution(displayIndex);
|
for (var displayResIndex = 0; displayResIndex < 2; displayResIndex++) {
|
||||||
|
var screenRect;
|
||||||
|
|
||||||
if (screenRect.width === 0) {
|
// Some platforms have different desktop resolutions
|
||||||
// Exceeded max count of displays
|
// and native resolutions (like macOS with Retina displays)
|
||||||
done = true
|
if (displayResIndex === 0) {
|
||||||
break
|
screenRect = SystemProperties.getDesktopResolution(displayIndex)
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
screenRect = SystemProperties.getNativeResolution(displayIndex)
|
||||||
|
}
|
||||||
|
|
||||||
var indexToAdd = 0
|
if (screenRect.width === 0) {
|
||||||
for (var j = 0; j < resolutionComboBox.count; j++) {
|
// Exceeded max count of displays
|
||||||
var existing_width = parseInt(resolutionListModel.get(j).video_width);
|
done = true
|
||||||
var existing_height = parseInt(resolutionListModel.get(j).video_height);
|
|
||||||
|
|
||||||
if (screenRect.width === existing_width && screenRect.height === existing_height) {
|
|
||||||
// Duplicate entry, skip
|
|
||||||
indexToAdd = -1
|
|
||||||
break
|
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
|
var indexToAdd = 0
|
||||||
if (indexToAdd >= 0) {
|
for (var j = 0; j < resolutionComboBox.count; j++) {
|
||||||
resolutionListModel.insert(indexToAdd,
|
var existing_width = parseInt(resolutionListModel.get(j).video_width);
|
||||||
{
|
var existing_height = parseInt(resolutionListModel.get(j).video_height);
|
||||||
"text": "Native ("+screenRect.width+"x"+screenRect.height+")",
|
|
||||||
"video_width": ""+screenRect.width,
|
if (screenRect.width === existing_width && screenRect.height === existing_height) {
|
||||||
"video_height": ""+screenRect.height,
|
// Duplicate entry, skip
|
||||||
"is_custom": false
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,7 +465,6 @@ Flickable {
|
|||||||
// ignore setting the index at first, and actually set it when the component is loaded
|
// ignore setting the index at first, and actually set it when the component is loaded
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
reinitialize()
|
reinitialize()
|
||||||
languageChanged.connect(reinitialize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id: fpsComboBox
|
id: fpsComboBox
|
||||||
@@ -523,53 +531,28 @@ Flickable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AutoResizingComboBox {
|
AutoResizingComboBox {
|
||||||
function createModel() {
|
// ignore setting the index at first, and actually set it when the component is loaded
|
||||||
var model = Qt.createQmlObject('import QtQuick 2.0; ListModel {}', parent, '')
|
Component.onCompleted: {
|
||||||
|
|
||||||
model.append({
|
|
||||||
text: qsTr("Fullscreen"),
|
|
||||||
val: StreamingPreferences.WM_FULLSCREEN
|
|
||||||
})
|
|
||||||
|
|
||||||
model.append({
|
|
||||||
text: qsTr("Borderless windowed"),
|
|
||||||
val: StreamingPreferences.WM_FULLSCREEN_DESKTOP
|
|
||||||
})
|
|
||||||
|
|
||||||
model.append({
|
|
||||||
text: qsTr("Windowed"),
|
|
||||||
val: StreamingPreferences.WM_WINDOWED
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// Set the recommended option based on the OS
|
|
||||||
for (var i = 0; i < model.count; i++) {
|
|
||||||
var thisWm = model.get(i).val;
|
|
||||||
if (thisWm === StreamingPreferences.recommendedFullScreenMode) {
|
|
||||||
model.get(i).text += qsTr(" (Recommended)")
|
|
||||||
model.move(i, 0, 1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return model
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// This is used on initialization and upon retranslation
|
|
||||||
function reinitialize() {
|
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
// Do nothing if the control won't even be visible
|
// Do nothing if the control won't even be visible
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
model = createModel()
|
// Set the recommended option based on the OS
|
||||||
|
for (var i = 0; i < windowModeListModel.count; i++) {
|
||||||
|
var thisWm = windowModeListModel.get(i).val;
|
||||||
|
if (thisWm === StreamingPreferences.recommendedFullScreenMode) {
|
||||||
|
windowModeListModel.get(i).text += qsTr(" (Recommended)")
|
||||||
|
windowModeListModel.move(i, 0, 1);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
|
|
||||||
// Set the current value based on the saved preferences
|
|
||||||
var savedWm = StreamingPreferences.windowMode
|
var savedWm = StreamingPreferences.windowMode
|
||||||
for (var i = 0; i < model.count; i++) {
|
for (var i = 0; i < windowModeListModel.count; i++) {
|
||||||
var thisWm = model.get(i).val;
|
var thisWm = windowModeListModel.get(i).val;
|
||||||
if (savedWm === thisWm) {
|
if (savedWm === thisWm) {
|
||||||
currentIndex = i
|
currentIndex = i
|
||||||
break
|
break
|
||||||
@@ -579,18 +562,28 @@ Flickable {
|
|||||||
activated(currentIndex)
|
activated(currentIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
reinitialize()
|
|
||||||
languageChanged.connect(reinitialize)
|
|
||||||
}
|
|
||||||
|
|
||||||
id: windowModeComboBox
|
id: windowModeComboBox
|
||||||
visible: SystemProperties.hasDesktopEnvironment
|
visible: SystemProperties.hasDesktopEnvironment
|
||||||
enabled: !SystemProperties.rendererAlwaysFullScreen
|
enabled: !SystemProperties.rendererAlwaysFullScreen
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
textRole: "text"
|
textRole: "text"
|
||||||
|
model: ListModel {
|
||||||
|
id: windowModeListModel
|
||||||
|
ListElement {
|
||||||
|
text: qsTr("Fullscreen")
|
||||||
|
val: StreamingPreferences.WM_FULLSCREEN
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
text: qsTr("Borderless windowed")
|
||||||
|
val: StreamingPreferences.WM_FULLSCREEN_DESKTOP
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
text: qsTr("Windowed")
|
||||||
|
val: StreamingPreferences.WM_WINDOWED
|
||||||
|
}
|
||||||
|
}
|
||||||
onActivated: {
|
onActivated: {
|
||||||
StreamingPreferences.windowMode = model.get(currentIndex).val
|
StreamingPreferences.windowMode = windowModeListModel.get(currentIndex).val
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
@@ -860,14 +853,6 @@ Flickable {
|
|||||||
text: "हिन्दी, हिंदी" // Hindi
|
text: "हिन्दी, हिंदी" // Hindi
|
||||||
val: StreamingPreferences.LANG_HI
|
val: StreamingPreferences.LANG_HI
|
||||||
} */
|
} */
|
||||||
ListElement {
|
|
||||||
text: "Język polski" // Polish
|
|
||||||
val: StreamingPreferences.LANG_PL
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
text: "Čeština" // Czech
|
|
||||||
val: StreamingPreferences.LANG_CS
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||||
onActivated : {
|
onActivated : {
|
||||||
@@ -882,9 +867,6 @@ Flickable {
|
|||||||
// Force the back operation to pop any AppView pages that exist.
|
// Force the back operation to pop any AppView pages that exist.
|
||||||
// The AppView stops working after retranslate() for some reason.
|
// The AppView stops working after retranslate() for some reason.
|
||||||
window.clearOnBack = true
|
window.clearOnBack = true
|
||||||
|
|
||||||
// Signal other controls to adjust their text
|
|
||||||
languageChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
|
||||||
ComputerModel::ComputerModel(QObject* object)
|
ComputerModel::ComputerModel(QObject* object)
|
||||||
: QAbstractListModel(object) {}
|
: QAbstractListModel(object) {}
|
||||||
|
|
||||||
@@ -138,9 +140,14 @@ void ComputerModel::renameComputer(int computerIndex, QString name)
|
|||||||
m_ComputerManager->renameHost(m_Computers[computerIndex], name);
|
m_ComputerManager->renameHost(m_Computers[computerIndex], name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Use QRandomGenerator when we drop Qt 5.9 support
|
||||||
QString ComputerModel::generatePinString()
|
QString ComputerModel::generatePinString()
|
||||||
{
|
{
|
||||||
return m_ComputerManager->generatePinString();
|
std::uniform_int_distribution<int> dist(0, 9999);
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 engine(rd());
|
||||||
|
|
||||||
|
return QString::asprintf("%04u", dist(engine));
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeferredTestConnectionTask : public QObject, public QRunnable
|
class DeferredTestConnectionTask : public QObject, public QRunnable
|
||||||
|
|||||||
+7
-17
@@ -181,20 +181,10 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround for lack of instanceof in Qt 5.9.
|
function navigateTo(url, objectName)
|
||||||
//
|
|
||||||
// Based on https://stackoverflow.com/questions/13923794/how-to-do-a-is-a-typeof-or-instanceof-in-qml
|
|
||||||
function qmltypeof(obj, className) { // QtObject, string -> bool
|
|
||||||
// className plus "(" is the class instance without modification
|
|
||||||
// className plus "_QML" is the class instance with user-defined properties
|
|
||||||
var str = obj.toString();
|
|
||||||
return str.startsWith(className + "(") || str.startsWith(className + "_QML");
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateTo(url, objectType)
|
|
||||||
{
|
{
|
||||||
var existingItem = stackView.find(function(item, index) {
|
var existingItem = stackView.find(function(item, index) {
|
||||||
return qmltypeof(item, objectType)
|
return item.objectName === objectName
|
||||||
})
|
})
|
||||||
|
|
||||||
if (existingItem !== null) {
|
if (existingItem !== null) {
|
||||||
@@ -261,7 +251,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: versionLabel
|
id: versionLabel
|
||||||
visible: qmltypeof(stackView.currentItem, "SettingsView")
|
visible: stackView.currentItem.objectName === qsTr("Settings")
|
||||||
text: qsTr("Version %1").arg(SystemProperties.versionString)
|
text: qsTr("Version %1").arg(SystemProperties.versionString)
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
horizontalAlignment: Qt.AlignRight
|
horizontalAlignment: Qt.AlignRight
|
||||||
@@ -271,7 +261,7 @@ ApplicationWindow {
|
|||||||
NavigableToolButton {
|
NavigableToolButton {
|
||||||
id: discordButton
|
id: discordButton
|
||||||
visible: SystemProperties.hasBrowser &&
|
visible: SystemProperties.hasBrowser &&
|
||||||
qmltypeof(stackView.currentItem, "SettingsView")
|
stackView.currentItem.objectName === qsTr("Settings")
|
||||||
|
|
||||||
iconSource: "qrc:/res/Discord-Logo-White.svg"
|
iconSource: "qrc:/res/Discord-Logo-White.svg"
|
||||||
|
|
||||||
@@ -290,7 +280,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
NavigableToolButton {
|
NavigableToolButton {
|
||||||
id: addPcButton
|
id: addPcButton
|
||||||
visible: qmltypeof(stackView.currentItem, "PcView")
|
visible: stackView.currentItem.objectName === qsTr("Computers")
|
||||||
|
|
||||||
iconSource: "qrc:/res/ic_add_to_queue_white_48px.svg"
|
iconSource: "qrc:/res/ic_add_to_queue_white_48px.svg"
|
||||||
|
|
||||||
@@ -388,7 +378,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
iconSource: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
iconSource: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
||||||
|
|
||||||
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "GamepadMapper")
|
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", qsTr("Gamepad Mapping"))
|
||||||
|
|
||||||
Keys.onDownPressed: {
|
Keys.onDownPressed: {
|
||||||
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
|
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
|
||||||
@@ -400,7 +390,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
iconSource: "qrc:/res/settings.svg"
|
iconSource: "qrc:/res/settings.svg"
|
||||||
|
|
||||||
onClicked: navigateTo("qrc:/gui/SettingsView.qml", "SettingsView")
|
onClicked: navigateTo("qrc:/gui/SettingsView.qml", qsTr("Settings"))
|
||||||
|
|
||||||
Keys.onDownPressed: {
|
Keys.onDownPressed: {
|
||||||
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
|
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+249
-319
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+162
-232
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+165
-235
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+175
-245
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+162
-232
File diff suppressed because it is too large.
Load diff
+171
-225
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+168
-238
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+162
-232
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+167
-237
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+162
-232
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+168
-238
File diff suppressed because it is too large.
Load diff
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+169
-239
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+180
-246
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+175
-245
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+168
-226
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+162
-232
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+168
-226
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+219
-281
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+162
-232
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+187
-257
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+165
-235
File diff suppressed because it is too large.
Load diff
+26
-104
@@ -28,17 +28,14 @@
|
|||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "cli/listapps.h"
|
|
||||||
#include "cli/quitstream.h"
|
#include "cli/quitstream.h"
|
||||||
#include "cli/startstream.h"
|
#include "cli/startstream.h"
|
||||||
#include "cli/pair.h"
|
|
||||||
#include "cli/commandlineparser.h"
|
#include "cli/commandlineparser.h"
|
||||||
#include "path.h"
|
#include "path.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "gui/computermodel.h"
|
#include "gui/computermodel.h"
|
||||||
#include "gui/appmodel.h"
|
#include "gui/appmodel.h"
|
||||||
#include "backend/autoupdatechecker.h"
|
#include "backend/autoupdatechecker.h"
|
||||||
#include "backend/computermanager.h"
|
|
||||||
#include "backend/systemproperties.h"
|
#include "backend/systemproperties.h"
|
||||||
#include "streaming/session.h"
|
#include "streaming/session.h"
|
||||||
#include "settings/streamingpreferences.h"
|
#include "settings/streamingpreferences.h"
|
||||||
@@ -63,11 +60,9 @@
|
|||||||
static QElapsedTimer s_LoggerTime;
|
static QElapsedTimer s_LoggerTime;
|
||||||
static QTextStream s_LoggerStream(stdout);
|
static QTextStream s_LoggerStream(stdout);
|
||||||
static QMutex s_LoggerLock;
|
static QMutex s_LoggerLock;
|
||||||
static bool s_SuppressVerboseOutput;
|
|
||||||
#ifdef LOG_TO_FILE
|
#ifdef LOG_TO_FILE
|
||||||
// Max log file size of 10 MB
|
#define MAX_LOG_LINES 10000
|
||||||
#define MAX_LOG_SIZE_BYTES (10 * 1024 * 1024)
|
static int s_LogLinesWritten = 0;
|
||||||
static int s_LogBytesWritten = 0;
|
|
||||||
static bool s_LogLimitReached = false;
|
static bool s_LogLimitReached = false;
|
||||||
static QFile* s_LoggerFile;
|
static QFile* s_LoggerFile;
|
||||||
#endif
|
#endif
|
||||||
@@ -80,7 +75,7 @@ void logToLoggerStream(QString& message)
|
|||||||
if (s_LogLimitReached) {
|
if (s_LogLimitReached) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (s_LogBytesWritten >= MAX_LOG_SIZE_BYTES) {
|
else if (s_LogLinesWritten == MAX_LOG_LINES) {
|
||||||
s_LoggerStream << "Log size limit reached!";
|
s_LoggerStream << "Log size limit reached!";
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
s_LoggerStream << Qt::endl;
|
s_LoggerStream << Qt::endl;
|
||||||
@@ -91,7 +86,7 @@ void logToLoggerStream(QString& message)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
s_LogBytesWritten += message.size();
|
s_LogLinesWritten++;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -105,27 +100,15 @@ void sdlLogToDiskHandler(void*, int category, SDL_LogPriority priority, const ch
|
|||||||
|
|
||||||
switch (priority) {
|
switch (priority) {
|
||||||
case SDL_LOG_PRIORITY_VERBOSE:
|
case SDL_LOG_PRIORITY_VERBOSE:
|
||||||
if (s_SuppressVerboseOutput) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
priorityTxt = "Verbose";
|
priorityTxt = "Verbose";
|
||||||
break;
|
break;
|
||||||
case SDL_LOG_PRIORITY_DEBUG:
|
case SDL_LOG_PRIORITY_DEBUG:
|
||||||
if (s_SuppressVerboseOutput) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
priorityTxt = "Debug";
|
priorityTxt = "Debug";
|
||||||
break;
|
break;
|
||||||
case SDL_LOG_PRIORITY_INFO:
|
case SDL_LOG_PRIORITY_INFO:
|
||||||
if (s_SuppressVerboseOutput) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
priorityTxt = "Info";
|
priorityTxt = "Info";
|
||||||
break;
|
break;
|
||||||
case SDL_LOG_PRIORITY_WARN:
|
case SDL_LOG_PRIORITY_WARN:
|
||||||
if (s_SuppressVerboseOutput) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
priorityTxt = "Warn";
|
priorityTxt = "Warn";
|
||||||
break;
|
break;
|
||||||
case SDL_LOG_PRIORITY_ERROR:
|
case SDL_LOG_PRIORITY_ERROR:
|
||||||
@@ -151,21 +134,12 @@ void qtLogToDiskHandler(QtMsgType type, const QMessageLogContext&, const QString
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case QtDebugMsg:
|
case QtDebugMsg:
|
||||||
if (s_SuppressVerboseOutput) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
typeTxt = "Debug";
|
typeTxt = "Debug";
|
||||||
break;
|
break;
|
||||||
case QtInfoMsg:
|
case QtInfoMsg:
|
||||||
if (s_SuppressVerboseOutput) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
typeTxt = "Info";
|
typeTxt = "Info";
|
||||||
break;
|
break;
|
||||||
case QtWarningMsg:
|
case QtWarningMsg:
|
||||||
if (s_SuppressVerboseOutput) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
typeTxt = "Warning";
|
typeTxt = "Warning";
|
||||||
break;
|
break;
|
||||||
case QtCriticalMsg:
|
case QtCriticalMsg:
|
||||||
@@ -192,9 +166,6 @@ void ffmpegLogToDiskHandler(void* ptr, int level, const char* fmt, va_list vl)
|
|||||||
if ((level & 0xFF) > av_log_get_level()) {
|
if ((level & 0xFF) > av_log_get_level()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if ((level & 0xFF) > AV_LOG_WARNING && s_SuppressVerboseOutput) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to use the *previous* printPrefix value to determine whether to
|
// We need to use the *previous* printPrefix value to determine whether to
|
||||||
// print the prefix this time. av_log_format_line() will set the printPrefix
|
// print the prefix this time. av_log_format_line() will set the printPrefix
|
||||||
@@ -289,6 +260,7 @@ int main(int argc, char *argv[])
|
|||||||
QCoreApplication::setApplicationName("Moonlight");
|
QCoreApplication::setApplicationName("Moonlight");
|
||||||
|
|
||||||
if (QFile(QDir::currentPath() + "/portable.dat").exists()) {
|
if (QFile(QDir::currentPath() + "/portable.dat").exists()) {
|
||||||
|
qInfo() << "Running in portable mode from:" << QDir::currentPath();
|
||||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||||
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, QDir::currentPath());
|
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, QDir::currentPath());
|
||||||
QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, QDir::currentPath());
|
QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, QDir::currentPath());
|
||||||
@@ -305,8 +277,8 @@ int main(int argc, char *argv[])
|
|||||||
#ifdef LOG_TO_FILE
|
#ifdef LOG_TO_FILE
|
||||||
QDir tempDir(Path::getLogDir());
|
QDir tempDir(Path::getLogDir());
|
||||||
s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch())));
|
s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch())));
|
||||||
if (s_LoggerFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
|
if (s_LoggerFile->open(QIODevice::WriteOnly)) {
|
||||||
QTextStream(stderr) << "Redirecting log output to " << s_LoggerFile->fileName() << Qt::endl;
|
qInfo() << "Redirecting log output to " << s_LoggerFile->fileName();
|
||||||
s_LoggerStream.setDevice(s_LoggerFile);
|
s_LoggerStream.setDevice(s_LoggerFile);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -409,6 +381,18 @@ int main(int argc, char *argv[])
|
|||||||
// Register custom metatypes for use in signals
|
// Register custom metatypes for use in signals
|
||||||
qRegisterMetaType<NvApp>("NvApp");
|
qRegisterMetaType<NvApp>("NvApp");
|
||||||
|
|
||||||
|
SDL_version compileVersion;
|
||||||
|
SDL_VERSION(&compileVersion);
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Compiled with SDL %d.%d.%d",
|
||||||
|
compileVersion.major, compileVersion.minor, compileVersion.patch);
|
||||||
|
|
||||||
|
SDL_version runtimeVersion;
|
||||||
|
SDL_GetVersion(&runtimeVersion);
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Running with SDL %d.%d.%d",
|
||||||
|
runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch);
|
||||||
|
|
||||||
// Allow the display to sleep by default. We will manually use SDL_DisableScreenSaver()
|
// Allow the display to sleep by default. We will manually use SDL_DisableScreenSaver()
|
||||||
// and SDL_EnableScreenSaver() when appropriate. This hint must be set before
|
// and SDL_EnableScreenSaver() when appropriate. This hint must be set before
|
||||||
// initializing the SDL video subsystem to have any effect.
|
// initializing the SDL video subsystem to have any effect.
|
||||||
@@ -486,47 +470,6 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
QGuiApplication app(argc, argv);
|
QGuiApplication app(argc, argv);
|
||||||
|
|
||||||
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
|
|
||||||
// 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)) {
|
|
||||||
HANDLE conOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
||||||
if (conOut != INVALID_HANDLE_VALUE && conOut != NULL) {
|
|
||||||
freopen("CONOUT$", "w", stdout);
|
|
||||||
setvbuf(stdout, NULL, _IONBF, 0);
|
|
||||||
}
|
|
||||||
HANDLE conErr = GetStdHandle(STD_ERROR_HANDLE);
|
|
||||||
if (conErr != INVALID_HANDLE_VALUE && conErr != NULL) {
|
|
||||||
freopen("CONOUT$", "w", stderr);
|
|
||||||
setvbuf(stderr, NULL, _IONBF, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_version compileVersion;
|
|
||||||
SDL_VERSION(&compileVersion);
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Compiled with SDL %d.%d.%d",
|
|
||||||
compileVersion.major, compileVersion.minor, compileVersion.patch);
|
|
||||||
|
|
||||||
SDL_version runtimeVersion;
|
|
||||||
SDL_GetVersion(&runtimeVersion);
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Running with SDL %d.%d.%d",
|
|
||||||
runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch);
|
|
||||||
|
|
||||||
// Apply the initial translation based on user preference
|
// Apply the initial translation based on user preference
|
||||||
StreamingPreferences prefs;
|
StreamingPreferences prefs;
|
||||||
prefs.retranslate();
|
prefs.retranslate();
|
||||||
@@ -640,9 +583,9 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
QString initialView;
|
QString initialView;
|
||||||
bool hasGUI = true;
|
|
||||||
|
|
||||||
switch (commandLineParserResult) {
|
GlobalCommandLineParser parser;
|
||||||
|
switch (parser.parse(app.arguments())) {
|
||||||
case GlobalCommandLineParser::NormalStartRequested:
|
case GlobalCommandLineParser::NormalStartRequested:
|
||||||
initialView = "qrc:/gui/PcView.qml";
|
initialView = "qrc:/gui/PcView.qml";
|
||||||
break;
|
break;
|
||||||
@@ -667,35 +610,14 @@ int main(int argc, char *argv[])
|
|||||||
engine.rootContext()->setContextProperty("launcher", launcher);
|
engine.rootContext()->setContextProperty("launcher", launcher);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GlobalCommandLineParser::PairRequested:
|
|
||||||
{
|
|
||||||
initialView = "qrc:/gui/CliPair.qml";
|
|
||||||
PairCommandLineParser pairParser;
|
|
||||||
pairParser.parse(app.arguments());
|
|
||||||
auto launcher = new CliPair::Launcher(pairParser.getHost(), pairParser.getPredefinedPin(), &app);
|
|
||||||
engine.rootContext()->setContextProperty("launcher", launcher);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case GlobalCommandLineParser::ListRequested:
|
|
||||||
{
|
|
||||||
ListCommandLineParser listParser;
|
|
||||||
listParser.parse(app.arguments());
|
|
||||||
auto launcher = new CliListApps::Launcher(listParser.getHost(), listParser, &app);
|
|
||||||
launcher->execute(new ComputerManager(&app));
|
|
||||||
hasGUI = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasGUI) {
|
engine.rootContext()->setContextProperty("initialView", initialView);
|
||||||
engine.rootContext()->setContextProperty("initialView", initialView);
|
|
||||||
|
|
||||||
// Load the main.qml file
|
|
||||||
engine.load(QUrl(QStringLiteral("qrc:/gui/main.qml")));
|
|
||||||
if (engine.rootObjects().isEmpty())
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Load the main.qml file
|
||||||
|
engine.load(QUrl(QStringLiteral("qrc:/gui/main.qml")));
|
||||||
|
if (engine.rootObjects().isEmpty())
|
||||||
|
return -1;
|
||||||
int err = app.exec();
|
int err = app.exec();
|
||||||
|
|
||||||
// Give worker tasks time to properly exit. Fixes PendingQuitTask
|
// Give worker tasks time to properly exit. Fixes PendingQuitTask
|
||||||
|
|||||||
+47
-35
@@ -11,10 +11,6 @@
|
|||||||
|
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
|
|
||||||
// NOTE: This file MUST NOT include fcntl.h due to open() -> open64()
|
|
||||||
// redirection that happens when _FILE_OFFSET_BITS=64!
|
|
||||||
// See masterhook_internal.c for details.
|
|
||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -39,21 +35,14 @@ struct stat64 g_DrmMasterStat;
|
|||||||
// The DRM master FD created for SDL
|
// The DRM master FD created for SDL
|
||||||
int g_SdlDrmMasterFd = -1;
|
int g_SdlDrmMasterFd = -1;
|
||||||
|
|
||||||
int drmIsMaster(int fd)
|
|
||||||
{
|
|
||||||
// Detect master by attempting something that requires master.
|
|
||||||
// This method is available in Mesa DRM since Feb 2019.
|
|
||||||
return drmAuthMagic(fd, 0) != -EACCES;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This hook will handle legacy DRM rendering
|
// This hook will handle legacy DRM rendering
|
||||||
int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
|
int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
|
||||||
uint32_t x, uint32_t y, uint32_t *connectors, int count,
|
uint32_t x, uint32_t y, uint32_t *connectors, int count,
|
||||||
drmModeModeInfoPtr mode)
|
drmModeModeInfoPtr mode)
|
||||||
{
|
{
|
||||||
// Grab the first DRM Master FD that makes it in here. This will be the Qt
|
// Grab the first DRM FD that makes it in here. This will be the Qt
|
||||||
// EGLFS backend's DRM FD, on which we will call drmDropMaster() later.
|
// EGLFS backend's DRM FD, which we will need later.
|
||||||
if (g_QtDrmMasterFd == -1 && drmIsMaster(fd)) {
|
if (g_QtDrmMasterFd == -1) {
|
||||||
g_QtDrmMasterFd = fd;
|
g_QtDrmMasterFd = fd;
|
||||||
fstat64(g_QtDrmMasterFd, &g_DrmMasterStat);
|
fstat64(g_QtDrmMasterFd, &g_DrmMasterStat);
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -62,16 +51,16 @@ int drmModeSetCrtc(int fd, uint32_t crtcId, uint32_t bufferId,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call into the real thing
|
// Call into the real thing
|
||||||
return ((typeof(drmModeSetCrtc)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, crtcId, bufferId, x, y, connectors, count, mode);
|
return ((typeof(drmModeSetCrtc)*)dlsym(RTLD_NEXT, "drmModeSetCrtc"))(fd, crtcId, bufferId, x, y, connectors, count, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This hook will handle atomic DRM rendering
|
// This hook will handle atomic DRM rendering
|
||||||
int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req,
|
int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req,
|
||||||
uint32_t flags, void *user_data)
|
uint32_t flags, void *user_data)
|
||||||
{
|
{
|
||||||
// Grab the first DRM Master FD that makes it in here. This will be the Qt
|
// Grab the first DRM FD that makes it in here. This will be the Qt
|
||||||
// EGLFS backend's DRM FD, on which we will call drmDropMaster() later.
|
// EGLFS backend's DRM FD, which we will need later.
|
||||||
if (g_QtDrmMasterFd == -1 && drmIsMaster(fd)) {
|
if (g_QtDrmMasterFd == -1) {
|
||||||
g_QtDrmMasterFd = fd;
|
g_QtDrmMasterFd = fd;
|
||||||
fstat64(g_QtDrmMasterFd, &g_DrmMasterStat);
|
fstat64(g_QtDrmMasterFd, &g_DrmMasterStat);
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -80,30 +69,53 @@ int drmModeAtomicCommit(int fd, drmModeAtomicReqPtr req,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call into the real thing
|
// Call into the real thing
|
||||||
return ((typeof(drmModeAtomicCommit)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd, req, flags, user_data);
|
return ((typeof(drmModeAtomicCommit)*)dlsym(RTLD_NEXT, "drmModeAtomicCommit"))(fd, req, flags, user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This hook will handle SDL's open() on the DRM device. We just need to
|
// This hook will handle SDL's open() on the DRM device. We just need to
|
||||||
// hook this variant of open(), since that's what SDL uses. When we see
|
// hook this variant of open(), since that's what SDL uses. When we see
|
||||||
// the open a FD for the same card as the Qt DRM master FD, we'll drop
|
// the open a FD for the same card as the Qt DRM master FD, we'll drop
|
||||||
// master on the Qt FD to allow the new FD to have master.
|
// master on the Qt FD to allow the new FD to have master.
|
||||||
int openHook(const char *funcname, const char *pathname, int flags, va_list va);
|
int open(const char *pathname, int flags)
|
||||||
|
|
||||||
int open(const char *pathname, int flags, ...)
|
|
||||||
{
|
{
|
||||||
va_list va;
|
// Call the real thing to do the open operation
|
||||||
va_start(va, flags);
|
int fd = ((typeof(open)*)dlsym(RTLD_NEXT, "open"))(pathname, flags);
|
||||||
int fd = openHook(__FUNCTION__, pathname, flags, va);
|
|
||||||
va_end(va);
|
// If the file was successfully opened and we have a DRM master FD,
|
||||||
return fd;
|
// check if the FD we just opened is a DRM device.
|
||||||
}
|
if (fd >= 0 && g_QtDrmMasterFd != -1) {
|
||||||
|
if (strncmp(pathname, "/dev/dri/card", 13) == 0) {
|
||||||
|
// It's a DRM device, but is it _our_ DRM device?
|
||||||
|
struct stat64 fdstat;
|
||||||
|
|
||||||
|
fstat64(fd, &fdstat);
|
||||||
|
if (g_DrmMasterStat.st_dev == fdstat.st_dev &&
|
||||||
|
g_DrmMasterStat.st_ino == fdstat.st_ino) {
|
||||||
|
// It is our device. Time to do the magic!
|
||||||
|
|
||||||
|
// This code assumes SDL only ever opens a single FD
|
||||||
|
// for a given DRM device.
|
||||||
|
SDL_assert(g_SdlDrmMasterFd == -1);
|
||||||
|
|
||||||
|
// Drop master on Qt's FD so we can pick it up for SDL.
|
||||||
|
if (drmDropMaster(g_QtDrmMasterFd) < 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to drop master on Qt DRM FD: %d",
|
||||||
|
errno);
|
||||||
|
// Hope for the best
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are not allowed to call drmSetMaster() without CAP_SYS_ADMIN,
|
||||||
|
// but since we just dropped the master, we can become master by
|
||||||
|
// simply creating a new FD. Let's do it.
|
||||||
|
close(fd);
|
||||||
|
fd = ((typeof(open)*)dlsym(RTLD_NEXT, "open"))(pathname, flags);
|
||||||
|
g_SdlDrmMasterFd = fd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int open64(const char *pathname, int flags, ...)
|
|
||||||
{
|
|
||||||
va_list va;
|
|
||||||
va_start(va, flags);
|
|
||||||
int fd = openHook(__FUNCTION__, pathname, flags, va);
|
|
||||||
va_end(va);
|
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +124,7 @@ int open64(const char *pathname, int flags, ...)
|
|||||||
int close(int fd)
|
int close(int fd)
|
||||||
{
|
{
|
||||||
// Call the real thing
|
// Call the real thing
|
||||||
int ret = ((typeof(close)*)dlsym(RTLD_NEXT, __FUNCTION__))(fd);
|
int ret = ((typeof(close)*)dlsym(RTLD_NEXT, "close"))(fd);
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
// If we just closed the SDL DRM master FD, restore master
|
// If we just closed the SDL DRM master FD, restore master
|
||||||
// to the Qt DRM FD. This works because the Qt DRM master FD
|
// to the Qt DRM FD. This works because the Qt DRM master FD
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
// This file contains the private implementation of the open() hook
|
|
||||||
// which must be in a separate compilation unit due to fcntl.h doing
|
|
||||||
// unwanted redirection of open() to open64().
|
|
||||||
|
|
||||||
#define _GNU_SOURCE
|
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
#include <xf86drm.h>
|
|
||||||
#include <xf86drmMode.h>
|
|
||||||
|
|
||||||
// __OPEN_NEEDS_MODE is a glibc-ism, so define it ourselves for other libc
|
|
||||||
#ifndef __OPEN_NEEDS_MODE
|
|
||||||
#ifdef __O_TMPFILE
|
|
||||||
# define __OPEN_NEEDS_MODE(oflag) \
|
|
||||||
(((oflag) & O_CREAT) != 0 || ((oflag) & __O_TMPFILE) == __O_TMPFILE)
|
|
||||||
#else
|
|
||||||
# define __OPEN_NEEDS_MODE(oflag) (((oflag) & O_CREAT) != 0)
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 15)
|
|
||||||
|
|
||||||
extern int g_QtDrmMasterFd;
|
|
||||||
extern struct stat64 g_DrmMasterStat;
|
|
||||||
extern int g_SdlDrmMasterFd;
|
|
||||||
|
|
||||||
int openHook(const char *funcname, const char *pathname, int flags, va_list va)
|
|
||||||
{
|
|
||||||
int fd;
|
|
||||||
mode_t mode;
|
|
||||||
|
|
||||||
// Call the real thing to do the open operation
|
|
||||||
if (__OPEN_NEEDS_MODE(flags)) {
|
|
||||||
mode = va_arg(va, mode_t);
|
|
||||||
fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags, mode);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
mode = 0;
|
|
||||||
fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the file was successfully opened and we have a DRM master FD,
|
|
||||||
// check if the FD we just opened is a DRM device.
|
|
||||||
if (fd >= 0 && g_QtDrmMasterFd != -1) {
|
|
||||||
if (strncmp(pathname, "/dev/dri/card", 13) == 0) {
|
|
||||||
// It's a DRM device, but is it _our_ DRM device?
|
|
||||||
struct stat64 fdstat;
|
|
||||||
|
|
||||||
fstat64(fd, &fdstat);
|
|
||||||
if (g_DrmMasterStat.st_dev == fdstat.st_dev &&
|
|
||||||
g_DrmMasterStat.st_ino == fdstat.st_ino) {
|
|
||||||
// It is our device. Time to do the magic!
|
|
||||||
|
|
||||||
// This code assumes SDL only ever opens a single FD
|
|
||||||
// for a given DRM device.
|
|
||||||
SDL_assert(g_SdlDrmMasterFd == -1);
|
|
||||||
|
|
||||||
// Drop master on Qt's FD so we can pick it up for SDL.
|
|
||||||
if (drmDropMaster(g_QtDrmMasterFd) < 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to drop master on Qt DRM FD: %d",
|
|
||||||
errno);
|
|
||||||
// Hope for the best
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are not allowed to call drmSetMaster() without CAP_SYS_ADMIN,
|
|
||||||
// but since we just dropped the master, we can become master by
|
|
||||||
// simply creating a new FD. Let's do it.
|
|
||||||
close(fd);
|
|
||||||
if (__OPEN_NEEDS_MODE(flags)) {
|
|
||||||
fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags, mode);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags);
|
|
||||||
}
|
|
||||||
g_SdlDrmMasterFd = fd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -18,6 +18,5 @@
|
|||||||
<file>gui/NavigableMessageDialog.qml</file>
|
<file>gui/NavigableMessageDialog.qml</file>
|
||||||
<file>gui/NavigableDialog.qml</file>
|
<file>gui/NavigableDialog.qml</file>
|
||||||
<file>gui/CenteredGridView.qml</file>
|
<file>gui/CenteredGridView.qml</file>
|
||||||
<file>gui/CliPair.qml</file>
|
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
[Platforms]
|
|
||||||
WindowsArguments = darkmode=1
|
|
||||||
@@ -57,10 +57,6 @@
|
|||||||
<file>languages/qml_it.qm</file>
|
<file>languages/qml_it.qm</file>
|
||||||
<file>languages/qml_pt.ts</file>
|
<file>languages/qml_pt.ts</file>
|
||||||
<file>languages/qml_pt.qm</file>
|
<file>languages/qml_pt.qm</file>
|
||||||
<file>languages/qml_pl.ts</file>
|
|
||||||
<file>languages/qml_pl.qm</file>
|
|
||||||
<file>languages/qml_cs.ts</file>
|
|
||||||
<file>languages/qml_cs.qm</file>
|
|
||||||
<!-- Don't include pt_BR until it is more complete -->
|
<!-- Don't include pt_BR until it is more complete -->
|
||||||
<!--file>languages/qml_pt_BR.qm</file-->
|
<!--file>languages/qml_pt_BR.qm</file-->
|
||||||
<!--file>languages/qml_pt_BR.ts</file-->
|
<!--file>languages/qml_pt_BR.ts</file-->
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include "streamingpreferences.h"
|
#include "streamingpreferences.h"
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
@@ -45,7 +44,7 @@
|
|||||||
#define SER_KEEPAWAKE "keepawake"
|
#define SER_KEEPAWAKE "keepawake"
|
||||||
#define SER_LANGUAGE "language"
|
#define SER_LANGUAGE "language"
|
||||||
|
|
||||||
#define CURRENT_DEFAULT_VER 2
|
#define CURRENT_DEFAULT_VER 1
|
||||||
|
|
||||||
StreamingPreferences::StreamingPreferences(QObject *parent)
|
StreamingPreferences::StreamingPreferences(QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
@@ -70,13 +69,7 @@ void StreamingPreferences::reload()
|
|||||||
#ifdef Q_OS_DARWIN
|
#ifdef Q_OS_DARWIN
|
||||||
recommendedFullScreenMode = WindowMode::WM_FULLSCREEN_DESKTOP;
|
recommendedFullScreenMode = WindowMode::WM_FULLSCREEN_DESKTOP;
|
||||||
#else
|
#else
|
||||||
// Wayland doesn't support modesetting, so use fullscreen desktop mode.
|
recommendedFullScreenMode = WindowMode::WM_FULLSCREEN;
|
||||||
if (WMUtils::isRunningWayland()) {
|
|
||||||
recommendedFullScreenMode = WindowMode::WM_FULLSCREEN_DESKTOP;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
recommendedFullScreenMode = WindowMode::WM_FULLSCREEN;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
width = settings.value(SER_WIDTH, 1280).toInt();
|
width = settings.value(SER_WIDTH, 1280).toInt();
|
||||||
@@ -124,7 +117,7 @@ void StreamingPreferences::reload()
|
|||||||
|
|
||||||
|
|
||||||
// Perform default settings updates as required based on last default version
|
// Perform default settings updates as required based on last default version
|
||||||
if (defaultVer < 1) {
|
if (defaultVer == 0) {
|
||||||
#ifdef Q_OS_DARWIN
|
#ifdef Q_OS_DARWIN
|
||||||
// Update window mode setting on macOS from full-screen (old default) to borderless windowed (new default)
|
// Update window mode setting on macOS from full-screen (old default) to borderless windowed (new default)
|
||||||
if (windowMode == WindowMode::WM_FULLSCREEN) {
|
if (windowMode == WindowMode::WM_FULLSCREEN) {
|
||||||
@@ -132,11 +125,6 @@ void StreamingPreferences::reload()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
if (defaultVer < 2) {
|
|
||||||
if (windowMode == WindowMode::WM_FULLSCREEN && WMUtils::isRunningWayland()) {
|
|
||||||
windowMode = WindowMode::WM_FULLSCREEN_DESKTOP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StreamingPreferences::retranslate()
|
bool StreamingPreferences::retranslate()
|
||||||
@@ -163,13 +151,13 @@ bool StreamingPreferences::retranslate()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newTranslator->load(QString(":/languages/qml_") + languageSuffix)) {
|
if (newTranslator->load(QString(":/languages/qml_") + languageSuffix)) {
|
||||||
qInfo() << "Successfully loaded translation for" << languageSuffix;
|
qInfo() << "Successfully loaded translation for " << languageSuffix;
|
||||||
|
|
||||||
translator = newTranslator;
|
translator = newTranslator;
|
||||||
QCoreApplication::installTranslator(translator);
|
QCoreApplication::installTranslator(translator);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qInfo() << "No translation available for" << languageSuffix;
|
qInfo() << "No translation available for " << languageSuffix;
|
||||||
delete newTranslator;
|
delete newTranslator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,10 +227,6 @@ QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Langua
|
|||||||
return "it";
|
return "it";
|
||||||
case LANG_HI:
|
case LANG_HI:
|
||||||
return "hi";
|
return "hi";
|
||||||
case LANG_PL:
|
|
||||||
return "pl";
|
|
||||||
case LANG_CS:
|
|
||||||
return "cs";
|
|
||||||
case LANG_AUTO:
|
case LANG_AUTO:
|
||||||
default:
|
default:
|
||||||
return QLocale::system().name();
|
return QLocale::system().name();
|
||||||
@@ -285,7 +269,6 @@ void StreamingPreferences::save()
|
|||||||
settings.setValue(SER_REVERSESCROLL, reverseScrollDirection);
|
settings.setValue(SER_REVERSESCROLL, reverseScrollDirection);
|
||||||
settings.setValue(SER_SWAPFACEBUTTONS, swapFaceButtons);
|
settings.setValue(SER_SWAPFACEBUTTONS, swapFaceButtons);
|
||||||
settings.setValue(SER_CAPTURESYSKEYS, captureSysKeysMode);
|
settings.setValue(SER_CAPTURESYSKEYS, captureSysKeysMode);
|
||||||
settings.setValue(SER_KEEPAWAKE, keepAwake);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
int StreamingPreferences::getDefaultBitrate(int width, int height, int fps)
|
||||||
|
|||||||
@@ -88,8 +88,6 @@ public:
|
|||||||
LANG_EL,
|
LANG_EL,
|
||||||
LANG_IT,
|
LANG_IT,
|
||||||
LANG_HI,
|
LANG_HI,
|
||||||
LANG_PL,
|
|
||||||
LANG_CS,
|
|
||||||
};
|
};
|
||||||
Q_ENUM(Language);
|
Q_ENUM(Language);
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,11 @@ bool SdlAudioRenderer::prepareForPlayback(const OPUS_MULTISTREAM_CONFIGURATION*
|
|||||||
want.format = AUDIO_S16;
|
want.format = AUDIO_S16;
|
||||||
want.channels = opusConfig->channelCount;
|
want.channels = opusConfig->channelCount;
|
||||||
|
|
||||||
// On PulseAudio systems, setting a value too small can cause underruns for other
|
// This is supposed to be a power of 2, but our
|
||||||
// applications sharing this output device. We impose a floor of 480 samples (10 ms)
|
// frames contain a non-power of 2 number of samples,
|
||||||
// to mitigate this issue.
|
// so the slop would require buffering another full frame.
|
||||||
want.samples = SDL_max(480, opusConfig->samplesPerFrame);
|
// Specifying non-Po2 seems to work for our supported platforms.
|
||||||
|
want.samples = opusConfig->samplesPerFrame;
|
||||||
|
|
||||||
m_FrameSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
|
m_FrameSize = opusConfig->samplesPerFrame * sizeof(short) * opusConfig->channelCount;
|
||||||
|
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle Start+Select+L1+R1 as a gamepad quit combo
|
// Handle Start+Select+L1+R1 as a gamepad quit combo
|
||||||
if (state->buttons == (PLAY_FLAG | BACK_FLAG | LB_FLAG | RB_FLAG) && qgetenv("NO_GAMEPAD_QUIT") != "1") {
|
if (state->buttons == (PLAY_FLAG | BACK_FLAG | LB_FLAG | RB_FLAG)) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Detected quit gamepad button combo");
|
"Detected quit gamepad button combo");
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s
|
|||||||
m_MouseWasInVideoRegion(false),
|
m_MouseWasInVideoRegion(false),
|
||||||
m_PendingMouseButtonsAllUpOnVideoRegionLeave(false),
|
m_PendingMouseButtonsAllUpOnVideoRegionLeave(false),
|
||||||
m_PointerRegionLockActive(false),
|
m_PointerRegionLockActive(false),
|
||||||
m_PointerRegionLockToggledByUser(false),
|
|
||||||
m_FakeCaptureActive(false),
|
m_FakeCaptureActive(false),
|
||||||
m_CaptureSystemKeysMode(prefs.captureSysKeysMode),
|
m_CaptureSystemKeysMode(prefs.captureSysKeysMode),
|
||||||
m_MouseCursorCapturedVisibilityState(SDL_DISABLE),
|
m_MouseCursorCapturedVisibilityState(SDL_DISABLE),
|
||||||
@@ -340,11 +339,27 @@ void SdlInputHandler::updateKeyboardGrabState()
|
|||||||
// Don't close the window on Alt+F4 when keyboard grab is enabled
|
// Don't close the window on Alt+F4 when keyboard grab is enabled
|
||||||
SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, shouldGrab ? "1" : "0");
|
SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, shouldGrab ? "1" : "0");
|
||||||
|
|
||||||
|
if (shouldGrab) {
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 15)
|
#if SDL_VERSION_ATLEAST(2, 0, 15)
|
||||||
// On SDL 2.0.15+, we can get keyboard-only grab on Win32, X11, and Wayland.
|
// On SDL 2.0.15, we can get keyboard-only grab on Win32, X11, and Wayland.
|
||||||
// SDL 2.0.18 adds keyboard grab on macOS (if built with non-AppStore APIs).
|
// This does nothing on macOS but it sets the SDL_WINDOW_KEYBOARD_GRABBED flag
|
||||||
SDL_SetWindowKeyboardGrab(m_Window, shouldGrab ? SDL_TRUE : SDL_FALSE);
|
// that we look for to see if keyboard capture is enabled. We'll handle macOS
|
||||||
|
// ourselves below using the private CGSSetGlobalHotKeyOperatingMode() API.
|
||||||
|
SDL_SetWindowKeyboardGrab(m_Window, SDL_TRUE);
|
||||||
|
#else
|
||||||
|
// If we're in full-screen desktop mode and SDL doesn't have keyboard grab yet,
|
||||||
|
// grab the cursor (will grab the keyboard too on X11).
|
||||||
|
if (SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN) {
|
||||||
|
SDL_SetWindowGrab(m_Window, SDL_TRUE);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#if SDL_VERSION_ATLEAST(2, 0, 15)
|
||||||
|
// Allow the keyboard to leave the window
|
||||||
|
SDL_SetWindowKeyboardGrab(m_Window, SDL_FALSE);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SdlInputHandler::isSystemKeyCaptureActive()
|
bool SdlInputHandler::isSystemKeyCaptureActive()
|
||||||
|
|||||||
@@ -160,7 +160,6 @@ private:
|
|||||||
bool m_MouseWasInVideoRegion;
|
bool m_MouseWasInVideoRegion;
|
||||||
bool m_PendingMouseButtonsAllUpOnVideoRegionLeave;
|
bool m_PendingMouseButtonsAllUpOnVideoRegionLeave;
|
||||||
bool m_PointerRegionLockActive;
|
bool m_PointerRegionLockActive;
|
||||||
bool m_PointerRegionLockToggledByUser;
|
|
||||||
|
|
||||||
int m_GamepadMask;
|
int m_GamepadMask;
|
||||||
GamepadState m_GamepadState[MAX_GAMEPADS];
|
GamepadState m_GamepadState[MAX_GAMEPADS];
|
||||||
|
|||||||
@@ -130,12 +130,6 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
|
|||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Detected pointer region lock toggle combo");
|
"Detected pointer region lock toggle combo");
|
||||||
m_PointerRegionLockActive = !m_PointerRegionLockActive;
|
m_PointerRegionLockActive = !m_PointerRegionLockActive;
|
||||||
|
|
||||||
// Remember that the user changed this manually, so we don't mess with it anymore
|
|
||||||
// during windowed <-> full-screen transitions.
|
|
||||||
m_PointerRegionLockToggledByUser = true;
|
|
||||||
|
|
||||||
// Apply the new region lock
|
|
||||||
updatePointerRegionLock();
|
updatePointerRegionLock();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -219,12 +219,6 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
|
|||||||
event->preciseY = -event->preciseY;
|
event->preciseY = -event->preciseY;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_DARWIN
|
|
||||||
// HACK: Clamp the scroll values on macOS to prevent OS scroll acceleration
|
|
||||||
// from generating wild scroll deltas when scrolling quickly.
|
|
||||||
event->preciseY = SDL_clamp(event->preciseY, -1.0f, 1.0f);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
LiSendHighResScrollEvent((short)(event->preciseY * 120)); // WHEEL_DELTA
|
LiSendHighResScrollEvent((short)(event->preciseY * 120)); // WHEEL_DELTA
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@@ -234,11 +228,6 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
|
|||||||
event->y = -event->y;
|
event->y = -event->y;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_DARWIN
|
|
||||||
// See comment above
|
|
||||||
event->y = SDL_clamp(event->y, -1, 1);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
LiSendScrollEvent((signed char)event->y);
|
LiSendScrollEvent((signed char)event->y);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -291,16 +280,10 @@ void SdlInputHandler::updatePointerRegionLock()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our pointer lock behavior tracks with the fullscreen mode unless the user has
|
// If we're in full-screen exclusive mode or region lock is enabled, grab the cursor so it can't accidentally leave our window.
|
||||||
// toggled it themselves using the keyboard shortcut. If that's the case, they
|
if (isCaptureActive() &&
|
||||||
// have full control over it and we don't touch it anymore.
|
(m_PointerRegionLockActive ||
|
||||||
if (!m_PointerRegionLockToggledByUser) {
|
(SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN)) {
|
||||||
// Lock the pointer in true full-screen mode and leave it unlocked in other modes
|
|
||||||
m_PointerRegionLockActive = (SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If region lock is enabled, grab the cursor so it can't accidentally leave our window.
|
|
||||||
if (isCaptureActive() && m_PointerRegionLockActive) {
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
||||||
SDL_Rect src, dst;
|
SDL_Rect src, dst;
|
||||||
|
|
||||||
|
|||||||
+117
-224
@@ -22,19 +22,6 @@
|
|||||||
#define ICON_SIZE 64
|
#define ICON_SIZE 64
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// HACK: Remove once proper Dark Mode support lands in SDL
|
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
#include <SDL_syswm.h>
|
|
||||||
#include <dwmapi.h>
|
|
||||||
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE_OLD
|
|
||||||
#define DWMWA_USE_IMMERSIVE_DARK_MODE_OLD 19
|
|
||||||
#endif
|
|
||||||
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
|
|
||||||
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#define SDL_CODE_FLUSH_WINDOW_EVENT_BARRIER 100
|
#define SDL_CODE_FLUSH_WINDOW_EVENT_BARRIER 100
|
||||||
#define SDL_CODE_GAMECONTROLLER_RUMBLE 101
|
#define SDL_CODE_GAMECONTROLLER_RUMBLE 101
|
||||||
|
|
||||||
@@ -48,7 +35,6 @@
|
|||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QCursor>
|
#include <QCursor>
|
||||||
#include <QWindow>
|
|
||||||
|
|
||||||
#define CONN_TEST_SERVER "qt.conntest.moonlight-stream.org"
|
#define CONN_TEST_SERVER "qt.conntest.moonlight-stream.org"
|
||||||
|
|
||||||
@@ -111,7 +97,6 @@ void Session::clConnectionTerminated(int errorCode)
|
|||||||
emit s_ActiveSession->displayLaunchError(tr("Your network connection isn't performing well. Reduce your video bitrate setting or try a faster connection."));
|
emit s_ActiveSession->displayLaunchError(tr("Your network connection isn't performing well. Reduce your video bitrate setting or try a faster connection."));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ML_ERROR_PROTECTED_CONTENT:
|
|
||||||
case ML_ERROR_UNEXPECTED_EARLY_TERMINATION:
|
case ML_ERROR_UNEXPECTED_EARLY_TERMINATION:
|
||||||
s_ActiveSession->m_UnexpectedTermination = true;
|
s_ActiveSession->m_UnexpectedTermination = true;
|
||||||
emit s_ActiveSession->displayLaunchError(tr("Something went wrong on your host PC when starting the stream.") + "\n\n" +
|
emit s_ActiveSession->displayLaunchError(tr("Something went wrong on your host PC when starting the stream.") + "\n\n" +
|
||||||
@@ -119,12 +104,6 @@ void Session::clConnectionTerminated(int errorCode)
|
|||||||
tr("If the issue persists, try reinstalling your GPU drivers and GeForce Experience."));
|
tr("If the issue persists, try reinstalling your GPU drivers and GeForce Experience."));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ML_ERROR_FRAME_CONVERSION:
|
|
||||||
s_ActiveSession->m_UnexpectedTermination = true;
|
|
||||||
emit s_ActiveSession->displayLaunchError(tr("The host PC reported a fatal video encoding error.") + "\n\n" +
|
|
||||||
tr("Try disabling HDR mode, changing the streaming resolution, or changing your host PC's display resolution."));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
s_ActiveSession->m_UnexpectedTermination = true;
|
s_ActiveSession->m_UnexpectedTermination = true;
|
||||||
emit s_ActiveSession->displayLaunchError(tr("Connection terminated"));
|
emit s_ActiveSession->displayLaunchError(tr("Connection terminated"));
|
||||||
@@ -232,7 +211,6 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
|
|||||||
params.window = window;
|
params.window = window;
|
||||||
params.enableVsync = enableVsync;
|
params.enableVsync = enableVsync;
|
||||||
params.enableFramePacing = enableFramePacing;
|
params.enableFramePacing = enableFramePacing;
|
||||||
params.testOnly = testOnly;
|
|
||||||
params.vds = vds;
|
params.vds = vds;
|
||||||
|
|
||||||
memset(¶ms.hdrMetadata, 0, sizeof(params.hdrMetadata));
|
memset(¶ms.hdrMetadata, 0, sizeof(params.hdrMetadata));
|
||||||
@@ -305,9 +283,14 @@ int Session::drSubmitDecodeUnit(PDECODE_UNIT du)
|
|||||||
// We need to destroy the decoder on the main thread to satisfy
|
// We need to destroy the decoder on the main thread to satisfy
|
||||||
// some API constraints (like DXVA2). If we can't acquire it,
|
// some API constraints (like DXVA2). If we can't acquire it,
|
||||||
// that means the decoder is about to be destroyed, so we can
|
// that means the decoder is about to be destroyed, so we can
|
||||||
// safely return DR_OK and wait for the IDR frame request by
|
// safely return DR_OK and wait for m_NeedsIdr to be set by
|
||||||
// the decoder reinitialization code.
|
// the decoder reinitialization code.
|
||||||
|
|
||||||
|
if (s_ActiveSession->getAndClearPendingIdrFrameStatus()) {
|
||||||
|
// If we reset our decoder, we'll need to request an IDR frame
|
||||||
|
return DR_NEED_IDR;
|
||||||
|
}
|
||||||
|
|
||||||
if (SDL_AtomicTryLock(&s_ActiveSession->m_DecoderLock)) {
|
if (SDL_AtomicTryLock(&s_ActiveSession->m_DecoderLock)) {
|
||||||
IVideoDecoder* decoder = s_ActiveSession->m_VideoDecoder;
|
IVideoDecoder* decoder = s_ActiveSession->m_VideoDecoder;
|
||||||
if (decoder != nullptr) {
|
if (decoder != nullptr) {
|
||||||
@@ -440,7 +423,8 @@ bool Session::populateDecoderProperties(SDL_Window* window)
|
|||||||
m_StreamConfig.colorRange);
|
m_StreamConfig.colorRange);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_StreamConfig.colorRange = decoder->getDecoderColorRange();
|
// Limited is the default for GFE
|
||||||
|
m_StreamConfig.colorRange = COLOR_RANGE_LIMITED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,6 +449,7 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
|
|||||||
m_AudioMuted(false),
|
m_AudioMuted(false),
|
||||||
m_DisplayOriginX(0),
|
m_DisplayOriginX(0),
|
||||||
m_DisplayOriginY(0),
|
m_DisplayOriginY(0),
|
||||||
|
m_PendingWindowedTransition(false),
|
||||||
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
|
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
|
||||||
m_InputHandler(nullptr),
|
m_InputHandler(nullptr),
|
||||||
m_MouseEmulationRefCount(0),
|
m_MouseEmulationRefCount(0),
|
||||||
@@ -476,6 +461,7 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
|
|||||||
m_AudioSampleCount(0),
|
m_AudioSampleCount(0),
|
||||||
m_DropAudioEndTime(0)
|
m_DropAudioEndTime(0)
|
||||||
{
|
{
|
||||||
|
SDL_AtomicSet(&m_NeedsIdr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Session::initialize()
|
bool Session::initialize()
|
||||||
@@ -811,13 +797,8 @@ bool Session::validateLaunch(SDL_Window* testWindow)
|
|||||||
emitLaunchWarning(tr("An attached gamepad has no mapping and won't be usable. Visit the Moonlight help to resolve this."));
|
emitLaunchWarning(tr("An attached gamepad has no mapping and won't be usable. Visit the Moonlight help to resolve this."));
|
||||||
}
|
}
|
||||||
|
|
||||||
// NVENC will fail to initialize when any dimension exceeds 4096 using:
|
// NVENC will fail to initialize when using dimensions over 4096 and H.264.
|
||||||
// - H.264 on all versions of NVENC
|
if (m_StreamConfig.width > 4096 || m_StreamConfig.height > 4096) {
|
||||||
// - HEVC prior to Pascal
|
|
||||||
//
|
|
||||||
// However, if we aren't using Nvidia hosting software, don't assume anything about
|
|
||||||
// encoding capabilities by using HEVC Main 10 support. It will likely be wrong.
|
|
||||||
if ((m_StreamConfig.width > 4096 || m_StreamConfig.height > 4096) && m_Computer->isNvidiaServerSoftware) {
|
|
||||||
// Pascal added support for 8K HEVC encoding support. Maxwell 2 could encode HEVC but only up to 4K.
|
// Pascal added support for 8K HEVC encoding support. Maxwell 2 could encode HEVC but only up to 4K.
|
||||||
// We can't directly identify Pascal, but we can look for HEVC Main10 which was added in the same generation.
|
// We can't directly identify Pascal, but we can look for HEVC Main10 which was added in the same generation.
|
||||||
if (m_Computer->maxLumaPixelsHEVC == 0 || !(m_Computer->serverCodecModeSupport & 0x200)) {
|
if (m_Computer->maxLumaPixelsHEVC == 0 || !(m_Computer->serverCodecModeSupport & 0x200)) {
|
||||||
@@ -916,10 +897,12 @@ void Session::getWindowDimensions(int& x, int& y,
|
|||||||
int& width, int& height)
|
int& width, int& height)
|
||||||
{
|
{
|
||||||
int displayIndex = 0;
|
int displayIndex = 0;
|
||||||
|
bool fullScreen;
|
||||||
|
|
||||||
if (m_Window != nullptr) {
|
if (m_Window != nullptr) {
|
||||||
displayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
displayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||||
SDL_assert(displayIndex >= 0);
|
SDL_assert(displayIndex >= 0);
|
||||||
|
fullScreen = (SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN);
|
||||||
}
|
}
|
||||||
// Create our window on the same display that Qt's UI
|
// Create our window on the same display that Qt's UI
|
||||||
// was being displayed on.
|
// was being displayed on.
|
||||||
@@ -946,32 +929,38 @@ void Session::getWindowDimensions(int& x, int& y,
|
|||||||
i, SDL_GetError());
|
i, SDL_GetError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fullScreen = m_IsFullScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Rect usableBounds;
|
SDL_Rect usableBounds;
|
||||||
if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) == 0) {
|
if (fullScreen && SDL_GetDisplayBounds(displayIndex, &usableBounds) == 0) {
|
||||||
// Don't use more than 80% of the display to leave room for system UI
|
width = usableBounds.w;
|
||||||
// and ensure the target size is not odd (otherwise one of the sides
|
height = usableBounds.h;
|
||||||
// of the image will have a one-pixel black bar next to it).
|
}
|
||||||
SDL_Rect src, dst;
|
else if (SDL_GetDisplayUsableBounds(displayIndex, &usableBounds) == 0) {
|
||||||
src.x = src.y = dst.x = dst.y = 0;
|
width = usableBounds.w;
|
||||||
src.w = m_StreamConfig.width;
|
height = usableBounds.h;
|
||||||
src.h = m_StreamConfig.height;
|
|
||||||
dst.w = ((int)SDL_ceilf(usableBounds.w * 0.80f) & ~0x1);
|
|
||||||
dst.h = ((int)SDL_ceilf(usableBounds.h * 0.80f) & ~0x1);
|
|
||||||
|
|
||||||
// Scale the window size while preserving aspect ratio
|
if (m_Window != nullptr) {
|
||||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
int top, left, bottom, right;
|
||||||
|
|
||||||
// If the stream window can fit within the usable drawing area with 1:1
|
if (SDL_GetWindowBordersSize(m_Window, &top, &left, &bottom, &right) == 0) {
|
||||||
// scaling, do that rather than filling the screen.
|
width -= left + right;
|
||||||
if (m_StreamConfig.width < dst.w && m_StreamConfig.height < dst.h) {
|
height -= top + bottom;
|
||||||
width = m_StreamConfig.width;
|
}
|
||||||
height = m_StreamConfig.height;
|
else {
|
||||||
}
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
else {
|
"Unable to get window border size: %s",
|
||||||
width = dst.w;
|
SDL_GetError());
|
||||||
height = dst.h;
|
}
|
||||||
|
|
||||||
|
// If the stream window can fit within the usable drawing area with 1:1
|
||||||
|
// scaling, do that rather than filling the screen.
|
||||||
|
if (m_StreamConfig.width < width && m_StreamConfig.height < height) {
|
||||||
|
width = m_StreamConfig.width;
|
||||||
|
height = m_StreamConfig.height;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -997,7 +986,7 @@ void Session::updateOptimalWindowDisplayMode()
|
|||||||
// If this doesn't fit the selected resolution, use the native
|
// If this doesn't fit the selected resolution, use the native
|
||||||
// resolution of the panel (unscaled).
|
// resolution of the panel (unscaled).
|
||||||
if (desktopMode.w < m_ActiveVideoWidth || desktopMode.h < m_ActiveVideoHeight) {
|
if (desktopMode.w < m_ActiveVideoWidth || desktopMode.h < m_ActiveVideoHeight) {
|
||||||
if (!StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode)) {
|
if (!StreamUtils::getRealDesktopMode(displayIndex, &desktopMode)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1017,9 +1006,6 @@ void Session::updateOptimalWindowDisplayMode()
|
|||||||
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
|
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
|
||||||
if (mode.w == desktopMode.w && mode.h == desktopMode.h &&
|
if (mode.w == desktopMode.w && mode.h == desktopMode.h &&
|
||||||
mode.refresh_rate % m_StreamConfig.fps == 0) {
|
mode.refresh_rate % m_StreamConfig.fps == 0) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Found display mode with desktop resolution: %dx%dx%d",
|
|
||||||
mode.w, mode.h, mode.refresh_rate);
|
|
||||||
if (mode.refresh_rate > bestMode.refresh_rate) {
|
if (mode.refresh_rate > bestMode.refresh_rate) {
|
||||||
bestMode = mode;
|
bestMode = mode;
|
||||||
}
|
}
|
||||||
@@ -1027,26 +1013,18 @@ void Session::updateOptimalWindowDisplayMode()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we didn't find a mode that matched the current resolution and
|
// For KMSDRM backends where we exclusively own the display, we should opt
|
||||||
// had a high enough refresh rate, start looking for lower resolution
|
// to change the resolution if that allows us to hit the desired frame rate
|
||||||
// modes that can meet the required refresh rate and minimum video
|
if (bestMode.refresh_rate == 0 && strcmp(SDL_GetCurrentVideoDriver(), "KMSDRM") == 0) {
|
||||||
// resolution. We will also try to pick a display mode that matches
|
|
||||||
// aspect ratio closest to the video stream.
|
|
||||||
if (bestMode.refresh_rate == 0) {
|
|
||||||
float bestModeAspectRatio = 0;
|
|
||||||
float videoAspectRatio = (float)m_ActiveVideoWidth / (float)m_ActiveVideoHeight;
|
|
||||||
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
||||||
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
|
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
|
||||||
float modeAspectRatio = (float)mode.w / (float)mode.h;
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Detected display mode: %dx%dx%d",
|
||||||
|
mode.w, mode.h, mode.refresh_rate);
|
||||||
if (mode.w >= m_ActiveVideoWidth && mode.h >= m_ActiveVideoHeight &&
|
if (mode.w >= m_ActiveVideoWidth && mode.h >= m_ActiveVideoHeight &&
|
||||||
mode.refresh_rate % m_StreamConfig.fps == 0) {
|
mode.refresh_rate % m_StreamConfig.fps == 0) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
if (mode.refresh_rate > bestMode.refresh_rate) {
|
||||||
"Found display mode with video resolution: %dx%dx%d",
|
|
||||||
mode.w, mode.h, mode.refresh_rate);
|
|
||||||
if (mode.refresh_rate >= bestMode.refresh_rate &&
|
|
||||||
(bestModeAspectRatio == 0 || fabs(videoAspectRatio - modeAspectRatio) <= fabs(videoAspectRatio - bestModeAspectRatio))) {
|
|
||||||
bestMode = mode;
|
bestMode = mode;
|
||||||
bestModeAspectRatio = modeAspectRatio;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1059,7 +1037,7 @@ void Session::updateOptimalWindowDisplayMode()
|
|||||||
// divide our FPS setting). We'll stick to the default in
|
// divide our FPS setting). We'll stick to the default in
|
||||||
// this case.
|
// this case.
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"No matching display mode found; using desktop mode");
|
"No matching refresh rate found; using desktop mode");
|
||||||
bestMode = desktopMode;
|
bestMode = desktopMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1078,37 +1056,28 @@ void Session::toggleFullscreen()
|
|||||||
{
|
{
|
||||||
bool fullScreen = !(SDL_GetWindowFlags(m_Window) & m_FullScreenFlag);
|
bool fullScreen = !(SDL_GetWindowFlags(m_Window) & m_FullScreenFlag);
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
if (fullScreen) {
|
||||||
// Destroy the video decoder before toggling full-screen because D3D9 can try
|
if (m_FullScreenFlag == SDL_WINDOW_FULLSCREEN && m_InputHandler->isCaptureActive()) {
|
||||||
// to put the window back into full-screen before we've managed to destroy
|
// Confine the cursor to the window if we're capturing input while transitioning to full screen.
|
||||||
// the renderer. This leads to excessive flickering and can cause the window
|
SDL_SetWindowGrab(m_Window, SDL_TRUE);
|
||||||
// decorations to get messed up as SDL and D3D9 fight over the window style.
|
}
|
||||||
SDL_AtomicLock(&m_DecoderLock);
|
|
||||||
delete m_VideoDecoder;
|
|
||||||
m_VideoDecoder = nullptr;
|
|
||||||
SDL_AtomicUnlock(&m_DecoderLock);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Actually enter/leave fullscreen
|
SDL_SetWindowResizable(m_Window, SDL_FALSE);
|
||||||
SDL_SetWindowFullscreen(m_Window, fullScreen ? m_FullScreenFlag : 0);
|
SDL_SetWindowFullscreen(m_Window, m_FullScreenFlag);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Unconfine the cursor
|
||||||
|
SDL_SetWindowGrab(m_Window, SDL_FALSE);
|
||||||
|
|
||||||
#ifdef Q_OS_DARWIN
|
SDL_SetWindowFullscreen(m_Window, 0);
|
||||||
// SDL on macOS has a bug that causes the window size to be reset to crazy
|
SDL_SetWindowResizable(m_Window, SDL_TRUE);
|
||||||
// large dimensions when exiting out of true fullscreen mode. We can work
|
|
||||||
// around the issue by manually resetting the position and size here.
|
// Reposition the window when the resize is complete
|
||||||
if (!fullScreen && m_FullScreenFlag == SDL_WINDOW_FULLSCREEN) {
|
m_PendingWindowedTransition = true;
|
||||||
int x, y, width, height;
|
|
||||||
getWindowDimensions(x, y, width, height);
|
|
||||||
SDL_SetWindowSize(m_Window, width, height);
|
|
||||||
SDL_SetWindowPosition(m_Window, x, y);
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// Input handler might need to start/stop keyboard grab after changing modes
|
// Input handler might need to start/stop keyboard grab after changing modes
|
||||||
m_InputHandler->updateKeyboardGrabState();
|
m_InputHandler->updateKeyboardGrabState();
|
||||||
|
|
||||||
// Input handler might need stop/stop mouse grab after changing modes
|
|
||||||
m_InputHandler->updatePointerRegionLock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::notifyMouseEmulationMode(bool enabled)
|
void Session::notifyMouseEmulationMode(bool enabled)
|
||||||
@@ -1227,27 +1196,17 @@ bool Session::startConnectionAsync()
|
|||||||
m_Preferences->packetSize);
|
m_Preferences->packetSize);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Use 1392 byte video packets by default
|
// isReachableOverVpn() does network I/O, so we only attempt to check
|
||||||
m_StreamConfig.packetSize = 1392;
|
// VPN reachability if we've already contacted the PC successfully
|
||||||
|
if (m_Computer->isReachableOverVpn()) {
|
||||||
// getActiveAddressReachability() does network I/O, so we only attempt to check
|
// It looks like our route to this PC is over a VPN.
|
||||||
// reachability if we've already contacted the PC successfully.
|
|
||||||
switch (m_Computer->getActiveAddressReachability()) {
|
|
||||||
case NvComputer::RI_LAN:
|
|
||||||
// This address is on-link, so treat it as a local address
|
|
||||||
// even if it's not in RFC 1918 space or it's an IPv6 address.
|
|
||||||
m_StreamConfig.streamingRemotely = STREAM_CFG_LOCAL;
|
|
||||||
break;
|
|
||||||
case NvComputer::RI_VPN:
|
|
||||||
// It looks like our route to this PC is over a VPN, so cap at 1024 bytes.
|
|
||||||
// Treat it as remote even if the target address is in RFC 1918 address space.
|
// Treat it as remote even if the target address is in RFC 1918 address space.
|
||||||
m_StreamConfig.streamingRemotely = STREAM_CFG_REMOTE;
|
m_StreamConfig.streamingRemotely = STREAM_CFG_REMOTE;
|
||||||
m_StreamConfig.packetSize = 1024;
|
m_StreamConfig.packetSize = 1024;
|
||||||
break;
|
}
|
||||||
default:
|
else {
|
||||||
// If we don't have reachability info, let moonlight-common-c decide.
|
|
||||||
m_StreamConfig.streamingRemotely = STREAM_CFG_AUTO;
|
m_StreamConfig.streamingRemotely = STREAM_CFG_AUTO;
|
||||||
break;
|
m_StreamConfig.packetSize = 1392;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1282,6 +1241,11 @@ void Session::flushWindowEvents()
|
|||||||
SDL_PushEvent(&flushEvent);
|
SDL_PushEvent(&flushEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Session::getAndClearPendingIdrFrameStatus()
|
||||||
|
{
|
||||||
|
return SDL_AtomicSet(&m_NeedsIdr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
class ExecThread : public QThread
|
class ExecThread : public QThread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -1406,27 +1370,12 @@ void Session::execInternal()
|
|||||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||||
|
|
||||||
// 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,
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Window = SDL_CreateWindow("Moonlight",
|
m_Window = SDL_CreateWindow("Moonlight",
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
defaultWindowFlags | StreamUtils::getPlatformWindowFlags());
|
SDL_WINDOW_ALLOW_HIGHDPI | StreamUtils::getPlatformWindowFlags());
|
||||||
if (!m_Window) {
|
if (!m_Window) {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_CreateWindow() failed with platform flags: %s",
|
"SDL_CreateWindow() failed with platform flags: %s",
|
||||||
@@ -1437,7 +1386,7 @@ void Session::execInternal()
|
|||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
defaultWindowFlags);
|
SDL_WINDOW_ALLOW_HIGHDPI);
|
||||||
if (!m_Window) {
|
if (!m_Window) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_CreateWindow() failed: %s",
|
"SDL_CreateWindow() failed: %s",
|
||||||
@@ -1451,52 +1400,6 @@ void Session::execInternal()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: Remove once proper Dark Mode support lands in SDL
|
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
{
|
|
||||||
BOOL darkModeEnabled = FALSE;
|
|
||||||
|
|
||||||
// Query whether dark mode is enabled for our Qt window (which tracks the OS dark mode state)
|
|
||||||
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;
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
|
|
||||||
if (SDL_GetWindowWMInfo(m_Window, &info) && info.subsystem == SDL_SYSWM_WINDOWS) {
|
|
||||||
RECT clientRect;
|
|
||||||
HBRUSH blackBrush;
|
|
||||||
|
|
||||||
// Draw a black background (otherwise our window will be bright white).
|
|
||||||
//
|
|
||||||
// TODO: Remove when SDL does this itself
|
|
||||||
blackBrush = CreateSolidBrush(0);
|
|
||||||
GetClientRect(info.info.win.window, &clientRect);
|
|
||||||
FillRect(info.info.win.hdc, &clientRect, blackBrush);
|
|
||||||
DeleteObject(blackBrush);
|
|
||||||
|
|
||||||
// If dark mode is enabled, propagate that to our SDL window
|
|
||||||
if (darkModeEnabled) {
|
|
||||||
if (FAILED(DwmSetWindowAttribute(info.info.win.window, DWMWA_USE_IMMERSIVE_DARK_MODE, &darkModeEnabled, sizeof(darkModeEnabled)))) {
|
|
||||||
DwmSetWindowAttribute(info.info.win.window, DWMWA_USE_IMMERSIVE_DARK_MODE_OLD, &darkModeEnabled, sizeof(darkModeEnabled));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle non-client rendering off and back on to ensure dark mode takes effect on Windows 10.
|
|
||||||
// DWM doesn't seem to correctly invalidate the non-client area after enabling dark mode.
|
|
||||||
DWMNCRENDERINGPOLICY ncPolicy = DWMNCRP_DISABLED;
|
|
||||||
DwmSetWindowAttribute(info.info.win.window, DWMWA_NCRENDERING_POLICY, &ncPolicy, sizeof(ncPolicy));
|
|
||||||
ncPolicy = DWMNCRP_ENABLED;
|
|
||||||
DwmSetWindowAttribute(info.info.win.window, DWMWA_NCRENDERING_POLICY, &ncPolicy, sizeof(ncPolicy));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_InputHandler->setWindow(m_Window);
|
m_InputHandler->setWindow(m_Window);
|
||||||
|
|
||||||
QSvgRenderer svgIconRenderer(QString(":/res/moonlight.svg"));
|
QSvgRenderer svgIconRenderer(QString(":/res/moonlight.svg"));
|
||||||
@@ -1520,32 +1423,27 @@ void Session::execInternal()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Update the window display mode based on our current monitor
|
// For non-full screen windows, call getWindowDimensions()
|
||||||
// for if/when we enter full-screen mode.
|
// again after creating a window to allow it to account
|
||||||
updateOptimalWindowDisplayMode();
|
// for window chrome size.
|
||||||
|
if (!m_IsFullScreen) {
|
||||||
|
getWindowDimensions(x, y, width, height);
|
||||||
|
|
||||||
// Enter full screen if requested
|
// We must set the size before the position because centering
|
||||||
if (m_IsFullScreen) {
|
// won't work unless it knows the final size of the window.
|
||||||
|
SDL_SetWindowSize(m_Window, width, height);
|
||||||
|
SDL_SetWindowPosition(m_Window, x, y);
|
||||||
|
|
||||||
|
// Passing SDL_WINDOW_RESIZABLE to set this during window
|
||||||
|
// creation causes our window to be full screen for some reason
|
||||||
|
SDL_SetWindowResizable(m_Window, SDL_TRUE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Update the window display mode based on our current monitor
|
||||||
|
updateOptimalWindowDisplayMode();
|
||||||
|
|
||||||
|
// Enter full screen
|
||||||
SDL_SetWindowFullscreen(m_Window, m_FullScreenFlag);
|
SDL_SetWindowFullscreen(m_Window, m_FullScreenFlag);
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
SDL_SysWMinfo info;
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
|
|
||||||
// Draw a black background again after entering full-screen to avoid visual artifacts
|
|
||||||
// where the old window decorations were before.
|
|
||||||
//
|
|
||||||
// TODO: Remove when SDL does this itself
|
|
||||||
if (SDL_GetWindowWMInfo(m_Window, &info) && info.subsystem == SDL_SYSWM_WINDOWS) {
|
|
||||||
RECT clientRect;
|
|
||||||
HBRUSH blackBrush;
|
|
||||||
|
|
||||||
blackBrush = CreateSolidBrush(0);
|
|
||||||
GetClientRect(info.info.win.window, &clientRect);
|
|
||||||
FillRect(info.info.win.hdc, &clientRect, blackBrush);
|
|
||||||
DeleteObject(blackBrush);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool needsFirstEnterCapture = false;
|
bool needsFirstEnterCapture = false;
|
||||||
@@ -1638,9 +1536,7 @@ void Session::execInternal()
|
|||||||
case SDL_USEREVENT:
|
case SDL_USEREVENT:
|
||||||
switch (event.user.code) {
|
switch (event.user.code) {
|
||||||
case SDL_CODE_FRAME_READY:
|
case SDL_CODE_FRAME_READY:
|
||||||
if (m_VideoDecoder != nullptr) {
|
m_VideoDecoder->renderFrameOnMainThread();
|
||||||
m_VideoDecoder->renderFrameOnMainThread();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SDL_CODE_HIDE_CURSOR:
|
case SDL_CODE_HIDE_CURSOR:
|
||||||
SDL_ShowCursor(SDL_DISABLE);
|
SDL_ShowCursor(SDL_DISABLE);
|
||||||
@@ -1698,17 +1594,9 @@ void Session::execInternal()
|
|||||||
// Check that the window display hasn't changed. If it has, we want
|
// Check that the window display hasn't changed. If it has, we want
|
||||||
// to recreate the decoder to allow it to adapt to the new display.
|
// to recreate the decoder to allow it to adapt to the new display.
|
||||||
// This will allow Pacer to pull the new display refresh rate.
|
// This will allow Pacer to pull the new display refresh rate.
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 18)
|
|
||||||
// On SDL 2.0.18+, there's an event for this specific situation
|
|
||||||
if (event.window.event != SDL_WINDOWEVENT_DISPLAY_CHANGED) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Prior to SDL 2.0.18, we must check the display index for each window event
|
|
||||||
if (SDL_GetWindowDisplayIndex(m_Window) == currentDisplayIndex) {
|
if (SDL_GetWindowDisplayIndex(m_Window) == currentDisplayIndex) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
// We can get a resize event after being minimized. Recreating the renderer at that time can cause
|
// We can get a resize event after being minimized. Recreating the renderer at that time can cause
|
||||||
@@ -1720,6 +1608,20 @@ void Session::execInternal()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Complete any repositioning that was deferred until
|
||||||
|
// the resize from full-screen to windowed had completed.
|
||||||
|
// If we try to do this immediately, the resize won't take effect
|
||||||
|
// properly on Windows.
|
||||||
|
if (m_PendingWindowedTransition) {
|
||||||
|
m_PendingWindowedTransition = false;
|
||||||
|
|
||||||
|
int x, y, width, height;
|
||||||
|
getWindowDimensions(x, y, width, height);
|
||||||
|
|
||||||
|
SDL_SetWindowSize(m_Window, width, height);
|
||||||
|
SDL_SetWindowPosition(m_Window, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_FlushingWindowEventsRef > 0) {
|
if (m_FlushingWindowEventsRef > 0) {
|
||||||
// Ignore window events for renderer reset if flushing
|
// Ignore window events for renderer reset if flushing
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -1740,12 +1642,6 @@ void Session::execInternal()
|
|||||||
case SDL_RENDER_DEVICE_RESET:
|
case SDL_RENDER_DEVICE_RESET:
|
||||||
case SDL_RENDER_TARGETS_RESET:
|
case SDL_RENDER_TARGETS_RESET:
|
||||||
|
|
||||||
if (event.type != SDL_WINDOWEVENT) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Recreating renderer by internal request: %d",
|
|
||||||
event.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_AtomicLock(&m_DecoderLock);
|
SDL_AtomicLock(&m_DecoderLock);
|
||||||
|
|
||||||
// Destroy the old decoder
|
// Destroy the old decoder
|
||||||
@@ -1758,11 +1654,8 @@ void Session::execInternal()
|
|||||||
flushWindowEvents();
|
flushWindowEvents();
|
||||||
|
|
||||||
// Update the window display mode based on our current monitor
|
// Update the window display mode based on our current monitor
|
||||||
// NB: Avoid a useless modeset by only doing this if it changed.
|
currentDisplayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||||
if (currentDisplayIndex != SDL_GetWindowDisplayIndex(m_Window)) {
|
updateOptimalWindowDisplayMode();
|
||||||
currentDisplayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
|
||||||
updateOptimalWindowDisplayMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that the old decoder is dead, flush any events it may
|
// Now that the old decoder is dead, flush any events it may
|
||||||
// have queued to reset itself (if this reset was the result
|
// have queued to reset itself (if this reset was the result
|
||||||
@@ -1809,7 +1702,7 @@ void Session::execInternal()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Request an IDR frame to complete the reset
|
// Request an IDR frame to complete the reset
|
||||||
LiRequestIdrFrame();
|
SDL_AtomicSet(&m_NeedsIdr, 1);
|
||||||
|
|
||||||
// Set HDR mode. We may miss the callback if we're in the middle
|
// Set HDR mode. We may miss the callback if we're in the middle
|
||||||
// of recreating our decoder at the time the HDR transition happens.
|
// of recreating our decoder at the time the HDR transition happens.
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ public:
|
|||||||
|
|
||||||
void flushWindowEvents();
|
void flushWindowEvents();
|
||||||
|
|
||||||
|
bool getAndClearPendingIdrFrameStatus();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void stageStarting(QString stage);
|
void stageStarting(QString stage);
|
||||||
|
|
||||||
@@ -154,12 +156,14 @@ private:
|
|||||||
SDL_Window* m_Window;
|
SDL_Window* m_Window;
|
||||||
IVideoDecoder* m_VideoDecoder;
|
IVideoDecoder* m_VideoDecoder;
|
||||||
SDL_SpinLock m_DecoderLock;
|
SDL_SpinLock m_DecoderLock;
|
||||||
|
SDL_atomic_t m_NeedsIdr;
|
||||||
bool m_AudioDisabled;
|
bool m_AudioDisabled;
|
||||||
bool m_AudioMuted;
|
bool m_AudioMuted;
|
||||||
Uint32 m_FullScreenFlag;
|
Uint32 m_FullScreenFlag;
|
||||||
int m_DisplayOriginX;
|
int m_DisplayOriginX;
|
||||||
int m_DisplayOriginY;
|
int m_DisplayOriginY;
|
||||||
bool m_ThreadedExec;
|
bool m_ThreadedExec;
|
||||||
|
bool m_PendingWindowedTransition;
|
||||||
bool m_UnexpectedTermination;
|
bool m_UnexpectedTermination;
|
||||||
SdlInputHandler* m_InputHandler;
|
SdlInputHandler* m_InputHandler;
|
||||||
int m_MouseEmulationRefCount;
|
int m_MouseEmulationRefCount;
|
||||||
|
|||||||
@@ -17,16 +17,18 @@ Uint32 StreamUtils::getPlatformWindowFlags()
|
|||||||
|
|
||||||
void StreamUtils::scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst)
|
void StreamUtils::scaleSourceToDestinationSurface(SDL_Rect* src, SDL_Rect* dst)
|
||||||
{
|
{
|
||||||
int dstH = SDL_ceilf((float)dst->w * src->h / src->w);
|
int dstH = dst->w * src->h / src->w;
|
||||||
int dstW = SDL_ceilf((float)dst->h * src->w / src->h);
|
int dstW = dst->h * src->w / src->h;
|
||||||
|
|
||||||
if (dstH > dst->h) {
|
if (dstH > dst->h) {
|
||||||
dst->x += (dst->w - dstW) / 2;
|
dst->x += (dst->w - dstW) / 2;
|
||||||
dst->w = dstW;
|
dst->w = dstW;
|
||||||
|
SDL_assert(dst->w * src->h / src->w <= dst->h);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dst->y += (dst->h - dstH) / 2;
|
dst->y += (dst->h - dstH) / 2;
|
||||||
dst->h = dstH;
|
dst->h = dstH;
|
||||||
|
SDL_assert(dst->h * src->w / src->h <= dst->w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +94,7 @@ int StreamUtils::getDisplayRefreshRate(SDL_Window* window)
|
|||||||
return mode.refresh_rate;
|
return mode.refresh_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode)
|
bool StreamUtils::getRealDesktopMode(int displayIndex, SDL_DisplayMode* mode)
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_DARWIN
|
#ifdef Q_OS_DARWIN
|
||||||
#define MAX_DISPLAYS 16
|
#define MAX_DISPLAYS 16
|
||||||
@@ -136,26 +138,11 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// We need to get the true display resolution without DPI scaling (since we use High DPI).
|
if (SDL_GetDesktopDisplayMode(displayIndex, mode) != 0) {
|
||||||
// Windows returns the real display resolution here, even if DPI scaling is enabled.
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
// macOS and Wayland report a resolution that includes the DPI scaling factor. Picking
|
"SDL_GetDesktopDisplayMode() failed: %s",
|
||||||
// the first mode on Wayland will get the native resolution without the scaling factor
|
SDL_GetError());
|
||||||
// (and macOS is handled in the #ifdef above).
|
return false;
|
||||||
if (!strcmp(SDL_GetCurrentVideoDriver(), "wayland")) {
|
|
||||||
if (SDL_GetDisplayMode(displayIndex, 0, mode) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetDisplayMode() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (SDL_GetDesktopDisplayMode(displayIndex, mode) != 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetDesktopDisplayMode() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public:
|
|||||||
void screenSpaceToNormalizedDeviceCoords(SDL_Rect* src, SDL_FRect* dst, int viewportWidth, int viewportHeight);
|
void screenSpaceToNormalizedDeviceCoords(SDL_Rect* src, SDL_FRect* dst, int viewportWidth, int viewportHeight);
|
||||||
|
|
||||||
static
|
static
|
||||||
bool getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode);
|
bool getRealDesktopMode(int displayIndex, SDL_DisplayMode* mode);
|
||||||
|
|
||||||
static
|
static
|
||||||
int getDisplayRefreshRate(SDL_Window* window);
|
int getDisplayRefreshRate(SDL_Window* window);
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ typedef struct _DECODER_PARAMETERS {
|
|||||||
int frameRate;
|
int frameRate;
|
||||||
bool enableVsync;
|
bool enableVsync;
|
||||||
bool enableFramePacing;
|
bool enableFramePacing;
|
||||||
bool testOnly;
|
|
||||||
HDR_MASTERING_METADATA hdrMetadata;
|
HDR_MASTERING_METADATA hdrMetadata;
|
||||||
} DECODER_PARAMETERS, *PDECODER_PARAMETERS;
|
} DECODER_PARAMETERS, *PDECODER_PARAMETERS;
|
||||||
|
|
||||||
@@ -68,7 +67,6 @@ public:
|
|||||||
virtual bool isHdrSupported() = 0;
|
virtual bool isHdrSupported() = 0;
|
||||||
virtual int getDecoderCapabilities() = 0;
|
virtual int getDecoderCapabilities() = 0;
|
||||||
virtual int getDecoderColorspace() = 0;
|
virtual int getDecoderColorspace() = 0;
|
||||||
virtual int getDecoderColorRange() = 0;
|
|
||||||
virtual QSize getDecoderMaxResolution() = 0;
|
virtual QSize getDecoderMaxResolution() = 0;
|
||||||
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
||||||
virtual void renderFrameOnMainThread() = 0;
|
virtual void renderFrameOnMainThread() = 0;
|
||||||
|
|||||||
@@ -57,11 +57,6 @@ bool CUDARenderer::isDirectRenderingSupported()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CUDARenderer::getDecoderCapabilities()
|
|
||||||
{
|
|
||||||
return CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC;
|
|
||||||
}
|
|
||||||
|
|
||||||
CUDAGLInteropHelper::CUDAGLInteropHelper(AVHWDeviceContext* context)
|
CUDAGLInteropHelper::CUDAGLInteropHelper(AVHWDeviceContext* context)
|
||||||
: m_Funcs(nullptr),
|
: m_Funcs(nullptr),
|
||||||
m_Context((AVCUDADeviceContext*)context->hwctx)
|
m_Context((AVCUDADeviceContext*)context->hwctx)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ public:
|
|||||||
virtual void renderFrame(AVFrame* frame) override;
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
virtual bool needsTestFrame() override;
|
virtual bool needsTestFrame() override;
|
||||||
virtual bool isDirectRenderingSupported() override;
|
virtual bool isDirectRenderingSupported() override;
|
||||||
virtual int getDecoderCapabilities() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AVBufferRef* m_HwContext;
|
AVBufferRef* m_HwContext;
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
#include <SDL_syswm.h>
|
#include <SDL_syswm.h>
|
||||||
#include <VersionHelpers.h>
|
#include <VersionHelpers.h>
|
||||||
|
|
||||||
#include <dwmapi.h>
|
|
||||||
|
|
||||||
#define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); }
|
#define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); }
|
||||||
|
|
||||||
typedef struct _VERTEX
|
typedef struct _VERTEX
|
||||||
@@ -73,21 +71,20 @@ typedef struct _CSC_CONST_BUF
|
|||||||
} CSC_CONST_BUF, *PCSC_CONST_BUF;
|
} CSC_CONST_BUF, *PCSC_CONST_BUF;
|
||||||
static_assert(sizeof(CSC_CONST_BUF) % 16 == 0, "Constant buffer sizes must be a multiple of 16");
|
static_assert(sizeof(CSC_CONST_BUF) % 16 == 0, "Constant buffer sizes must be a multiple of 16");
|
||||||
|
|
||||||
D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
|
D3D11VARenderer::D3D11VARenderer()
|
||||||
: m_DecoderSelectionPass(decoderSelectionPass),
|
: m_Factory(nullptr),
|
||||||
m_Factory(nullptr),
|
|
||||||
m_Device(nullptr),
|
m_Device(nullptr),
|
||||||
m_SwapChain(nullptr),
|
m_SwapChain(nullptr),
|
||||||
m_DeviceContext(nullptr),
|
m_DeviceContext(nullptr),
|
||||||
m_RenderTargetView(nullptr),
|
m_RenderTargetView(nullptr),
|
||||||
m_LastColorSpace(-1),
|
m_LastColorSpace(AVCOL_SPC_UNSPECIFIED),
|
||||||
m_LastFullRange(false),
|
m_LastColorRange(AVCOL_RANGE_UNSPECIFIED),
|
||||||
m_AllowTearing(false),
|
m_AllowTearing(false),
|
||||||
|
m_FrameWaitableObject(nullptr),
|
||||||
m_VideoGenericPixelShader(nullptr),
|
m_VideoGenericPixelShader(nullptr),
|
||||||
m_VideoBt601LimPixelShader(nullptr),
|
m_VideoBt601LimPixelShader(nullptr),
|
||||||
m_VideoBt2020LimPixelShader(nullptr),
|
m_VideoBt2020LimPixelShader(nullptr),
|
||||||
m_VideoVertexBuffer(nullptr),
|
m_VideoVertexBuffer(nullptr),
|
||||||
m_VideoTexture(nullptr),
|
|
||||||
m_OverlayLock(0),
|
m_OverlayLock(0),
|
||||||
m_OverlayPixelShader(nullptr),
|
m_OverlayPixelShader(nullptr),
|
||||||
m_HwDeviceContext(nullptr),
|
m_HwDeviceContext(nullptr),
|
||||||
@@ -99,14 +96,10 @@ D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
|
|||||||
RtlZeroMemory(m_VideoTextureResourceViews, sizeof(m_VideoTextureResourceViews));
|
RtlZeroMemory(m_VideoTextureResourceViews, sizeof(m_VideoTextureResourceViews));
|
||||||
|
|
||||||
m_ContextLock = SDL_CreateMutex();
|
m_ContextLock = SDL_CreateMutex();
|
||||||
|
|
||||||
DwmEnableMMCSS(TRUE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
D3D11VARenderer::~D3D11VARenderer()
|
D3D11VARenderer::~D3D11VARenderer()
|
||||||
{
|
{
|
||||||
DwmEnableMMCSS(FALSE);
|
|
||||||
|
|
||||||
SDL_DestroyMutex(m_ContextLock);
|
SDL_DestroyMutex(m_ContextLock);
|
||||||
|
|
||||||
SAFE_COM_RELEASE(m_VideoVertexBuffer);
|
SAFE_COM_RELEASE(m_VideoVertexBuffer);
|
||||||
@@ -115,11 +108,10 @@ D3D11VARenderer::~D3D11VARenderer()
|
|||||||
SAFE_COM_RELEASE(m_VideoGenericPixelShader);
|
SAFE_COM_RELEASE(m_VideoGenericPixelShader);
|
||||||
|
|
||||||
for (int i = 0; i < ARRAYSIZE(m_VideoTextureResourceViews); i++) {
|
for (int i = 0; i < ARRAYSIZE(m_VideoTextureResourceViews); i++) {
|
||||||
SAFE_COM_RELEASE(m_VideoTextureResourceViews[i]);
|
SAFE_COM_RELEASE(m_VideoTextureResourceViews[i][0]);
|
||||||
|
SAFE_COM_RELEASE(m_VideoTextureResourceViews[i][1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
SAFE_COM_RELEASE(m_VideoTexture);
|
|
||||||
|
|
||||||
for (int i = 0; i < ARRAYSIZE(m_OverlayVertexBuffers); i++) {
|
for (int i = 0; i < ARRAYSIZE(m_OverlayVertexBuffers); i++) {
|
||||||
SAFE_COM_RELEASE(m_OverlayVertexBuffers[i]);
|
SAFE_COM_RELEASE(m_OverlayVertexBuffers[i]);
|
||||||
}
|
}
|
||||||
@@ -135,6 +127,15 @@ D3D11VARenderer::~D3D11VARenderer()
|
|||||||
SAFE_COM_RELEASE(m_OverlayPixelShader);
|
SAFE_COM_RELEASE(m_OverlayPixelShader);
|
||||||
|
|
||||||
SAFE_COM_RELEASE(m_RenderTargetView);
|
SAFE_COM_RELEASE(m_RenderTargetView);
|
||||||
|
|
||||||
|
if (m_FrameWaitableObject != nullptr) {
|
||||||
|
CloseHandle(m_FrameWaitableObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_SwapChain != nullptr && !m_Windowed) {
|
||||||
|
// It's illegal to destroy a full-screen swapchain. Make sure we're in windowed mode.
|
||||||
|
m_SwapChain->SetFullscreenState(FALSE, nullptr);
|
||||||
|
}
|
||||||
SAFE_COM_RELEASE(m_SwapChain);
|
SAFE_COM_RELEASE(m_SwapChain);
|
||||||
|
|
||||||
if (m_HwFramesContext != nullptr) {
|
if (m_HwFramesContext != nullptr) {
|
||||||
@@ -153,88 +154,6 @@ D3D11VARenderer::~D3D11VARenderer()
|
|||||||
SAFE_COM_RELEASE(m_Factory);
|
SAFE_COM_RELEASE(m_Factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapterNotFound)
|
|
||||||
{
|
|
||||||
bool success = false;
|
|
||||||
IDXGIAdapter1* adapter = nullptr;
|
|
||||||
DXGI_ADAPTER_DESC1 adapterDesc;
|
|
||||||
HRESULT hr;
|
|
||||||
|
|
||||||
SDL_assert(m_Device == nullptr);
|
|
||||||
SDL_assert(m_DeviceContext == nullptr);
|
|
||||||
|
|
||||||
hr = m_Factory->EnumAdapters1(adapterIndex, &adapter);
|
|
||||||
if (hr == DXGI_ERROR_NOT_FOUND) {
|
|
||||||
// Expected at the end of enumeration
|
|
||||||
goto Exit;
|
|
||||||
}
|
|
||||||
else if (FAILED(hr)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"IDXGIFactory::EnumAdapters1() failed: %x",
|
|
||||||
hr);
|
|
||||||
goto Exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr = adapter->GetDesc1(&adapterDesc);
|
|
||||||
if (FAILED(hr)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"IDXGIAdapter::GetDesc() failed: %x",
|
|
||||||
hr);
|
|
||||||
goto Exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adapterDesc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) {
|
|
||||||
// Skip the WARP device. We know it will fail.
|
|
||||||
goto Exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Detected GPU %d: %S (%x:%x)",
|
|
||||||
adapterIndex,
|
|
||||||
adapterDesc.Description,
|
|
||||||
adapterDesc.VendorId,
|
|
||||||
adapterDesc.DeviceId);
|
|
||||||
|
|
||||||
hr = D3D11CreateDevice(adapter,
|
|
||||||
D3D_DRIVER_TYPE_UNKNOWN,
|
|
||||||
nullptr,
|
|
||||||
D3D11_CREATE_DEVICE_VIDEO_SUPPORT
|
|
||||||
#ifdef QT_DEBUG
|
|
||||||
| D3D11_CREATE_DEVICE_DEBUG
|
|
||||||
#endif
|
|
||||||
,
|
|
||||||
nullptr,
|
|
||||||
0,
|
|
||||||
D3D11_SDK_VERSION,
|
|
||||||
&m_Device,
|
|
||||||
nullptr,
|
|
||||||
&m_DeviceContext);
|
|
||||||
if (FAILED(hr)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"D3D11CreateDevice() failed: %x",
|
|
||||||
hr);
|
|
||||||
goto Exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkDecoderSupport(adapter)) {
|
|
||||||
m_DeviceContext->Release();
|
|
||||||
m_DeviceContext = nullptr;
|
|
||||||
m_Device->Release();
|
|
||||||
m_Device = nullptr;
|
|
||||||
|
|
||||||
goto Exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
|
|
||||||
Exit:
|
|
||||||
if (adapterNotFound != nullptr) {
|
|
||||||
*adapterNotFound = (adapter == nullptr);
|
|
||||||
}
|
|
||||||
SAFE_COM_RELEASE(adapter);
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
|
bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
|
||||||
{
|
{
|
||||||
int adapterIndex, outputIndex;
|
int adapterIndex, outputIndex;
|
||||||
@@ -242,14 +161,9 @@ bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
|
|
||||||
m_DecoderParams = *params;
|
m_DecoderParams = *params;
|
||||||
|
|
||||||
if (qgetenv("D3D11VA_ENABLED") == "0") {
|
// Use DXVA2 on anything older than Win10, so we don't have to handle a bunch
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
// of legacy Win7/Win8 codepaths in here.
|
||||||
"D3D11VA is disabled by environment variable");
|
if (!IsWindows10OrGreater()) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (!IsWindows10OrGreater()) {
|
|
||||||
// Use DXVA2 on anything older than Win10, so we don't have to handle a bunch
|
|
||||||
// of legacy Win7/Win8 codepaths in here.
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"D3D11VA renderer is only supported on Windows 10 or later.");
|
"D3D11VA renderer is only supported on Windows 10 or later.");
|
||||||
return false;
|
return false;
|
||||||
@@ -271,95 +185,149 @@ bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First try the adapter corresponding to the display where our window resides.
|
IDXGIAdapter* adapter;
|
||||||
// This will let us avoid a copy if the display GPU has the required decoder.
|
hr = m_Factory->EnumAdapters(adapterIndex, &adapter);
|
||||||
if (!createDeviceByAdapterIndex(adapterIndex)) {
|
if (FAILED(hr)) {
|
||||||
// If that didn't work, we'll try all GPUs in order until we find one
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
// or run out of GPUs (DXGI_ERROR_NOT_FOUND from EnumAdapters())
|
"IDXGIFactory::EnumAdapters() failed: %x",
|
||||||
bool adapterNotFound = false;
|
hr);
|
||||||
for (int i = 0; !adapterNotFound; i++) {
|
return false;
|
||||||
if (i == adapterIndex) {
|
|
||||||
// Don't try the same GPU again
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (createDeviceByAdapterIndex(i, &adapterNotFound)) {
|
|
||||||
// This GPU worked! Continue initialization.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (adapterNotFound) {
|
|
||||||
SDL_assert(m_Device == nullptr);
|
|
||||||
SDL_assert(m_DeviceContext == nullptr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hr = D3D11CreateDevice(adapter,
|
||||||
|
D3D_DRIVER_TYPE_UNKNOWN,
|
||||||
|
nullptr,
|
||||||
|
D3D11_CREATE_DEVICE_VIDEO_SUPPORT
|
||||||
|
#ifdef QT_DEBUG
|
||||||
|
| D3D11_CREATE_DEVICE_DEBUG
|
||||||
|
#endif
|
||||||
|
,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
D3D11_SDK_VERSION,
|
||||||
|
&m_Device,
|
||||||
|
nullptr,
|
||||||
|
&m_DeviceContext);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"D3D11CreateDevice() failed: %x",
|
||||||
|
hr);
|
||||||
|
adapter->Release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkDecoderSupport(adapter)) {
|
||||||
|
adapter->Release();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter->Release();
|
||||||
|
adapter = nullptr;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
m_Windowed = (SDL_GetWindowFlags(params->window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN;
|
||||||
|
#else
|
||||||
|
// Always use windowed or borderless windowed mode for now. SDL does mode-setting for us
|
||||||
|
// in full-screen exclusive mode, so this actually works out okay.
|
||||||
|
m_Windowed = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
|
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
|
||||||
swapChainDesc.Stereo = FALSE;
|
swapChainDesc.Stereo = FALSE;
|
||||||
swapChainDesc.SampleDesc.Count = 1;
|
swapChainDesc.SampleDesc.Count = 1;
|
||||||
swapChainDesc.SampleDesc.Quality = 0;
|
swapChainDesc.SampleDesc.Quality = 0;
|
||||||
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||||
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
|
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
|
||||||
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
|
||||||
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
|
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
|
||||||
swapChainDesc.Flags = 0;
|
swapChainDesc.Flags = 0;
|
||||||
|
|
||||||
// 3 front buffers (default GetMaximumFrameLatency() count)
|
// 1 front buffer + 1 back buffer + 1 extra for DWM to hold on to for Flip modes
|
||||||
// + 1 back buffer
|
swapChainDesc.BufferCount = 3;
|
||||||
// + 1 extra for DWM to hold on to for DirectFlip
|
|
||||||
//
|
|
||||||
// Even though we allocate 3 front buffers for pre-rendered frames,
|
|
||||||
// they won't actually increase presentation latency because we
|
|
||||||
// always use SyncInterval 0 which replaces the last one.
|
|
||||||
//
|
|
||||||
// IDXGIDevice1 has a SetMaximumFrameLatency() function, but counter-
|
|
||||||
// intuitively we must avoid it to reduce latency. If we set our max
|
|
||||||
// frame latency to 1 on thedevice, our SyncInterval 0 Present() calls
|
|
||||||
// will block on DWM (acting like SyncInterval 1) rather than doing
|
|
||||||
// the non-blocking present we expect.
|
|
||||||
//
|
|
||||||
// NB: 3 total buffers seems sufficient on NVIDIA hardware but
|
|
||||||
// causes performance issues (buffer starvation) on AMD GPUs.
|
|
||||||
swapChainDesc.BufferCount = 3 + 1 + 1;
|
|
||||||
|
|
||||||
// Use the current window size as the swapchain size
|
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fullScreenDesc = {};
|
||||||
SDL_GetWindowSize(params->window, (int*)&swapChainDesc.Width, (int*)&swapChainDesc.Height);
|
|
||||||
|
if (m_Windowed) {
|
||||||
|
// Use the current window size as the swapchain size
|
||||||
|
SDL_GetWindowSize(params->window, (int*)&swapChainDesc.Width, (int*)&swapChainDesc.Height);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Use the current display mode as the swapchain size
|
||||||
|
SDL_DisplayMode sdlMode;
|
||||||
|
if (SDL_GetWindowDisplayMode(params->window, &sdlMode) < 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL_GetWindowDisplayMode() failed: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
swapChainDesc.Width = sdlMode.w;
|
||||||
|
swapChainDesc.Height = sdlMode.h;
|
||||||
|
|
||||||
|
// Leave these unspecified to ensure we don't end up in an inefficient "proxy"
|
||||||
|
// full-screen swapchain with DXGI doing format conversion behind our backs.
|
||||||
|
// https://youtu.be/E3wTajGZOsA?t=1489
|
||||||
|
fullScreenDesc.RefreshRate.Numerator = 0;
|
||||||
|
fullScreenDesc.RefreshRate.Denominator = 0;
|
||||||
|
fullScreenDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
|
||||||
|
fullScreenDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
|
||||||
|
|
||||||
|
fullScreenDesc.Windowed = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
m_DisplayWidth = swapChainDesc.Width;
|
m_DisplayWidth = swapChainDesc.Width;
|
||||||
m_DisplayHeight = swapChainDesc.Height;
|
m_DisplayHeight = swapChainDesc.Height;
|
||||||
|
|
||||||
if (params->videoFormat & VIDEO_FORMAT_MASK_10BIT) {
|
if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||||
swapChainDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
|
swapChainDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING with flip mode for non-vsync case, if possible.
|
// Use DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING with flip mode for non-vsync case, if possible
|
||||||
// NOTE: This is only possible in windowed or borderless windowed mode.
|
|
||||||
if (!params->enableVsync) {
|
if (!params->enableVsync) {
|
||||||
BOOL allowTearing = FALSE;
|
// DXGI_PRESENT_ALLOW_TEARING may only be used in windowed mode
|
||||||
hr = m_Factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING,
|
if (m_Windowed) {
|
||||||
&allowTearing,
|
BOOL allowTearing = FALSE;
|
||||||
sizeof(allowTearing));
|
hr = m_Factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING,
|
||||||
if (SUCCEEDED(hr)) {
|
&allowTearing,
|
||||||
if (allowTearing) {
|
sizeof(allowTearing));
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
// Use flip discard with allow tearing mode if possible.
|
// Use flip discard with allow tearing mode if possible.
|
||||||
swapChainDesc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
swapChainDesc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
||||||
|
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
||||||
m_AllowTearing = true;
|
m_AllowTearing = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"OS/GPU doesn't support DXGI_FEATURE_PRESENT_ALLOW_TEARING");
|
"GPU driver doesn't support DXGI_FEATURE_PRESENT_ALLOW_TEARING");
|
||||||
|
|
||||||
|
// Without tearing support, we'll have to use regular discard mode to get tearing
|
||||||
|
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
// In full-screen exclusive mode, we'll have to use regular discard mode
|
||||||
"IDXGIFactory::CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING) failed: %x",
|
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
|
||||||
hr);
|
}
|
||||||
// Non-fatal
|
}
|
||||||
|
else {
|
||||||
|
// In V-sync mode, we can always use flip discard
|
||||||
|
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
||||||
|
|
||||||
|
// We'll use a waitable swapchain to ensure we get the lowest possible latency.
|
||||||
|
// NB: We can only use this option in windowed mode (or borderless fullscreen).
|
||||||
|
if (m_Windowed) {
|
||||||
|
// FIXME?: Using a a waitable swapchain in Independent Flip mode causes performance
|
||||||
|
// issues because the swapchain wait sometimes takes longer than the VBlank interval.
|
||||||
|
if ((SDL_GetWindowFlags(params->window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP) {
|
||||||
|
swapChainDesc.Flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
|
||||||
|
}
|
||||||
|
else if (qgetenv("D3D11VA_FORCE_WAITABLE_SWAPCHAIN") == "1") {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Using waitable swapchain in full-screen per environment variable override");
|
||||||
|
swapChainDesc.Flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,13 +336,11 @@ bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
SDL_GetWindowWMInfo(params->window, &info);
|
SDL_GetWindowWMInfo(params->window, &info);
|
||||||
SDL_assert(info.subsystem == SDL_SYSWM_WINDOWS);
|
SDL_assert(info.subsystem == SDL_SYSWM_WINDOWS);
|
||||||
|
|
||||||
// Always use windowed or borderless windowed mode.. SDL does mode-setting for us in
|
|
||||||
// full-screen exclusive mode (SDL_WINDOW_FULLSCREEN), so this actually works out okay.
|
|
||||||
IDXGISwapChain1* swapChain;
|
IDXGISwapChain1* swapChain;
|
||||||
hr = m_Factory->CreateSwapChainForHwnd(m_Device,
|
hr = m_Factory->CreateSwapChainForHwnd(m_Device,
|
||||||
info.info.win.window,
|
info.info.win.window,
|
||||||
&swapChainDesc,
|
&swapChainDesc,
|
||||||
nullptr,
|
m_Windowed ? nullptr : &fullScreenDesc,
|
||||||
nullptr,
|
nullptr,
|
||||||
&swapChain);
|
&swapChain);
|
||||||
|
|
||||||
@@ -454,18 +420,19 @@ bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
|
|
||||||
// We require NV12 or P010 textures for our shader
|
// We require NV12 or P010 textures for our shader
|
||||||
framesContext->format = AV_PIX_FMT_D3D11;
|
framesContext->format = AV_PIX_FMT_D3D11;
|
||||||
framesContext->sw_format = (params->videoFormat & VIDEO_FORMAT_MASK_10BIT) ?
|
framesContext->sw_format = params->videoFormat == VIDEO_FORMAT_H265_MAIN10 ?
|
||||||
AV_PIX_FMT_P010 : AV_PIX_FMT_NV12;
|
AV_PIX_FMT_P010 : AV_PIX_FMT_NV12;
|
||||||
|
|
||||||
framesContext->width = FFALIGN(params->width, m_TextureAlignment);
|
framesContext->width = FFALIGN(params->width, m_TextureAlignment);
|
||||||
framesContext->height = FFALIGN(params->height, m_TextureAlignment);
|
framesContext->height = FFALIGN(params->height, m_TextureAlignment);
|
||||||
|
|
||||||
// We can have up to 16 reference frames plus a working surface
|
// We can have up to 16 reference frames plus a working surface
|
||||||
framesContext->initial_pool_size = 17;
|
framesContext->initial_pool_size = DECODER_BUFFER_POOL_SIZE;
|
||||||
|
|
||||||
AVD3D11VAFramesContext* d3d11vaFramesContext = (AVD3D11VAFramesContext*)framesContext->hwctx;
|
AVD3D11VAFramesContext* d3d11vaFramesContext = (AVD3D11VAFramesContext*)framesContext->hwctx;
|
||||||
|
|
||||||
d3d11vaFramesContext->BindFlags = D3D11_BIND_DECODER;
|
// We need to override the default D3D11VA bind flags to bind the textures as a shader resources
|
||||||
|
d3d11vaFramesContext->BindFlags = D3D11_BIND_DECODER | D3D11_BIND_SHADER_RESOURCE;
|
||||||
|
|
||||||
int err = av_hwframe_ctx_init(m_HwFramesContext);
|
int err = av_hwframe_ctx_init(m_HwFramesContext);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
@@ -475,8 +442,50 @@ bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create our video texture and SRVs
|
// Create SRVs for all textures in the decoder pool
|
||||||
if (!setupVideoTexture()) {
|
if (!setupTexturePoolViews(d3d11vaFramesContext)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to set the maximum frame latency on waitable swapchains.
|
||||||
|
if (swapChainDesc.Flags & DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT) {
|
||||||
|
SDL_assert(params->enableVsync);
|
||||||
|
SDL_assert(m_Windowed);
|
||||||
|
|
||||||
|
// We only want one buffered frame on our waitable swapchain
|
||||||
|
hr = m_SwapChain->SetMaximumFrameLatency(1);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"IDXGISwapChain::SetMaximumFrameLatency() failed: %x",
|
||||||
|
hr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_FrameWaitableObject = m_SwapChain->GetFrameLatencyWaitableObject();
|
||||||
|
SDL_assert(m_FrameWaitableObject != nullptr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
IDXGIDevice1* dxgiDevice;
|
||||||
|
|
||||||
|
hr = m_Device->QueryInterface(__uuidof(IDXGIDevice1), (void **)&dxgiDevice);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"ID3D11Device::QueryInterface(IDXGIDevice1) failed: %x",
|
||||||
|
hr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the non-vsync case, we won't have a waitable swapchain,
|
||||||
|
// so we must use IDXGIDevice1::SetMaximumFrameLatency() instead.
|
||||||
|
hr = dxgiDevice->SetMaximumFrameLatency(1);
|
||||||
|
|
||||||
|
dxgiDevice->Release();
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"IDXGIDevice1::SetMaximumFrameLatency() failed: %x",
|
||||||
|
hr);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -569,6 +578,22 @@ void D3D11VARenderer::setHdrMode(bool enabled)
|
|||||||
unlockContext(this);
|
unlockContext(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void D3D11VARenderer::waitToRender()
|
||||||
|
{
|
||||||
|
if (m_FrameWaitableObject != nullptr) {
|
||||||
|
SDL_assert(m_Windowed);
|
||||||
|
SDL_assert(m_DecoderParams.enableVsync);
|
||||||
|
|
||||||
|
// Wait for the pipeline to be ready for the next frame in V-Sync mode.
|
||||||
|
//
|
||||||
|
// This callback happens before selecting the next frame to render, so
|
||||||
|
// we can wait for the previous frame to finish prior to picking the
|
||||||
|
// next one to display. This reduces the effective display latency
|
||||||
|
// by ensuring we always render the most recent frame immediately.
|
||||||
|
WaitForSingleObjectEx(m_FrameWaitableObject, 500, FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void D3D11VARenderer::renderFrame(AVFrame* frame)
|
void D3D11VARenderer::renderFrame(AVFrame* frame)
|
||||||
{
|
{
|
||||||
// Acquire the context lock for rendering to prevent concurrent
|
// Acquire the context lock for rendering to prevent concurrent
|
||||||
@@ -592,23 +617,55 @@ void D3D11VARenderer::renderFrame(AVFrame* frame)
|
|||||||
}
|
}
|
||||||
|
|
||||||
UINT flags;
|
UINT flags;
|
||||||
|
UINT syncInterval;
|
||||||
|
|
||||||
if (m_AllowTearing) {
|
if (m_AllowTearing) {
|
||||||
SDL_assert(!m_DecoderParams.enableVsync);
|
SDL_assert(!m_DecoderParams.enableVsync);
|
||||||
|
SDL_assert(m_Windowed);
|
||||||
|
|
||||||
// If tearing is allowed, use DXGI_PRESENT_ALLOW_TEARING with syncInterval 0.
|
// If tearing is allowed, use DXGI_PRESENT_ALLOW_TEARING with syncInterval 0.
|
||||||
// It is not valid to use any other syncInterval values in tearing mode.
|
// It is not valid to use any other syncInterval values in tearing mode.
|
||||||
|
syncInterval = 0;
|
||||||
flags = DXGI_PRESENT_ALLOW_TEARING;
|
flags = DXGI_PRESENT_ALLOW_TEARING;
|
||||||
}
|
}
|
||||||
else {
|
else if (!m_DecoderParams.enableVsync) {
|
||||||
// Otherwise, we'll submit as fast as possible and DWM will discard excess
|
// In any other non-vsync mode, just render with syncInterval 0.
|
||||||
// frames for us. If frame pacing is also enabled or we're in full-screen,
|
// We'll probably have a non-flip swapchain here.
|
||||||
// our Vsync source will keep us in sync with VBlank.
|
syncInterval = 0;
|
||||||
flags = 0;
|
flags = 0;
|
||||||
}
|
}
|
||||||
|
else if (m_FrameWaitableObject != nullptr) {
|
||||||
|
SDL_assert(m_Windowed);
|
||||||
|
SDL_assert(m_DecoderParams.enableVsync);
|
||||||
|
|
||||||
// Present according to the decoder parameters
|
// In windowed mode, we'll have a waitable swapchain, so we can
|
||||||
HRESULT hr = m_SwapChain->Present(0, flags);
|
// use syncInterval 0 and the wait will sync us with VBlank.
|
||||||
|
syncInterval = 0;
|
||||||
|
flags = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_assert(m_DecoderParams.enableVsync);
|
||||||
|
SDL_assert(m_FrameWaitableObject == nullptr);
|
||||||
|
|
||||||
|
// In full-screen mode, we won't have waitable swapchain.
|
||||||
|
// We'll use syncInterval 1 to synchronize with VBlank and pass
|
||||||
|
// DXGI_PRESENT_DO_NOT_WAIT for our flags to avoid blocking any
|
||||||
|
// concurrent decoding operations in flight.
|
||||||
|
syncInterval = 1;
|
||||||
|
flags = DXGI_PRESENT_DO_NOT_WAIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
HRESULT hr;
|
||||||
|
do {
|
||||||
|
// Present according to the decoder parameters
|
||||||
|
hr = m_SwapChain->Present(syncInterval, flags);
|
||||||
|
if (hr == DXGI_ERROR_WAS_STILL_DRAWING) {
|
||||||
|
// Unlock the context while we wait to try again
|
||||||
|
unlockContext(this);
|
||||||
|
SDL_Delay(1);
|
||||||
|
lockContext(this);
|
||||||
|
}
|
||||||
|
} while (hr == DXGI_ERROR_WAS_STILL_DRAWING);
|
||||||
|
|
||||||
// Release the context lock
|
// Release the context lock
|
||||||
unlockContext(this);
|
unlockContext(this);
|
||||||
@@ -674,14 +731,11 @@ void D3D11VARenderer::renderOverlay(Overlay::OverlayType type)
|
|||||||
|
|
||||||
void D3D11VARenderer::bindColorConversion(AVFrame* frame)
|
void D3D11VARenderer::bindColorConversion(AVFrame* frame)
|
||||||
{
|
{
|
||||||
bool fullRange = isFrameFullRange(frame);
|
|
||||||
int colorspace = getFrameColorspace(frame);
|
|
||||||
|
|
||||||
// We have purpose-built shaders for the common Rec 601 (SDR) and Rec 2020 (HDR) cases
|
// We have purpose-built shaders for the common Rec 601 (SDR) and Rec 2020 (HDR) cases
|
||||||
if (!fullRange && colorspace == COLORSPACE_REC_601) {
|
if (frame->color_range == AVCOL_RANGE_MPEG && frame->colorspace == AVCOL_SPC_SMPTE170M) {
|
||||||
m_DeviceContext->PSSetShader(m_VideoBt601LimPixelShader, nullptr, 0);
|
m_DeviceContext->PSSetShader(m_VideoBt601LimPixelShader, nullptr, 0);
|
||||||
}
|
}
|
||||||
else if (!fullRange && colorspace == COLORSPACE_REC_2020) {
|
else if (frame->color_range == AVCOL_RANGE_MPEG && frame->colorspace == AVCOL_SPC_BT2020_NCL) {
|
||||||
m_DeviceContext->PSSetShader(m_VideoBt2020LimPixelShader, nullptr, 0);
|
m_DeviceContext->PSSetShader(m_VideoBt2020LimPixelShader, nullptr, 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -689,14 +743,14 @@ void D3D11VARenderer::bindColorConversion(AVFrame* frame)
|
|||||||
m_DeviceContext->PSSetShader(m_VideoGenericPixelShader, nullptr, 0);
|
m_DeviceContext->PSSetShader(m_VideoGenericPixelShader, nullptr, 0);
|
||||||
|
|
||||||
// If nothing has changed since last frame, we're done
|
// If nothing has changed since last frame, we're done
|
||||||
if (colorspace == m_LastColorSpace && fullRange == m_LastFullRange) {
|
if (frame->colorspace == m_LastColorSpace && frame->color_range == m_LastColorRange) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Falling back to generic video pixel shader for %d (%s range)",
|
"Falling back to generic video pixel shader for %d:%d",
|
||||||
colorspace,
|
frame->colorspace,
|
||||||
fullRange ? "full" : "limited");
|
frame->color_range);
|
||||||
|
|
||||||
D3D11_BUFFER_DESC constDesc = {};
|
D3D11_BUFFER_DESC constDesc = {};
|
||||||
constDesc.ByteWidth = sizeof(CSC_CONST_BUF);
|
constDesc.ByteWidth = sizeof(CSC_CONST_BUF);
|
||||||
@@ -705,16 +759,22 @@ void D3D11VARenderer::bindColorConversion(AVFrame* frame)
|
|||||||
constDesc.CPUAccessFlags = 0;
|
constDesc.CPUAccessFlags = 0;
|
||||||
constDesc.MiscFlags = 0;
|
constDesc.MiscFlags = 0;
|
||||||
|
|
||||||
|
// This handles the case where the color range is unknown,
|
||||||
|
// so that we use Limited color range which is the default
|
||||||
|
// behavior for Moonlight.
|
||||||
CSC_CONST_BUF constBuf = {};
|
CSC_CONST_BUF constBuf = {};
|
||||||
|
bool fullRange = (frame->color_range == AVCOL_RANGE_JPEG);
|
||||||
const float* rawCscMatrix;
|
const float* rawCscMatrix;
|
||||||
switch (colorspace) {
|
switch (frame->colorspace) {
|
||||||
case COLORSPACE_REC_601:
|
case AVCOL_SPC_SMPTE170M:
|
||||||
|
case AVCOL_SPC_BT470BG:
|
||||||
rawCscMatrix = fullRange ? k_CscMatrix_Bt601Full : k_CscMatrix_Bt601Lim;
|
rawCscMatrix = fullRange ? k_CscMatrix_Bt601Full : k_CscMatrix_Bt601Lim;
|
||||||
break;
|
break;
|
||||||
case COLORSPACE_REC_709:
|
case AVCOL_SPC_BT709:
|
||||||
rawCscMatrix = fullRange ? k_CscMatrix_Bt709Full : k_CscMatrix_Bt709Lim;
|
rawCscMatrix = fullRange ? k_CscMatrix_Bt709Full : k_CscMatrix_Bt709Lim;
|
||||||
break;
|
break;
|
||||||
case COLORSPACE_REC_2020:
|
case AVCOL_SPC_BT2020_NCL:
|
||||||
|
case AVCOL_SPC_BT2020_CL:
|
||||||
rawCscMatrix = fullRange ? k_CscMatrix_Bt2020Full : k_CscMatrix_Bt2020Lim;
|
rawCscMatrix = fullRange ? k_CscMatrix_Bt2020Full : k_CscMatrix_Bt2020Lim;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -753,8 +813,8 @@ void D3D11VARenderer::bindColorConversion(AVFrame* frame)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_LastColorSpace = colorspace;
|
m_LastColorSpace = frame->colorspace;
|
||||||
m_LastFullRange = fullRange;
|
m_LastColorRange = frame->color_range;
|
||||||
}
|
}
|
||||||
|
|
||||||
void D3D11VARenderer::renderVideo(AVFrame* frame)
|
void D3D11VARenderer::renderVideo(AVFrame* frame)
|
||||||
@@ -764,21 +824,22 @@ void D3D11VARenderer::renderVideo(AVFrame* frame)
|
|||||||
UINT offset = 0;
|
UINT offset = 0;
|
||||||
m_DeviceContext->IASetVertexBuffers(0, 1, &m_VideoVertexBuffer, &stride, &offset);
|
m_DeviceContext->IASetVertexBuffers(0, 1, &m_VideoVertexBuffer, &stride, &offset);
|
||||||
|
|
||||||
// Copy this frame (minus alignment padding) into our video texture
|
// Our indexing logic depends on a direct mapping into m_VideoTextureResourceViews
|
||||||
D3D11_BOX srcBox;
|
// based on the texture index provided by FFmpeg.
|
||||||
srcBox.left = 0;
|
UINT textureIndex = (uintptr_t)frame->data[1];
|
||||||
srcBox.top = 0;
|
SDL_assert(textureIndex < DECODER_BUFFER_POOL_SIZE);
|
||||||
srcBox.right = m_DecoderParams.width;
|
if (textureIndex >= DECODER_BUFFER_POOL_SIZE) {
|
||||||
srcBox.bottom = m_DecoderParams.height;
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
srcBox.front = 0;
|
"Unexpected texture index: %u",
|
||||||
srcBox.back = 1;
|
textureIndex);
|
||||||
m_DeviceContext->CopySubresourceRegion(m_VideoTexture, 0, 0, 0, 0, (ID3D11Resource*)frame->data[0], (int)(intptr_t)frame->data[1], &srcBox);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Bind our CSC shader (and constant buffer, if required)
|
// Bind our CSC shader (and constant buffer, if required)
|
||||||
bindColorConversion(frame);
|
bindColorConversion(frame);
|
||||||
|
|
||||||
// Bind SRVs for this frame
|
// Bind SRVs for this frame
|
||||||
m_DeviceContext->PSSetShaderResources(0, 2, m_VideoTextureResourceViews);
|
m_DeviceContext->PSSetShaderResources(0, 2, m_VideoTextureResourceViews[textureIndex]);
|
||||||
|
|
||||||
// Draw the video
|
// Draw the video
|
||||||
m_DeviceContext->DrawIndexed(6, 0, 0);
|
m_DeviceContext->DrawIndexed(6, 0, 0);
|
||||||
@@ -819,7 +880,6 @@ void D3D11VARenderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
|||||||
|
|
||||||
// Create a texture with our pixel data
|
// Create a texture with our pixel data
|
||||||
SDL_assert(!SDL_MUSTLOCK(newSurface));
|
SDL_assert(!SDL_MUSTLOCK(newSurface));
|
||||||
SDL_assert(newSurface->format->format == SDL_PIXELFORMAT_ARGB8888);
|
|
||||||
|
|
||||||
D3D11_TEXTURE2D_DESC texDesc = {};
|
D3D11_TEXTURE2D_DESC texDesc = {};
|
||||||
texDesc.Width = newSurface->w;
|
texDesc.Width = newSurface->w;
|
||||||
@@ -999,6 +1059,12 @@ bool D3D11VARenderer::checkDecoderSupport(IDXGIAdapter* adapter)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Detected GPU: %S (%x:%x)",
|
||||||
|
adapterDesc.Description,
|
||||||
|
adapterDesc.VendorId,
|
||||||
|
adapterDesc.DeviceId);
|
||||||
|
|
||||||
if (DXUtil::isFormatHybridDecodedByHardware(m_DecoderParams.videoFormat, adapterDesc.VendorId, adapterDesc.DeviceId)) {
|
if (DXUtil::isFormatHybridDecodedByHardware(m_DecoderParams.videoFormat, adapterDesc.VendorId, adapterDesc.DeviceId)) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"GPU decoding for format %x is blocked due to hardware limitations",
|
"GPU decoding for format %x is blocked due to hardware limitations",
|
||||||
@@ -1011,34 +1077,8 @@ bool D3D11VARenderer::checkDecoderSupport(IDXGIAdapter* adapter)
|
|||||||
|
|
||||||
int D3D11VARenderer::getRendererAttributes()
|
int D3D11VARenderer::getRendererAttributes()
|
||||||
{
|
{
|
||||||
int attributes = 0;
|
|
||||||
|
|
||||||
// This renderer supports HDR
|
// This renderer supports HDR
|
||||||
attributes |= RENDERER_ATTRIBUTE_HDR_SUPPORT;
|
return RENDERER_ATTRIBUTE_HDR_SUPPORT;
|
||||||
|
|
||||||
// This renderer requires frame pacing to synchronize with VBlank when we're in full-screen.
|
|
||||||
// In windowed mode, we will render as fast we can and DWM will grab whatever is latest at the
|
|
||||||
// time unless the user opts for pacing. We will use pacing in full-screen mode and normal DWM
|
|
||||||
// sequencing in full-screen desktop mode to behave similarly to the DXVA2 renderer.
|
|
||||||
if ((SDL_GetWindowFlags(m_DecoderParams.window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
|
||||||
attributes |= RENDERER_ATTRIBUTE_FORCE_PACING;
|
|
||||||
}
|
|
||||||
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
int D3D11VARenderer::getDecoderCapabilities()
|
|
||||||
{
|
|
||||||
return CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool D3D11VARenderer::needsTestFrame()
|
|
||||||
{
|
|
||||||
// We can usually determine when D3D11VA will work based on which decoder GUIDs are supported,
|
|
||||||
// however there are some strange cases (Quadro P400 + Radeon HD 5570) where something goes
|
|
||||||
// horribly wrong and D3D11VideoDevice::CreateVideoDecoder() fails inside FFmpeg. We need to
|
|
||||||
// catch that case before we commit to using D3D11VA.
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void D3D11VARenderer::lockContext(void *lock_ctx)
|
void D3D11VARenderer::lockContext(void *lock_ctx)
|
||||||
@@ -1238,12 +1278,17 @@ bool D3D11VARenderer::setupRenderingResources()
|
|||||||
SDL_FRect renderRect;
|
SDL_FRect renderRect;
|
||||||
StreamUtils::screenSpaceToNormalizedDeviceCoords(&dst, &renderRect, m_DisplayWidth, m_DisplayHeight);
|
StreamUtils::screenSpaceToNormalizedDeviceCoords(&dst, &renderRect, m_DisplayWidth, m_DisplayHeight);
|
||||||
|
|
||||||
|
// Don't sample from the alignment padding area since that's not part of the video
|
||||||
|
SDL_assert(m_TextureAlignment != 0);
|
||||||
|
float uMax = (float)m_DecoderParams.width / FFALIGN(m_DecoderParams.width, m_TextureAlignment);
|
||||||
|
float vMax = (float)m_DecoderParams.height / FFALIGN(m_DecoderParams.height, m_TextureAlignment);
|
||||||
|
|
||||||
VERTEX verts[] =
|
VERTEX verts[] =
|
||||||
{
|
{
|
||||||
{renderRect.x, renderRect.y, 0, 1.0f},
|
{renderRect.x, renderRect.y, 0, vMax},
|
||||||
{renderRect.x, renderRect.y+renderRect.h, 0, 0},
|
{renderRect.x, renderRect.y+renderRect.h, 0, 0},
|
||||||
{renderRect.x+renderRect.w, renderRect.y, 1.0f, 1.0f},
|
{renderRect.x+renderRect.w, renderRect.y, uMax, vMax},
|
||||||
{renderRect.x+renderRect.w, renderRect.y+renderRect.h, 1.0f, 0},
|
{renderRect.x+renderRect.w, renderRect.y+renderRect.h, uMax, 0},
|
||||||
};
|
};
|
||||||
|
|
||||||
D3D11_BUFFER_DESC vbDesc = {};
|
D3D11_BUFFER_DESC vbDesc = {};
|
||||||
@@ -1311,55 +1356,42 @@ bool D3D11VARenderer::setupRenderingResources()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool D3D11VARenderer::setupVideoTexture()
|
bool D3D11VARenderer::setupTexturePoolViews(AVD3D11VAFramesContext* frameContext)
|
||||||
{
|
{
|
||||||
HRESULT hr;
|
|
||||||
D3D11_TEXTURE2D_DESC texDesc = {};
|
|
||||||
|
|
||||||
texDesc.Width = m_DecoderParams.width;
|
|
||||||
texDesc.Height = m_DecoderParams.height;
|
|
||||||
texDesc.MipLevels = 1;
|
|
||||||
texDesc.ArraySize = 1;
|
|
||||||
texDesc.Format = (m_DecoderParams.videoFormat & VIDEO_FORMAT_MASK_10BIT) ? DXGI_FORMAT_P010 : DXGI_FORMAT_NV12;
|
|
||||||
texDesc.SampleDesc.Quality = 0;
|
|
||||||
texDesc.SampleDesc.Count = 1;
|
|
||||||
texDesc.Usage = D3D11_USAGE_DEFAULT;
|
|
||||||
texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
|
||||||
texDesc.CPUAccessFlags = 0;
|
|
||||||
texDesc.MiscFlags = 0;
|
|
||||||
|
|
||||||
hr = m_Device->CreateTexture2D(&texDesc, nullptr, &m_VideoTexture);
|
|
||||||
if (FAILED(hr)) {
|
|
||||||
m_VideoTexture = nullptr;
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"ID3D11Device::CreateTexture2D() failed: %x",
|
|
||||||
hr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create luminance and chrominance SRVs for each plane of the texture
|
|
||||||
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
|
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
|
||||||
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
|
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
|
||||||
srvDesc.Texture2D.MostDetailedMip = 0;
|
srvDesc.Texture2DArray.MostDetailedMip = 0;
|
||||||
srvDesc.Texture2D.MipLevels = 1;
|
srvDesc.Texture2DArray.MipLevels = 1;
|
||||||
srvDesc.Format = (m_DecoderParams.videoFormat & VIDEO_FORMAT_MASK_10BIT) ? DXGI_FORMAT_R16_UNORM : DXGI_FORMAT_R8_UNORM;
|
srvDesc.Texture2DArray.ArraySize = 1;
|
||||||
hr = m_Device->CreateShaderResourceView(m_VideoTexture, &srvDesc, &m_VideoTextureResourceViews[0]);
|
|
||||||
if (FAILED(hr)) {
|
|
||||||
m_VideoTextureResourceViews[0] = nullptr;
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"ID3D11Device::CreateShaderResourceView() failed: %x",
|
|
||||||
hr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
srvDesc.Format = (m_DecoderParams.videoFormat & VIDEO_FORMAT_MASK_10BIT) ? DXGI_FORMAT_R16G16_UNORM : DXGI_FORMAT_R8G8_UNORM;
|
// Create luminance and chrominance SRVs for each texture in the pool
|
||||||
hr = m_Device->CreateShaderResourceView(m_VideoTexture, &srvDesc, &m_VideoTextureResourceViews[1]);
|
for (int i = 0; i < DECODER_BUFFER_POOL_SIZE; i++) {
|
||||||
if (FAILED(hr)) {
|
HRESULT hr;
|
||||||
m_VideoTextureResourceViews[1] = nullptr;
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
// Our rendering logic depends on the texture index working to map into our SRV array
|
||||||
"ID3D11Device::CreateShaderResourceView() failed: %x",
|
SDL_assert(i == frameContext->texture_infos[i].index);
|
||||||
hr);
|
|
||||||
return false;
|
srvDesc.Texture2DArray.FirstArraySlice = frameContext->texture_infos[i].index;
|
||||||
|
|
||||||
|
srvDesc.Format = m_DecoderParams.videoFormat == VIDEO_FORMAT_H265_MAIN10 ? DXGI_FORMAT_R16_UNORM : DXGI_FORMAT_R8_UNORM;
|
||||||
|
hr = m_Device->CreateShaderResourceView(frameContext->texture_infos[i].texture, &srvDesc, &m_VideoTextureResourceViews[i][0]);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
m_VideoTextureResourceViews[i][0] = nullptr;
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"ID3D11Device::CreateShaderResourceView() failed: %x",
|
||||||
|
hr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
srvDesc.Format = m_DecoderParams.videoFormat == VIDEO_FORMAT_H265_MAIN10 ? DXGI_FORMAT_R16G16_UNORM : DXGI_FORMAT_R8G8_UNORM;
|
||||||
|
hr = m_Device->CreateShaderResourceView(frameContext->texture_infos[i].texture, &srvDesc, &m_VideoTextureResourceViews[i][1]);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
m_VideoTextureResourceViews[i][1] = nullptr;
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"ID3D11Device::CreateShaderResourceView() failed: %x",
|
||||||
|
hr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -13,31 +13,27 @@ extern "C" {
|
|||||||
class D3D11VARenderer : public IFFmpegRenderer
|
class D3D11VARenderer : public IFFmpegRenderer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
D3D11VARenderer(int decoderSelectionPass);
|
D3D11VARenderer();
|
||||||
virtual ~D3D11VARenderer() override;
|
virtual ~D3D11VARenderer() override;
|
||||||
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||||
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary**) override;
|
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary**) override;
|
||||||
virtual bool prepareDecoderContextInGetFormat(AVCodecContext* context, AVPixelFormat pixelFormat) override;
|
virtual bool prepareDecoderContextInGetFormat(AVCodecContext* context, AVPixelFormat pixelFormat) override;
|
||||||
virtual void renderFrame(AVFrame* frame) override;
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
|
virtual void waitToRender() override;
|
||||||
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
||||||
virtual void setHdrMode(bool enabled) override;
|
virtual void setHdrMode(bool enabled) override;
|
||||||
virtual int getRendererAttributes() override;
|
virtual int getRendererAttributes() override;
|
||||||
virtual int getDecoderCapabilities() override;
|
|
||||||
virtual bool needsTestFrame() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void lockContext(void* lock_ctx);
|
static void lockContext(void* lock_ctx);
|
||||||
static void unlockContext(void* lock_ctx);
|
static void unlockContext(void* lock_ctx);
|
||||||
|
|
||||||
bool setupRenderingResources();
|
bool setupRenderingResources();
|
||||||
bool setupVideoTexture();
|
bool setupTexturePoolViews(AVD3D11VAFramesContext* frameContext);
|
||||||
void renderOverlay(Overlay::OverlayType type);
|
void renderOverlay(Overlay::OverlayType type);
|
||||||
void bindColorConversion(AVFrame* frame);
|
void bindColorConversion(AVFrame* frame);
|
||||||
void renderVideo(AVFrame* frame);
|
void renderVideo(AVFrame* frame);
|
||||||
bool checkDecoderSupport(IDXGIAdapter* adapter);
|
bool checkDecoderSupport(IDXGIAdapter* adapter);
|
||||||
bool createDeviceByAdapterIndex(int adapterIndex, bool* adapterNotFound = nullptr);
|
|
||||||
|
|
||||||
int m_DecoderSelectionPass;
|
|
||||||
|
|
||||||
IDXGIFactory5* m_Factory;
|
IDXGIFactory5* m_Factory;
|
||||||
ID3D11Device* m_Device;
|
ID3D11Device* m_Device;
|
||||||
@@ -50,18 +46,20 @@ private:
|
|||||||
int m_TextureAlignment;
|
int m_TextureAlignment;
|
||||||
int m_DisplayWidth;
|
int m_DisplayWidth;
|
||||||
int m_DisplayHeight;
|
int m_DisplayHeight;
|
||||||
int m_LastColorSpace;
|
bool m_Windowed;
|
||||||
bool m_LastFullRange;
|
AVColorSpace m_LastColorSpace;
|
||||||
|
AVColorRange m_LastColorRange;
|
||||||
|
|
||||||
bool m_AllowTearing;
|
bool m_AllowTearing;
|
||||||
|
HANDLE m_FrameWaitableObject;
|
||||||
|
|
||||||
ID3D11PixelShader* m_VideoGenericPixelShader;
|
ID3D11PixelShader* m_VideoGenericPixelShader;
|
||||||
ID3D11PixelShader* m_VideoBt601LimPixelShader;
|
ID3D11PixelShader* m_VideoBt601LimPixelShader;
|
||||||
ID3D11PixelShader* m_VideoBt2020LimPixelShader;
|
ID3D11PixelShader* m_VideoBt2020LimPixelShader;
|
||||||
ID3D11Buffer* m_VideoVertexBuffer;
|
ID3D11Buffer* m_VideoVertexBuffer;
|
||||||
|
|
||||||
ID3D11Texture2D* m_VideoTexture;
|
#define DECODER_BUFFER_POOL_SIZE 17
|
||||||
ID3D11ShaderResourceView* m_VideoTextureResourceViews[2];
|
ID3D11ShaderResourceView* m_VideoTextureResourceViews[DECODER_BUFFER_POOL_SIZE][2];
|
||||||
|
|
||||||
SDL_SpinLock m_OverlayLock;
|
SDL_SpinLock m_OverlayLock;
|
||||||
ID3D11Buffer* m_OverlayVertexBuffers[Overlay::OverlayMax];
|
ID3D11Buffer* m_OverlayVertexBuffers[Overlay::OverlayMax];
|
||||||
|
|||||||
@@ -29,15 +29,8 @@ extern "C" {
|
|||||||
|
|
||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
|
|
||||||
// HACK: Avoid including X11 headers which conflict with QDir
|
|
||||||
#ifdef SDL_VIDEO_DRIVER_X11
|
|
||||||
#undef SDL_VIDEO_DRIVER_X11
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <SDL_syswm.h>
|
#include <SDL_syswm.h>
|
||||||
|
|
||||||
#include <QDir>
|
|
||||||
|
|
||||||
DrmRenderer::DrmRenderer(IFFmpegRenderer *backendRenderer)
|
DrmRenderer::DrmRenderer(IFFmpegRenderer *backendRenderer)
|
||||||
: m_BackendRenderer(backendRenderer),
|
: m_BackendRenderer(backendRenderer),
|
||||||
m_HwContext(nullptr),
|
m_HwContext(nullptr),
|
||||||
@@ -50,8 +43,8 @@ DrmRenderer::DrmRenderer(IFFmpegRenderer *backendRenderer)
|
|||||||
m_CrtcId(0),
|
m_CrtcId(0),
|
||||||
m_PlaneId(0),
|
m_PlaneId(0),
|
||||||
m_CurrentFbId(0),
|
m_CurrentFbId(0),
|
||||||
m_LastFullRange(false),
|
m_LastColorRange(AVCOL_RANGE_UNSPECIFIED),
|
||||||
m_LastColorSpace(-1),
|
m_LastColorSpace(AVCOL_SPC_UNSPECIFIED),
|
||||||
m_ColorEncodingProp(nullptr),
|
m_ColorEncodingProp(nullptr),
|
||||||
m_ColorRangeProp(nullptr),
|
m_ColorRangeProp(nullptr),
|
||||||
m_HdrOutputMetadataProp(nullptr),
|
m_HdrOutputMetadataProp(nullptr),
|
||||||
@@ -118,7 +111,7 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
m_Main10Hdr = (params->videoFormat & VIDEO_FORMAT_MASK_10BIT);
|
m_Main10Hdr = (params->videoFormat == VIDEO_FORMAT_H265_MAIN10);
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 15)
|
#if SDL_VERSION_ATLEAST(2, 0, 15)
|
||||||
SDL_SysWMinfo info;
|
SDL_SysWMinfo info;
|
||||||
@@ -152,36 +145,17 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
m_DrmFd = open(userDevice, O_RDWR | O_CLOEXEC);
|
m_DrmFd = open(userDevice, O_RDWR | O_CLOEXEC);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
QDir driDir("/dev/dri");
|
const char* defaultDevices[] = {"/dev/dri/renderD128", "/dev/dri/card0"};
|
||||||
|
|
||||||
// We have to explicitly ask for devices to be returned
|
for (unsigned int i = 0; i < SDL_arraysize(defaultDevices); i++) {
|
||||||
driDir.setFilter(QDir::Files | QDir::System);
|
m_DrmFd = open(defaultDevices[i], O_RDWR | O_CLOEXEC);
|
||||||
|
|
||||||
// Try a render node first since we aren't using DRM for output in this codepath
|
|
||||||
for (QFileInfo& node : driDir.entryInfoList(QStringList("renderD*"))) {
|
|
||||||
QByteArray absolutePath = node.absoluteFilePath().toUtf8();
|
|
||||||
m_DrmFd = open(absolutePath.constData(), O_RDWR | O_CLOEXEC);
|
|
||||||
if (m_DrmFd >= 0) {
|
if (m_DrmFd >= 0) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Opened DRM render node: %s",
|
"Opened DRM device: %s",
|
||||||
absolutePath.constData());
|
defaultDevices[i]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If that fails, try to use a primary node and hope for the best
|
|
||||||
if (m_DrmFd < 0) {
|
|
||||||
for (QFileInfo& node : driDir.entryInfoList(QStringList("card*"))) {
|
|
||||||
QByteArray absolutePath = node.absoluteFilePath().toUtf8();
|
|
||||||
m_DrmFd = open(absolutePath.constData(), O_RDWR | O_CLOEXEC);
|
|
||||||
if (m_DrmFd >= 0) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Opened DRM primary node: %s",
|
|
||||||
absolutePath.constData());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_DrmFd < 0) {
|
if (m_DrmFd < 0) {
|
||||||
@@ -232,25 +206,6 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
return DIRECT_RENDERING_INIT_FAILED;
|
return DIRECT_RENDERING_INIT_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params->testOnly) {
|
|
||||||
// Create a dummy renderer to force SDL to complete the modesetting
|
|
||||||
// operation that the KMSDRM backend keeps pending until the next
|
|
||||||
// time we swap buffers. We have to do this before we enumerate
|
|
||||||
// CRTC modes below.
|
|
||||||
SDL_Renderer* renderer = SDL_CreateRenderer(params->window, -1, SDL_RENDERER_SOFTWARE);
|
|
||||||
if (renderer != nullptr) {
|
|
||||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
|
|
||||||
SDL_RenderClear(renderer);
|
|
||||||
SDL_RenderPresent(renderer);
|
|
||||||
SDL_DestroyRenderer(renderer);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_CreateRenderer() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drmModeRes* resources = drmModeGetResources(m_DrmFd);
|
drmModeRes* resources = drmModeGetResources(m_DrmFd);
|
||||||
if (resources == nullptr) {
|
if (resources == nullptr) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -488,9 +443,6 @@ int DrmRenderer::getRendererAttributes()
|
|||||||
// This renderer supports HDR
|
// This renderer supports HDR
|
||||||
attributes |= RENDERER_ATTRIBUTE_HDR_SUPPORT;
|
attributes |= RENDERER_ATTRIBUTE_HDR_SUPPORT;
|
||||||
|
|
||||||
// This renderer does not buffer any frames in the graphics pipeline
|
|
||||||
attributes |= RENDERER_ATTRIBUTE_NO_BUFFERING;
|
|
||||||
|
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,12 +560,7 @@ void DrmRenderer::renderFrame(AVFrame* frame)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int colorspace = getFrameColorspace(frame);
|
if (frame->color_range != m_LastColorRange) {
|
||||||
bool fullRange = isFrameFullRange(frame);
|
|
||||||
|
|
||||||
// We also update the color range when the colorspace changes in order to handle initialization
|
|
||||||
// where the last color range value may not actual be applied to the plane.
|
|
||||||
if (fullRange != m_LastFullRange || colorspace != m_LastColorSpace) {
|
|
||||||
const char* desiredValue = getDrmColorRangeValue(frame);
|
const char* desiredValue = getDrmColorRangeValue(frame);
|
||||||
|
|
||||||
if (m_ColorRangeProp != nullptr && desiredValue != nullptr) {
|
if (m_ColorRangeProp != nullptr && desiredValue != nullptr) {
|
||||||
@@ -652,10 +599,10 @@ void DrmRenderer::renderFrame(AVFrame* frame)
|
|||||||
"COLOR_RANGE property does not exist on output plane. Colors may be inaccurate!");
|
"COLOR_RANGE property does not exist on output plane. Colors may be inaccurate!");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_LastFullRange = fullRange;
|
m_LastColorRange = frame->color_range;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (colorspace != m_LastColorSpace) {
|
if (frame->colorspace != m_LastColorSpace) {
|
||||||
const char* desiredValue = getDrmColorEncodingValue(frame);
|
const char* desiredValue = getDrmColorEncodingValue(frame);
|
||||||
|
|
||||||
if (m_ColorEncodingProp != nullptr && desiredValue != nullptr) {
|
if (m_ColorEncodingProp != nullptr && desiredValue != nullptr) {
|
||||||
@@ -694,7 +641,7 @@ void DrmRenderer::renderFrame(AVFrame* frame)
|
|||||||
"COLOR_ENCODING property does not exist on output plane. Colors may be inaccurate!");
|
"COLOR_ENCODING property does not exist on output plane. Colors may be inaccurate!");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_LastColorSpace = colorspace;
|
m_LastColorSpace = frame->colorspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the overlay
|
// Update the overlay
|
||||||
@@ -750,12 +697,12 @@ bool DrmRenderer::isDirectRenderingSupported()
|
|||||||
|
|
||||||
const char* DrmRenderer::getDrmColorEncodingValue(AVFrame* frame)
|
const char* DrmRenderer::getDrmColorEncodingValue(AVFrame* frame)
|
||||||
{
|
{
|
||||||
switch (getFrameColorspace(frame)) {
|
switch (frame->colorspace) {
|
||||||
case COLORSPACE_REC_601:
|
case AVCOL_SPC_SMPTE170M:
|
||||||
return "ITU-R BT.601 YCbCr";
|
return "ITU-R BT.601 YCbCr";
|
||||||
case COLORSPACE_REC_709:
|
case AVCOL_SPC_BT709:
|
||||||
return "ITU-R BT.709 YCbCr";
|
return "ITU-R BT.709 YCbCr";
|
||||||
case COLORSPACE_REC_2020:
|
case AVCOL_SPC_BT2020_NCL:
|
||||||
return "ITU-R BT.2020 YCbCr";
|
return "ITU-R BT.2020 YCbCr";
|
||||||
default:
|
default:
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -764,7 +711,14 @@ const char* DrmRenderer::getDrmColorEncodingValue(AVFrame* frame)
|
|||||||
|
|
||||||
const char* DrmRenderer::getDrmColorRangeValue(AVFrame* frame)
|
const char* DrmRenderer::getDrmColorRangeValue(AVFrame* frame)
|
||||||
{
|
{
|
||||||
return isFrameFullRange(frame) ? "YCbCr full range" : "YCbCr limited range";
|
switch (frame->color_range) {
|
||||||
|
case AVCOL_RANGE_MPEG:
|
||||||
|
return "YCbCr limited range";
|
||||||
|
case AVCOL_RANGE_JPEG:
|
||||||
|
return "YCbCr full range";
|
||||||
|
default:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_EGL
|
#ifdef HAVE_EGL
|
||||||
@@ -910,25 +864,40 @@ ssize_t DrmRenderer::exportEGLImages(AVFrame *frame, EGLDisplay dpy,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add colorspace metadata
|
// Add colorspace data if present
|
||||||
switch (getFrameColorspace(frame)) {
|
switch (frame->colorspace) {
|
||||||
case COLORSPACE_REC_601:
|
case AVCOL_SPC_BT2020_CL:
|
||||||
attribs[attribIndex++] = EGL_YUV_COLOR_SPACE_HINT_EXT;
|
case AVCOL_SPC_BT2020_NCL:
|
||||||
attribs[attribIndex++] = EGL_ITU_REC601_EXT;
|
|
||||||
break;
|
|
||||||
case COLORSPACE_REC_709:
|
|
||||||
attribs[attribIndex++] = EGL_YUV_COLOR_SPACE_HINT_EXT;
|
|
||||||
attribs[attribIndex++] = EGL_ITU_REC709_EXT;
|
|
||||||
break;
|
|
||||||
case COLORSPACE_REC_2020:
|
|
||||||
attribs[attribIndex++] = EGL_YUV_COLOR_SPACE_HINT_EXT;
|
attribs[attribIndex++] = EGL_YUV_COLOR_SPACE_HINT_EXT;
|
||||||
attribs[attribIndex++] = EGL_ITU_REC2020_EXT;
|
attribs[attribIndex++] = EGL_ITU_REC2020_EXT;
|
||||||
break;
|
break;
|
||||||
|
case AVCOL_SPC_SMPTE170M:
|
||||||
|
case AVCOL_SPC_BT470BG:
|
||||||
|
case AVCOL_SPC_FCC:
|
||||||
|
attribs[attribIndex++] = EGL_YUV_COLOR_SPACE_HINT_EXT;
|
||||||
|
attribs[attribIndex++] = EGL_ITU_REC601_EXT;
|
||||||
|
break;
|
||||||
|
case AVCOL_SPC_BT709:
|
||||||
|
attribs[attribIndex++] = EGL_YUV_COLOR_SPACE_HINT_EXT;
|
||||||
|
attribs[attribIndex++] = EGL_ITU_REC709_EXT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add color range metadata
|
// Add color range data if present
|
||||||
attribs[attribIndex++] = EGL_SAMPLE_RANGE_HINT_EXT;
|
switch (frame->color_range) {
|
||||||
attribs[attribIndex++] = isFrameFullRange(frame) ? EGL_YUV_FULL_RANGE_EXT : EGL_YUV_NARROW_RANGE_EXT;
|
case AVCOL_RANGE_JPEG:
|
||||||
|
attribs[attribIndex++] = EGL_SAMPLE_RANGE_HINT_EXT;
|
||||||
|
attribs[attribIndex++] = EGL_YUV_FULL_RANGE_EXT;
|
||||||
|
break;
|
||||||
|
case AVCOL_RANGE_MPEG:
|
||||||
|
attribs[attribIndex++] = EGL_SAMPLE_RANGE_HINT_EXT;
|
||||||
|
attribs[attribIndex++] = EGL_YUV_NARROW_RANGE_EXT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Terminate the attribute list
|
// Terminate the attribute list
|
||||||
attribs[attribIndex++] = EGL_NONE;
|
attribs[attribIndex++] = EGL_NONE;
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ private:
|
|||||||
uint32_t m_CrtcId;
|
uint32_t m_CrtcId;
|
||||||
uint32_t m_PlaneId;
|
uint32_t m_PlaneId;
|
||||||
uint32_t m_CurrentFbId;
|
uint32_t m_CurrentFbId;
|
||||||
bool m_LastFullRange;
|
AVColorRange m_LastColorRange;
|
||||||
int m_LastColorSpace;
|
AVColorSpace m_LastColorSpace;
|
||||||
drmModePropertyPtr m_ColorEncodingProp;
|
drmModePropertyPtr m_ColorEncodingProp;
|
||||||
drmModePropertyPtr m_ColorRangeProp;
|
drmModePropertyPtr m_ColorRangeProp;
|
||||||
drmModePropertyPtr m_HdrOutputMetadataProp;
|
drmModePropertyPtr m_HdrOutputMetadataProp;
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ typedef struct _VERTEX
|
|||||||
float tu, tv;
|
float tu, tv;
|
||||||
} VERTEX, *PVERTEX;
|
} VERTEX, *PVERTEX;
|
||||||
|
|
||||||
DXVA2Renderer::DXVA2Renderer(int decoderSelectionPass) :
|
DXVA2Renderer::DXVA2Renderer() :
|
||||||
m_DecoderSelectionPass(decoderSelectionPass),
|
|
||||||
m_DecService(nullptr),
|
m_DecService(nullptr),
|
||||||
m_Decoder(nullptr),
|
m_Decoder(nullptr),
|
||||||
m_SurfacesUsed(0),
|
m_SurfacesUsed(0),
|
||||||
@@ -435,13 +434,6 @@ bool DXVA2Renderer::initializeDeviceQuirks()
|
|||||||
// For other GPUs, we'll avoid populating it as was our previous behavior.
|
// For other GPUs, we'll avoid populating it as was our previous behavior.
|
||||||
m_DeviceQuirks |= DXVA2_QUIRK_SET_DEST_FORMAT;
|
m_DeviceQuirks |= DXVA2_QUIRK_SET_DEST_FORMAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag this display device if it has a WDDM 2.0+ driver for the decoder selection logic
|
|
||||||
if (HIWORD(id.DriverVersion.HighPart) >= 20) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Detected WDDM 2.0 or later display driver");
|
|
||||||
m_DeviceQuirks |= DXVA2_QUIRK_WDDM_20_PLUS;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -567,7 +559,7 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
|||||||
d3dpp.hDeviceWindow = info.info.win.window;
|
d3dpp.hDeviceWindow = info.info.win.window;
|
||||||
d3dpp.Flags = D3DPRESENTFLAG_VIDEO;
|
d3dpp.Flags = D3DPRESENTFLAG_VIDEO;
|
||||||
|
|
||||||
if (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT) {
|
if (m_VideoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||||
// Verify 10-bit A2R10G10B10 color support. This is only available
|
// Verify 10-bit A2R10G10B10 color support. This is only available
|
||||||
// as a display format in full-screen exclusive mode on DX9.
|
// as a display format in full-screen exclusive mode on DX9.
|
||||||
hr = d3d9ex->CheckDeviceType(adapterIndex,
|
hr = d3d9ex->CheckDeviceType(adapterIndex,
|
||||||
@@ -589,7 +581,7 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
|||||||
d3dpp.BackBufferHeight = currentMode.Height;
|
d3dpp.BackBufferHeight = currentMode.Height;
|
||||||
d3dpp.FullScreen_RefreshRateInHz = currentMode.RefreshRate;
|
d3dpp.FullScreen_RefreshRateInHz = currentMode.RefreshRate;
|
||||||
|
|
||||||
if (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT) {
|
if (m_VideoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||||
d3dpp.BackBufferFormat = currentMode.Format = D3DFMT_A2R10G10B10;
|
d3dpp.BackBufferFormat = currentMode.Format = D3DFMT_A2R10G10B10;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -609,18 +601,20 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
|||||||
// If composition enabled, disable v-sync and let DWM manage things
|
// If composition enabled, disable v-sync and let DWM manage things
|
||||||
// to reduce latency by avoiding double v-syncing.
|
// to reduce latency by avoiding double v-syncing.
|
||||||
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
|
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
|
||||||
d3dpp.SwapEffect = D3DSWAPEFFECT_FLIPEX;
|
|
||||||
|
|
||||||
|
// If V-sync is enabled (not rendering faster than display),
|
||||||
|
// we can use FlipEx for more efficient swapping.
|
||||||
if (enableVsync) {
|
if (enableVsync) {
|
||||||
// D3DSWAPEFFECT_FLIPEX requires at least 3 back buffers to allow us to
|
// D3DSWAPEFFECT_FLIPEX requires at least 2 back buffers to allow us to
|
||||||
// continue while DWM is waiting to render the surface to the display.
|
// continue while DWM is waiting to render the surface to the display.
|
||||||
// NVIDIA seems to be fine with 2, but AMD needs 3 to perform well.
|
d3dpp.SwapEffect = D3DSWAPEFFECT_FLIPEX;
|
||||||
d3dpp.BackBufferCount = 3;
|
d3dpp.BackBufferCount = 2;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// With V-sync off, we need 1 more back buffer to render to while the
|
// With V-sync off, we won't use FlipEx because that will block while
|
||||||
// driver/DWM are holding the others.
|
// DWM is waiting to render our surface (effectively behaving like V-Sync).
|
||||||
d3dpp.BackBufferCount = 4;
|
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
|
||||||
|
d3dpp.BackBufferCount = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_BlockingPresent = false;
|
m_BlockingPresent = false;
|
||||||
@@ -685,17 +679,12 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must not call this for flip swapchains. It will counterintuitively
|
hr = m_Device->SetMaximumFrameLatency(1);
|
||||||
// increase latency by forcing our Present() to block on DWM even when
|
if (FAILED(hr)) {
|
||||||
// using D3DPRESENT_INTERVAL_IMMEDIATE.
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
if (d3dpp.SwapEffect != D3DSWAPEFFECT_FLIPEX) {
|
"SetMaximumFrameLatency() failed: %x",
|
||||||
hr = m_Device->SetMaximumFrameLatency(1);
|
hr);
|
||||||
if (FAILED(hr)) {
|
return false;
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SetMaximumFrameLatency() failed: %x",
|
|
||||||
hr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -703,23 +692,16 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
|||||||
|
|
||||||
bool DXVA2Renderer::initialize(PDECODER_PARAMETERS params)
|
bool DXVA2Renderer::initialize(PDECODER_PARAMETERS params)
|
||||||
{
|
{
|
||||||
if (qgetenv("DXVA2_ENABLED") == "0") {
|
// Don't use DXVA2 for HDR10. While it can render 10-bit color, it doesn't support
|
||||||
|
// the HDR colorspace and HDR display metadata required to enable HDR mode properly.
|
||||||
|
if (params->videoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (qgetenv("DXVA2_ENABLED") == "0") {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"DXVA2 is disabled by environment variable");
|
"DXVA2 is disabled by environment variable");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if ((params->videoFormat & VIDEO_FORMAT_MASK_10BIT) && m_DecoderSelectionPass == 0) {
|
|
||||||
// Avoid using DXVA2 for HDR10. While it can render 10-bit color, it doesn't support
|
|
||||||
// the HDR colorspace and HDR display metadata required to enable HDR mode properly.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#ifndef Q_PROCESSOR_X86
|
|
||||||
else if (qgetenv("DXVA2_ENABLED") != "1" && m_DecoderSelectionPass == 0) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"DXVA2 is disabled by default on ARM64. Set DXVA2_ENABLED=1 to override.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_VideoFormat = params->videoFormat;
|
m_VideoFormat = params->videoFormat;
|
||||||
m_VideoWidth = params->width;
|
m_VideoWidth = params->width;
|
||||||
@@ -751,7 +733,7 @@ bool DXVA2Renderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_Unknown;
|
m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_Unknown;
|
||||||
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame;
|
||||||
|
|
||||||
if (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT) {
|
if (m_VideoFormat == VIDEO_FORMAT_H265_MAIN10) {
|
||||||
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('P','0','1','0');
|
m_Desc.Format = (D3DFORMAT)MAKEFOURCC('P','0','1','0');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -766,26 +748,6 @@ bool DXVA2Renderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a WDDM 2.0 or later display driver and we're not running in
|
|
||||||
// full-screen exclusive mode, prefer the D3D11VA renderer.
|
|
||||||
//
|
|
||||||
// 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).
|
|
||||||
//
|
|
||||||
// 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) &&
|
|
||||||
!((SDL_GetWindowFlags(params->window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN)) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Defaulting to D3D11VA for non-FSE mode");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!initializeDecoder()) {
|
if (!initializeDecoder()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -861,9 +823,19 @@ void DXVA2Renderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy (and convert, if necessary) the surface pixels to the texture
|
if (newSurface->pitch == lockedRect.Pitch) {
|
||||||
SDL_ConvertPixels(newSurface->w, newSurface->h, newSurface->format->format, newSurface->pixels,
|
// If the pitch matches, we can take the fast path and use a single copy to transfer the pixels
|
||||||
newSurface->pitch, SDL_PIXELFORMAT_ARGB8888, lockedRect.pBits, lockedRect.Pitch);
|
RtlCopyMemory(lockedRect.pBits, newSurface->pixels, newSurface->pitch * newSurface->h);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If the pitch doesn't match, we'll need to copy each row separately
|
||||||
|
int pitch = SDL_min(newSurface->pitch, lockedRect.Pitch);
|
||||||
|
for (int i = 0; i < newSurface->h; i++) {
|
||||||
|
RtlCopyMemory(((PUCHAR)lockedRect.pBits) + (lockedRect.Pitch * i),
|
||||||
|
((PUCHAR)newSurface->pixels) + (newSurface->pitch * i),
|
||||||
|
pitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newTexture->UnlockRect(0);
|
newTexture->UnlockRect(0);
|
||||||
|
|
||||||
@@ -1004,17 +976,22 @@ int DXVA2Renderer::getDecoderColorspace()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int DXVA2Renderer::getDecoderCapabilities()
|
|
||||||
{
|
|
||||||
return CAPABILITY_REFERENCE_FRAME_INVALIDATION_HEVC;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DXVA2Renderer::renderFrame(AVFrame *frame)
|
void DXVA2Renderer::renderFrame(AVFrame *frame)
|
||||||
{
|
{
|
||||||
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
|
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
m_Desc.SampleFormat.NominalRange = isFrameFullRange(frame) ? DXVA2_NominalRange_0_255 : DXVA2_NominalRange_16_235;
|
switch (frame->color_range) {
|
||||||
|
case AVCOL_RANGE_JPEG:
|
||||||
|
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_0_255;
|
||||||
|
break;
|
||||||
|
case AVCOL_RANGE_MPEG:
|
||||||
|
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_16_235;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
m_Desc.SampleFormat.NominalRange = DXVA2_NominalRange_Unknown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
switch (frame->color_primaries) {
|
switch (frame->color_primaries) {
|
||||||
case AVCOL_PRI_BT709:
|
case AVCOL_PRI_BT709:
|
||||||
@@ -1062,13 +1039,17 @@ void DXVA2Renderer::renderFrame(AVFrame *frame)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (getFrameColorspace(frame)) {
|
switch (frame->colorspace) {
|
||||||
case COLORSPACE_REC_709:
|
case AVCOL_SPC_BT709:
|
||||||
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT709;
|
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT709;
|
||||||
break;
|
break;
|
||||||
case COLORSPACE_REC_601:
|
case AVCOL_SPC_BT470BG:
|
||||||
|
case AVCOL_SPC_SMPTE170M:
|
||||||
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT601;
|
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_BT601;
|
||||||
break;
|
break;
|
||||||
|
case AVCOL_SPC_SMPTE240M:
|
||||||
|
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_SMPTE240M;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_Unknown;
|
m_Desc.SampleFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_Unknown;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -13,14 +13,13 @@ extern "C" {
|
|||||||
class DXVA2Renderer : public IFFmpegRenderer
|
class DXVA2Renderer : public IFFmpegRenderer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DXVA2Renderer(int decoderSelectionPass);
|
DXVA2Renderer();
|
||||||
virtual ~DXVA2Renderer() override;
|
virtual ~DXVA2Renderer() override;
|
||||||
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||||
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override;
|
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override;
|
||||||
virtual void renderFrame(AVFrame* frame) override;
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
virtual void notifyOverlayUpdated(Overlay::OverlayType type) override;
|
virtual void notifyOverlayUpdated(Overlay::OverlayType type) override;
|
||||||
virtual int getDecoderColorspace() override;
|
virtual int getDecoderColorspace() override;
|
||||||
virtual int getDecoderCapabilities() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool initializeDecoder();
|
bool initializeDecoder();
|
||||||
@@ -45,8 +44,6 @@ private:
|
|||||||
static
|
static
|
||||||
int ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int flags);
|
int ffGetBuffer2(AVCodecContext* context, AVFrame* frame, int flags);
|
||||||
|
|
||||||
int m_DecoderSelectionPass;
|
|
||||||
|
|
||||||
int m_VideoFormat;
|
int m_VideoFormat;
|
||||||
int m_VideoWidth;
|
int m_VideoWidth;
|
||||||
int m_VideoHeight;
|
int m_VideoHeight;
|
||||||
@@ -80,6 +77,5 @@ private:
|
|||||||
|
|
||||||
#define DXVA2_QUIRK_NO_VP 0x01
|
#define DXVA2_QUIRK_NO_VP 0x01
|
||||||
#define DXVA2_QUIRK_SET_DEST_FORMAT 0x02
|
#define DXVA2_QUIRK_SET_DEST_FORMAT 0x02
|
||||||
#define DXVA2_QUIRK_WDDM_20_PLUS 0x04
|
|
||||||
int m_DeviceQuirks;
|
int m_DeviceQuirks;
|
||||||
};
|
};
|
||||||
@@ -193,7 +193,6 @@ void EGLRenderer::renderOverlay(Overlay::OverlayType type)
|
|||||||
SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type);
|
SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type);
|
||||||
if (newSurface != nullptr) {
|
if (newSurface != nullptr) {
|
||||||
SDL_assert(!SDL_MUSTLOCK(newSurface));
|
SDL_assert(!SDL_MUSTLOCK(newSurface));
|
||||||
SDL_assert(newSurface->format->format == SDL_PIXELFORMAT_ARGB8888);
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, m_OverlayTextures[type]);
|
glBindTexture(GL_TEXTURE_2D, m_OverlayTextures[type]);
|
||||||
|
|
||||||
@@ -459,8 +458,8 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
// Don't retry if we've already failed to create a renderer for this
|
// Don't retry if we've already failed to create a renderer for this
|
||||||
// window *unless* the format has changed from 10-bit to 8-bit.
|
// window *unless* the format has changed from 10-bit to 8-bit.
|
||||||
if (m_Window == s_LastFailedWindow &&
|
if (m_Window == s_LastFailedWindow &&
|
||||||
!!(params->videoFormat & VIDEO_FORMAT_MASK_10BIT) ==
|
(params->videoFormat & VIDEO_FORMAT_H265_MAIN10) ==
|
||||||
!!(s_LastFailedVideoFormat & VIDEO_FORMAT_MASK_10BIT)) {
|
(s_LastFailedVideoFormat & VIDEO_FORMAT_H265_MAIN10)) {
|
||||||
EGL_LOG(Error, "SDL_CreateRenderer() already failed on this window!");
|
EGL_LOG(Error, "SDL_CreateRenderer() already failed on this window!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -664,22 +663,10 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
m_ViewportWidth = dst.w;
|
m_ViewportWidth = dst.w;
|
||||||
m_ViewportHeight = dst.h;
|
m_ViewportHeight = dst.h;
|
||||||
|
|
||||||
// SDL always uses swap interval 0 under the hood on Wayland systems,
|
glClearColor(0, 0, 0, 1);
|
||||||
// because the compositor guarantees tear-free rendering. In this
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
// situation, swap interval > 0 behaves as a frame pacing option
|
|
||||||
// rather than a way to eliminate tearing as SDL will block in
|
if (params->enableVsync) {
|
||||||
// SwapBuffers until the compositor consumes the frame. This will
|
|
||||||
// needlessly increases latency, so we should avoid it unless asked.
|
|
||||||
//
|
|
||||||
// HACK: In SDL 2.0.22+ on GNOME systems with fractional DPI scaling,
|
|
||||||
// the Wayland viewport can be stale when using Super+Left/Right/Up
|
|
||||||
// to resize the window. This seems to happen significantly more often
|
|
||||||
// with vsync enabled, so this also mitigates that problem too.
|
|
||||||
if (params->enableVsync
|
|
||||||
#ifdef SDL_VIDEO_DRIVER_WAYLAND
|
|
||||||
&& (info.subsystem != SDL_SYSWM_WAYLAND || params->enableFramePacing)
|
|
||||||
#endif
|
|
||||||
) {
|
|
||||||
SDL_GL_SetSwapInterval(1);
|
SDL_GL_SetSwapInterval(1);
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 15) && defined(SDL_VIDEO_DRIVER_KMSDRM)
|
#if SDL_VERSION_ATLEAST(2, 0, 15) && defined(SDL_VIDEO_DRIVER_KMSDRM)
|
||||||
@@ -696,12 +683,7 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
SDL_GL_SetSwapInterval(0);
|
SDL_GL_SetSwapInterval(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params->testOnly) {
|
SDL_GL_SwapWindow(params->window);
|
||||||
// Draw a black frame until the video stream starts rendering
|
|
||||||
glClearColor(0, 0, 0, 1);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
SDL_GL_SwapWindow(params->window);
|
|
||||||
}
|
|
||||||
|
|
||||||
glGenTextures(EGL_MAX_PLANES, m_Textures);
|
glGenTextures(EGL_MAX_PLANES, m_Textures);
|
||||||
for (size_t i = 0; i < EGL_MAX_PLANES; ++i) {
|
for (size_t i = 0; i < EGL_MAX_PLANES; ++i) {
|
||||||
@@ -732,15 +714,6 @@ bool EGLRenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
// Detach the context from this thread, so the render thread can attach it
|
// Detach the context from this thread, so the render thread can attach it
|
||||||
SDL_GL_MakeCurrent(m_Window, nullptr);
|
SDL_GL_MakeCurrent(m_Window, nullptr);
|
||||||
|
|
||||||
#ifdef SDL_HINT_VIDEO_X11_FORCE_EGL
|
|
||||||
if (err == GL_NO_ERROR) {
|
|
||||||
// If we got a working GL implementation via EGL, avoid using GLX from now on.
|
|
||||||
// GLX will cause problems if we later want to use EGL again on this window.
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "EGL passed preflight checks. Using EGL for GL context creation.");
|
|
||||||
SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return err == GL_NO_ERROR;
|
return err == GL_NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,7 +721,10 @@ const float *EGLRenderer::getColorOffsets(const AVFrame* frame) {
|
|||||||
static const float limitedOffsets[] = { 16.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f };
|
static const float limitedOffsets[] = { 16.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f };
|
||||||
static const float fullOffsets[] = { 0.0f, 128.0f / 255.0f, 128.0f / 255.0f };
|
static const float fullOffsets[] = { 0.0f, 128.0f / 255.0f, 128.0f / 255.0f };
|
||||||
|
|
||||||
return isFrameFullRange(frame) ? fullOffsets : limitedOffsets;
|
// This handles the case where the color range is unknown,
|
||||||
|
// so that we use Limited color range which is the default
|
||||||
|
// behavior for Moonlight.
|
||||||
|
return (frame->color_range == AVCOL_RANGE_JPEG) ? fullOffsets : limitedOffsets;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float *EGLRenderer::getColorMatrix(const AVFrame* frame) {
|
const float *EGLRenderer::getColorMatrix(const AVFrame* frame) {
|
||||||
@@ -786,17 +762,33 @@ const float *EGLRenderer::getColorMatrix(const AVFrame* frame) {
|
|||||||
1.4746f, -0.5714f, 0.0f
|
1.4746f, -0.5714f, 0.0f
|
||||||
};
|
};
|
||||||
|
|
||||||
bool fullRange = isFrameFullRange(frame);
|
// This handles the case where the color range is unknown,
|
||||||
switch (getFrameColorspace(frame)) {
|
// so that we use Limited color range which is the default
|
||||||
case COLORSPACE_REC_601:
|
// behavior for Moonlight.
|
||||||
|
bool fullRange = (frame->color_range == AVCOL_RANGE_JPEG);
|
||||||
|
switch (frame->colorspace) {
|
||||||
|
case AVCOL_SPC_SMPTE170M:
|
||||||
|
case AVCOL_SPC_BT470BG:
|
||||||
return fullRange ? bt601Full : bt601Lim;
|
return fullRange ? bt601Full : bt601Lim;
|
||||||
case COLORSPACE_REC_709:
|
case AVCOL_SPC_BT709:
|
||||||
return fullRange ? bt709Full : bt709Lim;
|
return fullRange ? bt709Full : bt709Lim;
|
||||||
case COLORSPACE_REC_2020:
|
case AVCOL_SPC_BT2020_NCL:
|
||||||
|
case AVCOL_SPC_BT2020_CL:
|
||||||
return fullRange ? bt2020Full : bt2020Lim;
|
return fullRange ? bt2020Full : bt2020Lim;
|
||||||
default:
|
default:
|
||||||
SDL_assert(false);
|
// Some backends don't populate this, so we'll assume
|
||||||
}
|
// the host gave us what we asked for by default.
|
||||||
|
switch (getDecoderColorspace()) {
|
||||||
|
case COLORSPACE_REC_601:
|
||||||
|
return fullRange ? bt601Full : bt601Lim;
|
||||||
|
case COLORSPACE_REC_709:
|
||||||
|
return fullRange ? bt709Full : bt709Lim;
|
||||||
|
case COLORSPACE_REC_2020:
|
||||||
|
return fullRange ? bt2020Full : bt2020Lim;
|
||||||
|
default:
|
||||||
|
SDL_assert(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return bt601Lim;
|
return bt601Lim;
|
||||||
}
|
}
|
||||||
@@ -867,18 +859,9 @@ void EGLRenderer::waitToRender()
|
|||||||
// See comment in renderFrame() for more details.
|
// See comment in renderFrame() for more details.
|
||||||
SDL_GL_MakeCurrent(m_Window, m_Context);
|
SDL_GL_MakeCurrent(m_Window, m_Context);
|
||||||
|
|
||||||
// Wait for the previous buffer swap to finish before picking the next frame to render.
|
if (m_LastRenderSync != 0) {
|
||||||
// This way we'll get the latest available frame and render it without blocking.
|
SDL_assert(m_eglClientWaitSync != nullptr);
|
||||||
if (m_BlockingSwapBuffers) {
|
m_eglClientWaitSync(m_EGLDisplay, m_LastRenderSync, EGL_SYNC_FLUSH_COMMANDS_BIT, EGL_FOREVER);
|
||||||
// Try to use eglClientWaitSync() if the driver supports it
|
|
||||||
if (m_LastRenderSync != EGL_NO_SYNC) {
|
|
||||||
SDL_assert(m_eglClientWaitSync != nullptr);
|
|
||||||
m_eglClientWaitSync(m_EGLDisplay, m_LastRenderSync, EGL_SYNC_FLUSH_COMMANDS_BIT, EGL_FOREVER);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Use glFinish() if fences aren't available
|
|
||||||
glFinish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -901,18 +884,6 @@ void EGLRenderer::renderFrame(AVFrame* frame)
|
|||||||
|
|
||||||
if (!specialize()) {
|
if (!specialize()) {
|
||||||
m_EGLImagePixelFormat = AV_PIX_FMT_NONE;
|
m_EGLImagePixelFormat = AV_PIX_FMT_NONE;
|
||||||
|
|
||||||
// Failure to specialize is fatal. We must reset the renderer
|
|
||||||
// to recover successfully.
|
|
||||||
//
|
|
||||||
// Note: This seems to be easy to trigger when transitioning from
|
|
||||||
// maximized mode by dragging the window down on GNOME 42 using
|
|
||||||
// XWayland. Other strategies like calling glGetError() don't seem
|
|
||||||
// to be able to detect this situation for some reason.
|
|
||||||
SDL_Event event;
|
|
||||||
event.type = SDL_RENDER_TARGETS_RESET;
|
|
||||||
SDL_PushEvent(&event);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -952,14 +923,8 @@ void EGLRenderer::renderFrame(AVFrame* frame)
|
|||||||
SDL_GL_SwapWindow(m_Window);
|
SDL_GL_SwapWindow(m_Window);
|
||||||
|
|
||||||
if (m_BlockingSwapBuffers) {
|
if (m_BlockingSwapBuffers) {
|
||||||
// This glClear() requires the new back buffer to complete. This ensures
|
|
||||||
// our eglClientWaitSync() or glFinish() call in waitToRender() will not
|
|
||||||
// return before the new buffer is actually ready for rendering.
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
|
|
||||||
// If we this EGL implementation supports fences, use those to delay
|
// If we this EGL implementation supports fences, use those to delay
|
||||||
// rendering the next frame until this one is completed. If not, we'll
|
// rendering the next frame until this one is completed.
|
||||||
// have to just use glFinish().
|
|
||||||
if (m_eglClientWaitSync != nullptr) {
|
if (m_eglClientWaitSync != nullptr) {
|
||||||
// Delete the sync object from last render
|
// Delete the sync object from last render
|
||||||
if (m_LastRenderSync != EGL_NO_SYNC) {
|
if (m_LastRenderSync != EGL_NO_SYNC) {
|
||||||
@@ -975,6 +940,17 @@ void EGLRenderer::renderFrame(AVFrame* frame)
|
|||||||
m_LastRenderSync = m_eglCreateSyncKHR(m_EGLDisplay, EGL_SYNC_FENCE, nullptr);
|
m_LastRenderSync = m_eglCreateSyncKHR(m_EGLDisplay, EGL_SYNC_FENCE, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// This glClear() forces us to block until the buffer swap is
|
||||||
|
// complete to continue rendering. Mesa won't actually wait
|
||||||
|
// for the swap with just glFinish() alone. Waiting here keeps us
|
||||||
|
// in lock step with the display refresh rate. If we don't wait
|
||||||
|
// here, we'll stall on the first GL call next frame. Doing the
|
||||||
|
// wait here instead allows more time for a newer frame to arrive
|
||||||
|
// for next renderFrame() call.
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
glFinish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Backend->freeEGLImages(m_EGLDisplay, imgs);
|
m_Backend->freeEGLImages(m_EGLDisplay, imgs);
|
||||||
@@ -992,17 +968,6 @@ bool EGLRenderer::testRenderFrame(AVFrame* frame)
|
|||||||
{
|
{
|
||||||
EGLImage imgs[EGL_MAX_PLANES];
|
EGLImage imgs[EGL_MAX_PLANES];
|
||||||
|
|
||||||
#ifdef HAVE_MMAL
|
|
||||||
// EGL rendering is so slow on the Raspberry Pi that we should basically
|
|
||||||
// never use it. It is suitable for 1080p 30 FPS on a good day, and much
|
|
||||||
// much less than that if you decide to do something crazy like stream
|
|
||||||
// in full-screen. It's nice that it at least works now on Bullseye, but
|
|
||||||
// it's so slow that we actually wish it didn't.
|
|
||||||
if (qgetenv("RPI_ALLOW_EGL_RENDER") != "1") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Make sure we can get working EGLImages from the backend renderer.
|
// Make sure we can get working EGLImages from the backend renderer.
|
||||||
// Some devices (Raspberry Pi) will happily decode into DRM formats that
|
// Some devices (Raspberry Pi) will happily decode into DRM formats that
|
||||||
// its own GL implementation won't accept in eglCreateImage().
|
// its own GL implementation won't accept in eglCreateImage().
|
||||||
|
|||||||
@@ -219,9 +219,20 @@ int MmalRenderer::getDecoderColorspace()
|
|||||||
|
|
||||||
void MmalRenderer::setupBackground(PDECODER_PARAMETERS params)
|
void MmalRenderer::setupBackground(PDECODER_PARAMETERS params)
|
||||||
{
|
{
|
||||||
if (!params->testOnly) {
|
SDL_SysWMinfo info;
|
||||||
// Create a renderer and draw a black background for the area not covered by the MMAL overlay.
|
|
||||||
// On the KMSDRM backend, this triggers the modeset that puts the CRTC into the mode we selected.
|
SDL_VERSION(&info.version);
|
||||||
|
|
||||||
|
if (!SDL_GetWindowWMInfo(params->window, &info)) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL_GetWindowWMInfo() failed: %s",
|
||||||
|
SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On X11, we can safely create a renderer and draw a black background.
|
||||||
|
// Due to conflicts with Qt, it's unsafe to do this for KMSDRM.
|
||||||
|
if (info.subsystem == SDL_SYSWM_X11) {
|
||||||
m_BackgroundRenderer = SDL_CreateRenderer(params->window, -1, SDL_RENDERER_SOFTWARE);
|
m_BackgroundRenderer = SDL_CreateRenderer(params->window, -1, SDL_RENDERER_SOFTWARE);
|
||||||
if (m_BackgroundRenderer == nullptr) {
|
if (m_BackgroundRenderer == nullptr) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
|||||||
@@ -11,11 +11,8 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Limit the number of queued frames to prevent excessive memory consumption
|
// Limit the number of queued frames to prevent excessive memory consumption
|
||||||
// if the V-Sync source or renderer is blocked for a while. It's important
|
// if the V-Sync source or renderer is blocked for a while.
|
||||||
// that the sum of all queued frames between both pacing and rendering queues
|
#define MAX_QUEUED_FRAMES 8
|
||||||
// must not exceed the number buffer pool size to avoid running the decoder
|
|
||||||
// out of available decoding surfaces.
|
|
||||||
#define MAX_QUEUED_FRAMES 4
|
|
||||||
|
|
||||||
// We may be woken up slightly late so don't go all the way
|
// We may be woken up slightly late so don't go all the way
|
||||||
// up to the next V-sync since we may accidentally step into
|
// up to the next V-sync since we may accidentally step into
|
||||||
@@ -75,10 +72,8 @@ void Pacer::renderOnMainThread()
|
|||||||
m_FrameQueueLock.lock();
|
m_FrameQueueLock.lock();
|
||||||
|
|
||||||
if (!m_RenderQueue.isEmpty()) {
|
if (!m_RenderQueue.isEmpty()) {
|
||||||
AVFrame* frame = m_RenderQueue.dequeue();
|
// Releases m_FrameQueueLock
|
||||||
m_FrameQueueLock.unlock();
|
renderLastFrameAndUnlock();
|
||||||
|
|
||||||
renderFrame(frame);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_FrameQueueLock.unlock();
|
m_FrameQueueLock.unlock();
|
||||||
@@ -114,10 +109,9 @@ int Pacer::renderThread(void* context)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
AVFrame* frame = me->m_RenderQueue.dequeue();
|
// Render the latest frame and discard the others
|
||||||
me->m_FrameQueueLock.unlock();
|
// NB: m_FrameQueueLock still held here!
|
||||||
|
me->renderLastFrameAndUnlock();
|
||||||
me->renderFrame(frame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify the renderer that it is being destroyed soon
|
// Notify the renderer that it is being destroyed soon
|
||||||
@@ -147,6 +141,33 @@ void Pacer::enqueueFrameForRenderingAndUnlock(AVFrame *frame)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Caller must hold m_FrameQueueLock
|
||||||
|
void Pacer::renderLastFrameAndUnlock()
|
||||||
|
{
|
||||||
|
// Dequeue the most recent frame for rendering and free the others.
|
||||||
|
AVFrame* lastFrame = nullptr;
|
||||||
|
while (!m_RenderQueue.isEmpty()) {
|
||||||
|
if (lastFrame != nullptr) {
|
||||||
|
// Don't hold the frame queue lock across av_frame_free(),
|
||||||
|
// since it could need to talk to the GPU driver. This is safe
|
||||||
|
// because we're guaranteed that the queue will not shrink during
|
||||||
|
// this time (and so dequeue() below will always get something).
|
||||||
|
m_FrameQueueLock.unlock();
|
||||||
|
av_frame_free(&lastFrame);
|
||||||
|
m_VideoStats->pacerDroppedFrames++;
|
||||||
|
m_FrameQueueLock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
lastFrame = m_RenderQueue.dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release the frame queue lock before rendering
|
||||||
|
m_FrameQueueLock.unlock();
|
||||||
|
|
||||||
|
// Render and free the mot current frame
|
||||||
|
renderFrame(lastFrame);
|
||||||
|
}
|
||||||
|
|
||||||
// Called in an arbitrary thread by the IVsyncSource on V-sync
|
// Called in an arbitrary thread by the IVsyncSource on V-sync
|
||||||
// or an event synchronized with V-sync
|
// or an event synchronized with V-sync
|
||||||
void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
|
void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
|
||||||
@@ -211,7 +232,6 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
|||||||
{
|
{
|
||||||
m_MaxVideoFps = maxVideoFps;
|
m_MaxVideoFps = maxVideoFps;
|
||||||
m_DisplayFps = StreamUtils::getDisplayRefreshRate(window);
|
m_DisplayFps = StreamUtils::getDisplayRefreshRate(window);
|
||||||
m_RendererAttributes = m_VsyncRenderer->getRendererAttributes();
|
|
||||||
|
|
||||||
if (enablePacing) {
|
if (enablePacing) {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -229,8 +249,6 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
|||||||
// immediately like they used to.
|
// immediately like they used to.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SDL_assert(m_VsyncSource != nullptr || !(m_RendererAttributes & RENDERER_ATTRIBUTE_FORCE_PACING));
|
|
||||||
|
|
||||||
if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window, m_DisplayFps)) {
|
if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window, m_DisplayFps)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -265,32 +283,23 @@ void Pacer::renderFrame(AVFrame* frame)
|
|||||||
// Drop frames if we have too many queued up for a while
|
// Drop frames if we have too many queued up for a while
|
||||||
m_FrameQueueLock.lock();
|
m_FrameQueueLock.lock();
|
||||||
|
|
||||||
int frameDropTarget;
|
int frameDropTarget = 0;
|
||||||
|
for (int queueHistoryEntry : m_RenderQueueHistory) {
|
||||||
if (m_RendererAttributes & RENDERER_ATTRIBUTE_NO_BUFFERING) {
|
if (queueHistoryEntry == 0) {
|
||||||
// Renderers that don't buffer any frames but don't support waitToRender() need us to buffer
|
// Be lenient as long as the queue length
|
||||||
// an extra frame to ensure they don't starve while waiting to present.
|
// resolves before the end of frame history
|
||||||
frameDropTarget = 1;
|
frameDropTarget = 2;
|
||||||
}
|
break;
|
||||||
else {
|
|
||||||
frameDropTarget = 0;
|
|
||||||
for (int queueHistoryEntry : m_RenderQueueHistory) {
|
|
||||||
if (queueHistoryEntry == 0) {
|
|
||||||
// Be lenient as long as the queue length
|
|
||||||
// resolves before the end of frame history
|
|
||||||
frameDropTarget = 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep a rolling 500 ms window of render queue history
|
|
||||||
if (m_RenderQueueHistory.count() == m_MaxVideoFps / 2) {
|
|
||||||
m_RenderQueueHistory.dequeue();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_RenderQueueHistory.enqueue(m_RenderQueue.count());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep a rolling 500 ms window of render queue history
|
||||||
|
if (m_RenderQueueHistory.count() == m_MaxVideoFps / 2) {
|
||||||
|
m_RenderQueueHistory.dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_RenderQueueHistory.enqueue(m_RenderQueue.count());
|
||||||
|
|
||||||
// Catch up if we're several frames ahead
|
// Catch up if we're several frames ahead
|
||||||
while (m_RenderQueue.count() > frameDropTarget) {
|
while (m_RenderQueue.count() > frameDropTarget) {
|
||||||
AVFrame* frame = m_RenderQueue.dequeue();
|
AVFrame* frame = m_RenderQueue.dequeue();
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ private:
|
|||||||
|
|
||||||
void enqueueFrameForRenderingAndUnlock(AVFrame* frame);
|
void enqueueFrameForRenderingAndUnlock(AVFrame* frame);
|
||||||
|
|
||||||
|
void renderLastFrameAndUnlock();
|
||||||
|
|
||||||
void renderFrame(AVFrame* frame);
|
void renderFrame(AVFrame* frame);
|
||||||
|
|
||||||
void dropFrameForEnqueue(QQueue<AVFrame*>& queue);
|
void dropFrameForEnqueue(QQueue<AVFrame*>& queue);
|
||||||
@@ -52,5 +54,4 @@ private:
|
|||||||
int m_MaxVideoFps;
|
int m_MaxVideoFps;
|
||||||
int m_DisplayFps;
|
int m_DisplayFps;
|
||||||
PVIDEO_STATS m_VideoStats;
|
PVIDEO_STATS m_VideoStats;
|
||||||
int m_RendererAttributes;
|
|
||||||
};
|
};
|
||||||
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user