Skip to content

Commit e1abe6a

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 e1abe6a

20 files changed

Lines changed: 1894 additions & 149 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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@
1717
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 & 0 deletions
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();

app/cli/commandlineparser.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

app/gui/SettingsView.qml

Lines changed: 48 additions & 0 deletions
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

@@ -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 {

app/settings/streamingpreferences.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#define SER_ABSTOUCHMODE "abstouchmode"
3434
#define SER_STARTWINDOWED "startwindowed"
3535
#define SER_FRAMEPACING "framepacing"
36+
#define SER_VIDEOENHANCEMENT "videoenhancement"
3637
#define SER_CONNWARNINGS "connwarnings"
3738
#define SER_UIDISPLAYMODE "uidisplaymode"
3839
#define SER_RICHPRESENCE "richpresence"
@@ -131,6 +132,7 @@ void StreamingPreferences::reload()
131132
absoluteMouseMode = settings.value(SER_ABSMOUSEMODE, false).toBool();
132133
absoluteTouchMode = settings.value(SER_ABSTOUCHMODE, true).toBool();
133134
framePacing = settings.value(SER_FRAMEPACING, false).toBool();
135+
videoEnhancement = settings.value(SER_VIDEOENHANCEMENT, false).toBool();
134136
connectionWarnings = settings.value(SER_CONNWARNINGS, true).toBool();
135137
richPresence = settings.value(SER_RICHPRESENCE, true).toBool();
136138
gamepadMouse = settings.value(SER_GAMEPADMOUSE, true).toBool();
@@ -321,6 +323,7 @@ void StreamingPreferences::save()
321323
settings.setValue(SER_ABSMOUSEMODE, absoluteMouseMode);
322324
settings.setValue(SER_ABSTOUCHMODE, absoluteTouchMode);
323325
settings.setValue(SER_FRAMEPACING, framePacing);
326+
settings.setValue(SER_VIDEOENHANCEMENT, videoEnhancement);
324327
settings.setValue(SER_CONNWARNINGS, connectionWarnings);
325328
settings.setValue(SER_RICHPRESENCE, richPresence);
326329
settings.setValue(SER_GAMEPADMOUSE, gamepadMouse);

app/settings/streamingpreferences.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ class StreamingPreferences : public QObject
119119
Q_PROPERTY(bool absoluteMouseMode MEMBER absoluteMouseMode NOTIFY absoluteMouseModeChanged)
120120
Q_PROPERTY(bool absoluteTouchMode MEMBER absoluteTouchMode NOTIFY absoluteTouchModeChanged)
121121
Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged)
122+
Q_PROPERTY(bool videoEnhancement MEMBER videoEnhancement NOTIFY videoEnhancementChanged)
122123
Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged)
123124
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged)
124125
Q_PROPERTY(bool gamepadMouse MEMBER gamepadMouse NOTIFY gamepadMouseChanged)
@@ -158,6 +159,7 @@ class StreamingPreferences : public QObject
158159
bool absoluteMouseMode;
159160
bool absoluteTouchMode;
160161
bool framePacing;
162+
bool videoEnhancement;
161163
bool connectionWarnings;
162164
bool richPresence;
163165
bool gamepadMouse;
@@ -202,6 +204,7 @@ class StreamingPreferences : public QObject
202204
void uiDisplayModeChanged();
203205
void windowModeChanged();
204206
void framePacingChanged();
207+
void videoEnhancementChanged();
205208
void connectionWarningsChanged();
206209
void richPresenceChanged();
207210
void gamepadMouseChanged();

app/streaming/session.cpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ void Session::clSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g
261261

