Skip to content

Commit 71bdbfb

Browse files
committed
Add Video Super Resolution for Windows (AMD, Intel and NVIDIA) and MacOS
Video Super Resolution (VSR) is to Video as DLSS is to 3D Rendering. Why not let Moonlight being one of the first game streaming solution leveraging such technology? AI upscaling means significantly less bandwidth usage without compromising the video quality! Tested with these GPUs: Nvidia RTX 4070 Ti (Windows) AMD RX 7600 (Windows) AMD iGPU 780M (Windows) Intel Arc A380 (Windows) Intel UHD Graphics (16EU), an iGPU from N95 CPU (Windows) M1 Pro (MacOS) M3 Pro (MacOS)
1 parent fabb4fd commit 71bdbfb

20 files changed

Lines changed: 2135 additions & 393 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33

44
**/.vs/
5+
**/.vscode/
56
build/
67
config.tests/*/.qmake.stash
78
config.tests/*/Makefile

.gitmodules

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
[submodule "moonlight-common-c/moonlight-common-c"]
2-
path = moonlight-common-c/moonlight-common-c
2+
path = moonlight-common-c/moonlight-common-c
33
url = https://github.com/moonlight-stream/moonlight-common-c.git
44
[submodule "qmdnsengine/qmdnsengine"]
5-
path = qmdnsengine/qmdnsengine
5+
path = qmdnsengine/qmdnsengine
66
url = https://github.com/cgutman/qmdnsengine.git
77
[submodule "app/SDL_GameControllerDB"]
8-
path = app/SDL_GameControllerDB
8+
path = app/SDL_GameControllerDB
99
url = https://github.com/gabomdq/SDL_GameControllerDB.git
1010
[submodule "soundio/libsoundio"]
11-
path = soundio/libsoundio
11+
path = soundio/libsoundio
1212
url = https://github.com/cgutman/libsoundio.git
1313
[submodule "h264bitstream/h264bitstream"]
14-
path = h264bitstream/h264bitstream
14+
path = h264bitstream/h264bitstream
1515
url = https://github.com/aizvorski/h264bitstream.git
1616
[submodule "libs"]
17-
path = libs
17+
path = libs
1818
url = https://github.com/cgutman/moonlight-qt-prebuilts.git
1919
shallow = true
20+
[submodule "third-party/AMF"]
21+
path = third-party/AMF
22+
url = https://github.com/GPUOpen-LibrariesAndSDKs/AMF.git

app/app.pro

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ macx {
165165
CONFIG += discord-rpc
166166
}
167167

168-
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal -framework QuartzCore
168+
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal -framework MetalFx -framework QuartzCore
169169

170170
# For libsoundio
171171
LIBS += -framework CoreAudio -framework AudioUnit
@@ -211,6 +211,7 @@ SOURCES += \
211211
gui/sdlgamepadkeynavigation.cpp \
212212
streaming/video/overlaymanager.cpp \
213213
backend/systemproperties.cpp \
214+
streaming/video/videoenhancement.cpp \
214215
wm.cpp
215216

216217
HEADERS += \
@@ -220,6 +221,7 @@ HEADERS += \
220221
cli/pair.h \
221222
settings/compatfetcher.h \
222223
settings/mappingfetcher.h \
224+
streaming/video/videoenhancement.h \
223225
utils.h \
224226
backend/computerseeker.h \
225227
backend/identitymanager.h \
@@ -403,6 +405,18 @@ win32:!winrt {
403405
streaming/video/ffmpeg-renderers/d3d11va.h \
404406
streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h
405407
}
408+
win32:!winrt {
409+
message(AMF enabled for AMD Drivers)
410+
411+
SOURCES += \
412+
../third-party/AMF/amf/public/common/AMFFactory.cpp \
413+
../third-party/AMF/amf/public/common/AMFSTL.cpp \
414+
../third-party/AMF/amf/public/common/Thread.cpp \
415+
../third-party/AMF/amf/public/common/TraceAdapter.cpp \
416+
../third-party/AMF/amf/public/common/Windows/ThreadWindows.cpp
417+
418+
INCLUDEPATH += $$PWD/../third-party/AMF/amf
419+
}
406420
macx {
407421
message(VideoToolbox renderer selected)
408422

app/backend/systemproperties.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "streaming/session.h"
88
#include "streaming/streamutils.h"
9+
#include "streaming/video/videoenhancement.h"
910

1011
#ifdef Q_OS_WIN32
1112
#define WIN32_LEAN_AND_MEAN
@@ -248,3 +249,27 @@ void SystemProperties::refreshDisplaysInternal()
248249

249250
SDL_QuitSubSystem(SDL_INIT_VIDEO);
250251
}
252+
253+
/**
254+
* \brief Inform if the GPU is capable of Video enhancement
255+
*
256+
* Check if either Video Super-Resolution or SDR-to-HDR features can be used by the GPU.
257+
*
258+
* \return bool Returns true if the GPU is capable
259+
*/
260+
bool SystemProperties::isVideoEnhancementCapable()
261+
{
262+
VideoEnhancement* videoEnhancement = &VideoEnhancement::getInstance();
263+
return videoEnhancement->isUIvisible() && (videoEnhancement->isVSRcapable() || videoEnhancement->isHDRcapable());
264+
}
265+
266+
/**
267+
* \brief Inform if the GPU's driver is at an experiemental state of Video enhancement implementation
268+
*
269+
* \return bool Returns true if it is experimental yet
270+
*/
271+
bool SystemProperties::isVideoEnhancementExperimental()
272+
{
273+
VideoEnhancement* videoEnhancement = &VideoEnhancement::getInstance();
274+
return videoEnhancement->isExperimental();
275+
}

app/backend/systemproperties.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class SystemProperties : public QObject
3232
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
3333
Q_INVOKABLE QRect getSafeAreaResolution(int displayIndex);
3434
Q_INVOKABLE int getRefreshRate(int displayIndex);
35+
Q_INVOKABLE bool isVideoEnhancementCapable();
36+
Q_INVOKABLE bool isVideoEnhancementExperimental();
3537

3638
signals:
3739
void unmappedGamepadsChanged();
@@ -59,4 +61,3 @@ class SystemProperties : public QObject
5961
bool supportsHdr;
6062
bool usesMaterial3Theme;
6163
};
62-

app/cli/commandlineparser.cpp

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,13 @@ class CommandLineParser : public QCommandLineParser
5959

6060
void showMessage(QString message, MessageType type) const
6161
{
62-
#if defined(Q_OS_WIN32)
62+
#if defined(Q_OS_WIN32)
6363
UINT flags = MB_OK | MB_TOPMOST | MB_SETFOREGROUND;
6464
flags |= (type == Info ? MB_ICONINFORMATION : MB_ICONERROR);
6565
QString title = "Moonlight";
6666
MessageBoxW(nullptr, reinterpret_cast<const wchar_t *>(message.utf16()),
6767
reinterpret_cast<const wchar_t *>(title.utf16()), flags);
68-
#endif
68+
#endif
6969
message = message.endsWith('\n') ? message : message + '\n';
7070
fputs(qPrintable(message), type == Info ? stdout : stderr);
7171
}
@@ -170,7 +170,7 @@ GlobalCommandLineParser::ParseResult GlobalCommandLineParser::parse(const QStrin
170170
" pair Pair a new host\n"
171171
"\n"
172172
"See 'moonlight <action> --help' for help of specific action."
173-
);
173+
);
174174
parser.addPositionalArgument("action", "Action to execute", "<action>");
175175
parser.parse(args);
176176
auto posArgs = parser.positionalArguments();
@@ -221,7 +221,7 @@ void QuitCommandLineParser::parse(const QStringList &args)
221221
parser.setApplicationDescription(
222222
"\n"
223223
"Quit the currently running app on the given host."
224-
);
224+
);
225225
parser.addPositionalArgument("quit", "quit running app");
226226
parser.addPositionalArgument("host", "Host computer name, UUID, or IP address", "<host>");
227227