262262
bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
263263
SDL_Window* window, int videoFormat, int width, int height,
264-
int frameRate, bool enableVsync, bool enableFramePacing, bool testOnly, IVideoDecoder*& chosenDecoder)
264+
int frameRate, bool enableVsync, bool enableFramePacing, bool enableVideoEnhancement, bool testOnly, IVideoDecoder*& chosenDecoder)
265265
{
266266
DECODER_PARAMETERS params;
267267

@@ -277,6 +277,7 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
277277
params.window = window;
278278
params.enableVsync = enableVsync;
279279
params.enableFramePacing = enableFramePacing;
280+
params.enableVideoEnhancement = enableVideoEnhancement;
280281
params.testOnly = testOnly;
281282
params.vds = vds;
282283

@@ -381,7 +382,7 @@ void Session::getDecoderInfo(SDL_Window* window,
381382
// Try an HEVC Main10 decoder first to see if we have HDR support
382383
if (chooseDecoder(StreamingPreferences::VDS_FORCE_HARDWARE,
383384
window, VIDEO_FORMAT_H265_MAIN10, 1920, 1080, 60,
384-
false, false, true, decoder)) {
385+
false, false, false, true, decoder)) {
385386
isHardwareAccelerated = decoder->isHardwareAccelerated();
386387
isFullScreenOnly = decoder->isAlwaysFullScreen();
387388
isHdrSupported = decoder->isHdrSupported();
@@ -394,7 +395,7 @@ void Session::getDecoderInfo(SDL_Window* window,
394395
// Try an AV1 Main10 decoder next to see if we have HDR support
395396
if (chooseDecoder(StreamingPreferences::VDS_FORCE_HARDWARE,
396397
window, VIDEO_FORMAT_AV1_MAIN10, 1920, 1080, 60,
397-
false, false, true, decoder)) {
398+
false, false, false, true, decoder)) {
398399
// If we've got a working AV1 Main 10-bit decoder, we'll enable the HDR checkbox
399400
// but we will still continue probing to get other attributes for HEVC or H.264
400401
// decoders. See the AV1 comment at the top of the function for more info.
@@ -406,10 +407,10 @@ void Session::getDecoderInfo(SDL_Window* window,
406407
// that supports HDR rendering with software decoded frames.
407408
if (chooseDecoder(StreamingPreferences::VDS_FORCE_SOFTWARE,
408409
window, VIDEO_FORMAT_H265_MAIN10, 1920, 1080, 60,
409-
false, false, true, decoder) ||
410+
false, false, false, true, decoder) ||
410411
chooseDecoder(StreamingPreferences::VDS_FORCE_SOFTWARE,
411412
window, VIDEO_FORMAT_AV1_MAIN10, 1920, 1080, 60,
412-
false, false, true, decoder)) {
413+
false, false, false, true, decoder)) {
413414
isHdrSupported = decoder->isHdrSupported();
414415
delete decoder;
415416
}
@@ -423,7 +424,7 @@ void Session::getDecoderInfo(SDL_Window* window,
423424
// Try a regular hardware accelerated HEVC decoder now
424425
if (chooseDecoder(StreamingPreferences::VDS_FORCE_HARDWARE,
425426
window, VIDEO_FORMAT_H265, 1920, 1080, 60,
426-
false, false, true, decoder)) {
427+
false, false, false, true, decoder)) {
427428
isHardwareAccelerated = decoder->isHardwareAccelerated();
428429
isFullScreenOnly = decoder->isAlwaysFullScreen();
429430
maxResolution = decoder->getDecoderMaxResolution();
@@ -450,7 +451,7 @@ void Session::getDecoderInfo(SDL_Window* window,
450451
// This will fall back to software decoding, so it should always work.
451452
if (chooseDecoder(StreamingPreferences::VDS_AUTO,
452453
window, VIDEO_FORMAT_H264, 1920, 1080, 60,
453-
false, false, true, decoder)) {
454+
false, false, false, true, decoder)) {
454455
isHardwareAccelerated = decoder->isHardwareAccelerated();
455456
isFullScreenOnly = decoder->isAlwaysFullScreen();
456457
maxResolution = decoder->getDecoderMaxResolution();
@@ -470,7 +471,7 @@ Session::getDecoderAvailability(SDL_Window* window,
470471
{
471472
IVideoDecoder* decoder;
472473

473-
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, false, false, true, decoder)) {
474+
if (!chooseDecoder(vds, window, videoFormat, width, height, frameRate, false, false, false, true, decoder)) {
474475
return DecoderAvailability::None;
475476
}
476477

@@ -491,7 +492,7 @@ bool Session::populateDecoderProperties(SDL_Window* window)
491492
m_StreamConfig.width,
492493
m_StreamConfig.height,
493494
m_StreamConfig.fps,
494-
false, false, true, decoder)) {
495+
false, false, false, true, decoder)) {
495496
return false;
496497
}
497498

@@ -2219,6 +2220,7 @@ void Session::execInternal()
22192220
m_ActiveVideoHeight, m_ActiveVideoFrameRate,
22202221
enableVsync,
22212222
enableVsync && m_Preferences->framePacing,
2223+
m_Preferences->videoEnhancement,
22222224
false,
22232225
s_ActiveSession->m_VideoDecoder)) {
22242226
SDL_AtomicUnlock(&m_DecoderLock);

0 commit comments

Comments
 (0)