@@ -263,7 +263,7 @@ void PairCommandLineParser::parse(const QStringList &args)
263263
parser.setApplicationDescription(
264264
"\n"
265265
"Pair with the specified host."
266-
);
266+
);
267267
parser.addPositionalArgument("pair", "pair host");
268268
parser.addPositionalArgument("host", "Host computer name, UUID, or IP address", "<host>");
269269
parser.addValueOption("pin", "4 digit pairing PIN");
@@ -303,31 +303,31 @@ QString PairCommandLineParser::getPredefinedPin() const
303303
StreamCommandLineParser::StreamCommandLineParser()
304304
{
305305
m_WindowModeMap = {
306-
{"fullscreen", StreamingPreferences::WM_FULLSCREEN},
307-
{"windowed", StreamingPreferences::WM_WINDOWED},
308-
{"borderless", StreamingPreferences::WM_FULLSCREEN_DESKTOP},
309-
};
306+
{"fullscreen", StreamingPreferences::WM_FULLSCREEN},
307+
{"windowed", StreamingPreferences::WM_WINDOWED},
308+
{"borderless", StreamingPreferences::WM_FULLSCREEN_DESKTOP},
309+
};
310310
m_AudioConfigMap = {
311-
{"stereo", StreamingPreferences::AC_STEREO},
312-
{"5.1-surround", StreamingPreferences::AC_51_SURROUND},
313-
{"7.1-surround", StreamingPreferences::AC_71_SURROUND},
314-
};
311+
{"stereo", StreamingPreferences::AC_STEREO},
312+
{"5.1-surround", StreamingPreferences::AC_51_SURROUND},
313+
{"7.1-surround", StreamingPreferences::AC_71_SURROUND},
314+
};
315315
m_VideoCodecMap = {
316-
{"auto", StreamingPreferences::VCC_AUTO},
317-
{"H.264", StreamingPreferences::VCC_FORCE_H264},
318-
{"HEVC", StreamingPreferences::VCC_FORCE_HEVC},
319-
{"AV1", StreamingPreferences::VCC_FORCE_AV1},
320-
};
316+
{"auto", StreamingPreferences::VCC_AUTO},
317+
{"H.264", StreamingPreferences::VCC_FORCE_H264},
318+
{"HEVC", StreamingPreferences::VCC_FORCE_HEVC},
319+
{"AV1", StreamingPreferences::VCC_FORCE_AV1},
320+
};
321321
m_VideoDecoderMap = {
322-
{"auto", StreamingPreferences::VDS_AUTO},
323-
{"software", StreamingPreferences::VDS_FORCE_SOFTWARE},
324-
{"hardware", StreamingPreferences::VDS_FORCE_HARDWARE},
325-
};
322+
{"auto", StreamingPreferences::VDS_AUTO},
323+
{"software", StreamingPreferences::VDS_FORCE_SOFTWARE},
324+
{"hardware", StreamingPreferences::VDS_FORCE_HARDWARE},
325+
};
326326
m_CaptureSysKeysModeMap = {
327-
{"never", StreamingPreferences::CSK_OFF},
328-
{"fullscreen", StreamingPreferences::CSK_FULLSCREEN},
329-
{"always", StreamingPreferences::CSK_ALWAYS},
330-
};
327+
{"never", StreamingPreferences::CSK_OFF},
328+
{"fullscreen", StreamingPreferences::CSK_FULLSCREEN},
329+
{"always", StreamingPreferences::CSK_ALWAYS},
330+
};
331331
}
332332

333333
StreamCommandLineParser::~StreamCommandLineParser()
@@ -341,7 +341,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
341341
parser.setApplicationDescription(
342342
"\n"
343343
"Starts directly streaming a given app."
344-
);
344+
);
345345
parser.addPositionalArgument("stream", "Start stream");
346346

347347
// Add other arguments and options
@@ -367,6 +367,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
367367
parser.addToggleOption("game-optimization", "game optimizations");
368368
parser.addToggleOption("audio-on-host", "audio on host PC");
369369
parser.addToggleOption("frame-pacing", "frame pacing");
370+
parser.addToggleOption("video-enhancement", "Enhance video with AI");
370371
parser.addToggleOption("mute-on-focus-loss", "mute audio when Moonlight window loses focus");
371372
parser.addToggleOption("background-gamepad", "background gamepad input");
372373
parser.addToggleOption("reverse-scroll-direction", "inverted scroll direction");
@@ -474,6 +475,9 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
474475
// Resolve --frame-pacing and --no-frame-pacing options
475476
preferences->framePacing = parser.getToggleOptionValue("frame-pacing", preferences->framePacing);
476477

478+
// Resolve --video-enhancement and --no-video-enhancement options
479+
preferences->videoEnhancement = parser.getToggleOptionValue("video-enhancement", preferences->videoEnhancement);
480+
477481
// Resolve --mute-on-focus-loss and --no-mute-on-focus-loss options
478482
preferences->muteOnFocusLoss = parser.getToggleOptionValue("mute-on-focus-loss", preferences->muteOnFocusLoss);
479483

@@ -497,7 +501,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
497501

498502
// Resolve --yuv444 and --no-yuv444 options
499503
preferences->enableYUV444 = parser.getToggleOptionValue("yuv444", preferences->enableYUV444);
500-
504+
501505
// Resolve --capture-system-keys option
502506
if (parser.isSet("capture-system-keys")) {
503507
preferences->captureSysKeysMode = mapValue(m_CaptureSysKeysModeMap, parser.getChoiceOptionValue("capture-system-keys"));
@@ -555,7 +559,7 @@ void ListCommandLineParser::parse(const QStringList &args)
555559
parser.setApplicationDescription(
556560
"\n"
557561
"List the available apps on the given host."
558-
);
562+
);
559563
parser.addPositionalArgument("list", "list available apps");
560564
parser.addPositionalArgument("host", "Host computer name, UUID, or IP address", "<host>");
561565

app/gui/SettingsView.qml

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,44 @@ Flickable {
820820
ToolTip.visible: hovered
821821
ToolTip.text: qsTr("Frame pacing reduces micro-stutter by delaying frames that come in too early")
822822
}
823+
824+
CheckBox {
825+
id: videoEnhancementCheck
826+
width: parent.width
827+
hoverEnabled: true
828+
text: qsTr("Video AI-Enhancement")
829+
font.pointSize: 12
830+
enabled: SystemProperties.isVideoEnhancementCapable()
831+
checked: {
832+
return SystemProperties.isVideoEnhancementCapable() && StreamingPreferences.videoEnhancement
833+
}
834+
property bool keepValue: checked;
835+
onCheckedChanged: {
836+
StreamingPreferences.videoEnhancement = checked
837+
}
838+
ToolTip.delay: 1000
839+
ToolTip.timeout: 5000
840+
ToolTip.visible: hovered
841+
ToolTip.text:
842+
qsTr("Enhance video quality by utilizing the GPU's AI-Enhancement capabilities.")
843+
+ qsTr("\nThis feature effectively upscales, reduces compression artifacts and enhances the clarity of streamed content.")
844+
+ qsTr("\nNote:")
845+
+ qsTr("\n - If available, ensure that appropriate settings (i.e. RTX Video enhancement) are enabled in your GPU driver configuration.")
846+
+ qsTr("\n - HDR rendering has divers issues depending on the GPU used, we are working on it but we advise to currently use Non-HDR.")
847+
+ qsTr("\n - Be advised that using this feature on laptops running on battery power may lead to significant battery drain.")
848+
849+
Component.onCompleted: {
850+
if (!SystemProperties.isVideoEnhancementCapable()){
851+
// VSR or SDR->HDR feature could not be initialized by any GPU available
852+
text = qsTr("Video AI-Enhancement (Not supported by the GPU)")
853+
enabled = false;
854+
checked = false;
855+
} else if(SystemProperties.isVideoEnhancementExperimental()){
856+
// Indicate if the feature is available but not officially deployed by the Vendor
857+
text = qsTr("Video AI-Enhancement (Experimental)")
858+
}
859+
}
860+
}
823861
}
824862
}
825863

@@ -1176,7 +1214,7 @@ Flickable {
11761214
ListElement {
11771215
text: qsTr("Maximized")
11781216
val: StreamingPreferences.UI_MAXIMIZED
1179-
}
1217+
}
11801218
ListElement {
11811219
text: qsTr("Fullscreen")
11821220
val: StreamingPreferences.UI_FULLSCREEN
@@ -1523,6 +1561,16 @@ Flickable {
15231561
StreamingPreferences.videoDecoderSelection = decoderListModel.get(currentIndex).val
15241562
}
15251563
}
1564+
onCurrentIndexChanged: {
1565+
if(decoderListModel.get(currentIndex).val === StreamingPreferences.VDS_FORCE_SOFTWARE){
1566+
videoEnhancementCheck.enabled = false;
1567+
videoEnhancementCheck.keepValue = videoEnhancementCheck.checked;
1568+
videoEnhancementCheck.checked = false;
1569+
} else {
1570+
videoEnhancementCheck.enabled = true;
1571+
videoEnhancementCheck.checked = videoEnhancementCheck.keepValue;
1572+
}
1573+
}
15261574
}
15271575

15281576
Label {

0 commit comments

Comments
 (0)