mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-22 16:17:20 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7a2d9f44d | ||
|
|
ee5d0acc1f | ||
|
|
6a5a69cabd | ||
|
|
cd7391f908 | ||
|
|
d6d28f1afd | ||
|
|
5a6620a9ae | ||
|
|
5a47ddca33 | ||
|
|
b58f021fc8 | ||
|
|
12b93337d1 | ||
|
|
807fc6b31c | ||
|
|
959080aa95 | ||
|
|
18a772aeba | ||
|
|
9bdd17f1c3 | ||
|
|
3d1967bd1e | ||
|
|
469bd32bde | ||
|
|
351b4edf0c | ||
|
|
a0a8b70bc1 | ||
|
|
e07f069222 | ||
|
|
47ccd1e481 | ||
|
|
96fba43186 | ||
|
|
9e7c212c6d | ||
|
|
e67d83e097 | ||
|
|
4474f815af | ||
|
|
e130a15037 | ||
|
|
845ef147c9 | ||
|
|
fb17c15cfd | ||
|
|
bb74cedf59 | ||
|
|
9af6906380 | ||
|
|
c1ae3cceda | ||
|
|
e1e3bd512f | ||
|
|
cf3ff32340 | ||
|
|
2c98deeae3 | ||
|
|
a572681737 | ||
|
|
e78b389bc0 | ||
|
|
83ec929758 | ||
|
|
9117f6565e | ||
|
|
aa33432c81 | ||
|
|
e25d9b0da2 | ||
|
|
0dfa8a7b53 | ||
|
|
85a9f85c54 | ||
|
|
ed68f920f1 | ||
|
|
f9a4498f9b | ||
|
|
4d1acf2e30 | ||
|
|
db4739b82d | ||
|
|
38bf833708 | ||
|
|
364093aef8 | ||
|
|
6d220a9062 | ||
|
|
abec8b3fe1 | ||
|
|
93ed985043 | ||
|
|
23c737cab9 | ||
|
|
cb850f013a | ||
|
|
6bd964a094 | ||
|
|
419c788ab5 | ||
|
|
a412100a11 | ||
|
|
0531666f38 | ||
|
|
c096238998 | ||
|
|
211fec0ff0 | ||
|
|
58e27875ae | ||
|
|
954e6608a1 | ||
|
|
1cd588f25c | ||
|
|
ebfe035a18 | ||
|
|
6f22ed13c1 | ||
|
|
4aa2b8570c | ||
|
|
fe6562e31c | ||
|
|
654e386263 | ||
|
|
76d0eb6b63 | ||
|
|
02d867fe09 | ||
|
|
76ff53f09f | ||
|
|
37d92dee16 | ||
|
|
afbc49e39a | ||
|
|
d1ccd19fcc | ||
|
|
ebe270bec5 | ||
|
|
784234c857 | ||
|
|
6a85996b4a | ||
|
|
c6710f93dc | ||
|
|
5e46c4ec2b | ||
|
|
011feab6ce | ||
|
|
f8c5d3c0ce | ||
|
|
74d452bf35 | ||
|
|
fd2ff61a40 | ||
|
|
8a87a09947 | ||
|
|
cf544a8703 | ||
|
|
0e1190a059 | ||
|
|
f2535f1e6e | ||
|
|
cefa567f95 | ||
|
|
9be782ae8e | ||
|
|
610e07d4bd | ||
|
|
75eb921a04 | ||
|
|
97bd515b11 | ||
|
|
a45695a22a | ||
|
|
0d67fd1ff0 | ||
|
|
a093a0ae59 | ||
|
|
c9ad8ffa69 | ||
|
|
9987914ebb | ||
|
|
fbfd6fb462 | ||
|
|
bec053e3cf | ||
|
|
d364b24378 | ||
|
|
8c67586cd0 | ||
|
|
89a628a0dd | ||
|
|
4fb9681b7f | ||
|
|
c13128ca1d | ||
|
|
ceca83346b | ||
|
|
fc6c51c75a | ||
|
|
161d91acf8 | ||
|
|
06ef10a2ad | ||
|
|
7b29b22192 | ||
|
|
289a7165b3 | ||
|
|
62011c37f4 | ||
|
|
919e44b29b | ||
|
|
32be1b158b | ||
|
|
f635860afd | ||
|
|
3b078f095b | ||
|
|
72fc7ea31a | ||
|
|
f4edd1e63a | ||
|
|
6011384afe | ||
|
|
896456a8ac | ||
|
|
8959ddbeb6 | ||
|
|
767215fce5 | ||
|
|
3ef05f3a35 | ||
|
|
12624bc5c4 | ||
|
|
5bd0dea85d | ||
|
|
80242f2e90 | ||
|
|
cf3e328f9d | ||
|
|
150002d4d4 | ||
|
|
b972a22459 | ||
|
|
db06239018 | ||
|
|
bdc7d53515 | ||
|
|
7f83b79c19 | ||
|
|
47b3aff352 | ||
|
|
679f237c1b | ||
|
|
3d8de56f25 | ||
|
|
801cebbbc7 | ||
|
|
66fb0465c4 | ||
|
|
8bbc9b2c57 | ||
|
|
be7694dd8b | ||
|
|
ce8b24dfa7 | ||
|
|
75ddac54c5 | ||
|
|
b01a83ff39 | ||
|
|
c3e886fbcc | ||
|
|
e20d56041e | ||
|
|
114074bfd8 | ||
|
|
e44e29e7eb | ||
|
|
03663d5552 | ||
|
|
dce2a857f7 | ||
|
|
61f71a318e | ||
|
|
646ce2bf66 | ||
|
|
2bbae90e2c | ||
|
|
bb8e229814 | ||
|
|
9ed8252cc6 | ||
|
|
2cef09471b | ||
|
|
2d15382e31 | ||
|
|
770872238d | ||
|
|
2a05b890d8 | ||
|
|
481f23b6e9 | ||
|
|
4e7107a09e | ||
|
|
0223394725 | ||
|
|
058739321c | ||
|
|
708bec2937 | ||
|
|
c939537c5c | ||
|
|
9497657c83 | ||
|
|
d4050f3a4f | ||
|
|
c74c4ef041 | ||
|
|
d75663052d | ||
|
|
8f2db99406 | ||
|
|
6a58dbb05e | ||
|
|
8aeb976128 | ||
|
|
7d51a4b67d | ||
|
|
f7d412e3bf | ||
|
|
4469c7fae3 | ||
|
|
ee95efc0f1 | ||
|
|
3ddc8ad68a | ||
|
|
333d97160e | ||
|
|
aac5ad3fd0 | ||
|
|
1ef083c952 | ||
|
|
c63b918bb2 | ||
|
|
4dba4ad538 | ||
|
|
83fcad6bd1 | ||
|
|
e08178474d | ||
|
|
f374e88bac | ||
|
|
3cb169ea26 | ||
|
|
58f87e39a9 | ||
|
|
b1c8005d1a | ||
|
|
f8a2e22a18 | ||
|
|
7ea118fbae | ||
|
|
2149024c35 | ||
|
|
f5d71c8c9a | ||
|
|
e108a05895 | ||
|
|
ea8bccd508 | ||
|
|
bca919c48c | ||
|
|
511ffe03b5 |
No files matched your search
@@ -37,12 +37,13 @@ List the games you've tried that exhibit the issue. To see if the issue is game-
|
|||||||
- OS: [e.g. Windows 10 1809]
|
- OS: [e.g. Windows 10 1809]
|
||||||
- Moonlight Version: [e.g. v0.9.0]
|
- Moonlight Version: [e.g. v0.9.0]
|
||||||
- GPU: [e.g. Intel HD Graphics 520]
|
- GPU: [e.g. Intel HD Graphics 520]
|
||||||
|
- Linux package type (if applicable): [e.g. Flatpak]
|
||||||
|
|
||||||
**Server PC details (please complete the following information)**
|
**Server PC details (please complete the following information)**
|
||||||
- OS: [e.g. Windows 10 1809]
|
- OS: [e.g. Windows 10 1809]
|
||||||
- GeForce Experience version: [e.g. 3.16.0.140]
|
- Sunshine or GeForce Experience version: [e.g. Sunshine v0.21.0]
|
||||||
- Nvidia GPU driver: [e.g. 417.35]
|
- GPU: [e.g. AMD Radeon RX 7900 XT]
|
||||||
- Antivirus and firewall software: [e.g. Windows Defender and Windows Firewall]
|
- GPU driver: [e.g. 24.1.1]
|
||||||
|
|
||||||
**Moonlight Logs (please attach)**
|
**Moonlight Logs (please attach)**
|
||||||
- On Windows, `Moonlight-###.log` files can be found in `%TEMP%`. Simply type that into the File Explorer path field to navigate there.
|
- On Windows, `Moonlight-###.log` files can be found in `%TEMP%`. Simply type that into the File Explorer path field to navigate there.
|
||||||
|
|||||||
@@ -158,17 +158,6 @@ private:
|
|||||||
L"EZFRD32.dll",
|
L"EZFRD32.dll",
|
||||||
L"EZFRD64.dll",
|
L"EZFRD64.dll",
|
||||||
|
|
||||||
// These are the newer dList DLLs for Optimus hybrid graphics DDI.
|
|
||||||
// https://docs.microsoft.com/en-us/windows-hardware/drivers/display/hybrid-system-ddi
|
|
||||||
//
|
|
||||||
// We forcefully block them from loading because Optimus has a bug that
|
|
||||||
// deadlocks DXVA2 when we present with D3DPRESENT_DONOTWAIT. This will prevent
|
|
||||||
// Optimus from ever using the dGPU even if the user has requested it.
|
|
||||||
// https://github.com/moonlight-stream/moonlight-qt/issues/240
|
|
||||||
// https://github.com/moonlight-stream/moonlight-qt/issues/235
|
|
||||||
L"nvdlist.dll",
|
|
||||||
L"nvdlistx.dll",
|
|
||||||
|
|
||||||
// These are the older dList/AppInit DLLs for Optimus hybrid graphics DDI.
|
// These are the older dList/AppInit DLLs for Optimus hybrid graphics DDI.
|
||||||
// https://docs.microsoft.com/en-us/windows-hardware/drivers/display/hybrid-system-ddi
|
// https://docs.microsoft.com/en-us/windows-hardware/drivers/display/hybrid-system-ddi
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -26,36 +26,37 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
|
|||||||
- [Flatpak (for other Linux distros)](https://flathub.org/apps/details/com.moonlight_stream.Moonlight)
|
- [Flatpak (for other Linux distros)](https://flathub.org/apps/details/com.moonlight_stream.Moonlight)
|
||||||
- [AppImage](https://github.com/moonlight-stream/moonlight-qt/releases)
|
- [AppImage](https://github.com/moonlight-stream/moonlight-qt/releases)
|
||||||
- [Raspberry Pi 4](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-Raspberry-Pi-4)
|
- [Raspberry Pi 4](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-Raspberry-Pi-4)
|
||||||
|
- [Generic ARM SBC packages for Debian](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-ARM%E2%80%90based-Single-Board-Computers) (not for Raspberry Pi)
|
||||||
|
- [Experimental RISC-V SBC packages for Debian](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-RISC%E2%80%90V-Single-Board-Computers)
|
||||||
- [NVIDIA Jetson and Nintendo Switch (Ubuntu L4T)](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-Linux4Tegra-(L4T)-Ubuntu)
|
- [NVIDIA Jetson and Nintendo Switch (Ubuntu L4T)](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-Linux4Tegra-(L4T)-Ubuntu)
|
||||||
|
|
||||||
#### Special Thanks
|
#### Special Thanks
|
||||||
|
|
||||||
[](https://cloudsmith.com)
|
[](https://cloudsmith.com)
|
||||||
|
|
||||||
Hosting for Moonlight's Raspberry Pi and L4T package repositories is graciously provided for free by [Cloudsmith](https://cloudsmith.com).
|
Hosting for Moonlight's Debian and L4T package repositories is graciously provided for free by [Cloudsmith](https://cloudsmith.com).
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
### Windows Build Requirements
|
### Windows Build Requirements
|
||||||
* Qt 5.15 SDK or later
|
* Qt 5.15 SDK or later. Qt 6 is also supported for x64 and ARM64 builds.
|
||||||
* Windows 7 or later
|
|
||||||
* [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) (Community edition is fine)
|
* [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) (Community edition is fine)
|
||||||
* Select **MSVC** option during Qt installation. MinGW is not supported.
|
* Select **MSVC** option during Qt installation. MinGW is not supported.
|
||||||
* [7-Zip](https://www.7-zip.org/) (only if building installers for non-development PCs)
|
* [7-Zip](https://www.7-zip.org/) (only if building installers for non-development PCs)
|
||||||
|
|
||||||
### macOS Build Requirements
|
### macOS Build Requirements
|
||||||
* Qt 6.2 SDK or later
|
* Qt 6.4 SDK or later
|
||||||
* macOS Mojave (10.14) or later
|
|
||||||
* Xcode 13 or later
|
* Xcode 13 or later
|
||||||
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
|
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
|
||||||
|
|
||||||
### Linux/Unix Build Requirements
|
### Linux/Unix Build Requirements
|
||||||
* Qt 5.9 SDK or later
|
* Qt 5.9 SDK or later. Qt 6 is also supported.
|
||||||
* GCC or Clang
|
* GCC or Clang
|
||||||
* Install the required packages:
|
* Install the required packages:
|
||||||
* Debian/Ubuntu: `libegl1-mesa-dev libgl1-mesa-dev libopus-dev libqt5svg5-dev libsdl2-dev libsdl2-ttf-dev libssl-dev libavcodec-dev 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 libavformat-dev libva-dev libvdpau-dev libxkbcommon-dev qtwayland5 qt5-qmake qtbase5-dev qtdeclarative5-dev qtquickcontrols2-5-dev wayland-protocols qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtquick-window2 qml-module-qtquick2`
|
||||||
* RedHat/Fedora: `openssl-devel SDL2-devel SDL2_ttf-devel ffmpeg-devel qt5-qtsvg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel`
|
* RedHat/Fedora: `openssl-devel SDL2-devel SDL2_ttf-devel ffmpeg-devel qt5-qtsvg-devel qt5-qtquickcontrols2-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel`
|
||||||
* FFmpeg 4.0 or later is required to build. If your distro doesn't package FFmpeg 4.0 or later, you can build and install it from source on https://ffmpeg.org/
|
* FFmpeg 4.0 or later is required to build. If your distro doesn't package FFmpeg 4.0 or later, you can build and install it from source on https://ffmpeg.org/
|
||||||
|
* Building the Vulkan HDR renderer requires a `libplacebo-dev`/`libplacebo-devel` version of at least v338.0 and FFmpeg 6.1 or later.
|
||||||
|
|
||||||
### Steam Link Build Requirements
|
### Steam Link Build Requirements
|
||||||
* [Steam Link SDK](https://github.com/ValveSoftware/steamlink-sdk) cloned on your build system
|
* [Steam Link SDK](https://github.com/ValveSoftware/steamlink-sdk) cloned on your build system
|
||||||
@@ -65,7 +66,7 @@ Hosting for Moonlight's Raspberry Pi and L4T package repositories is graciously
|
|||||||
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
|
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
|
||||||
* You can install Qt via Homebrew on macOS, but you will need to use `brew install qt --with-debug` to be able to create debug builds of Moonlight.
|
* You can install Qt via Homebrew on macOS, but you will need to use `brew install qt --with-debug` to be able to create debug builds of Moonlight.
|
||||||
* You may also use your Linux distro's package manager for the Qt SDK as long as the packages are Qt 5.9 or later.
|
* You may also use your Linux distro's package manager for the Qt SDK as long as the packages are Qt 5.9 or later.
|
||||||
* This step is not required for building on Steam Link, because the Steam Link SDK includes Qt 5.9.
|
* This step is not required for building on Steam Link, because the Steam Link SDK includes Qt 5.14.
|
||||||
2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
|
2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
|
||||||
3. Open the project in Qt Creator or build from qmake on the command line.
|
3. Open the project in Qt Creator or build from qmake on the command line.
|
||||||
* To build a binary for use on non-development machines, use the scripts in the `scripts` folder.
|
* To build a binary for use on non-development machines, use the scripts in the `scripts` folder.
|
||||||
@@ -75,7 +76,7 @@ Hosting for Moonlight's Raspberry Pi and L4T package repositories is graciously
|
|||||||
* To build from the command line for development use on macOS or Linux, run `qmake moonlight-qt.pro` then `make debug` or `make release`
|
* To build from the command line for development use on macOS or Linux, run `qmake moonlight-qt.pro` then `make debug` or `make release`
|
||||||
* To create an embedded build for a single-purpose device, use `qmake "CONFIG+=embedded" moonlight-qt.pro` and build normally.
|
* To create an embedded build for a single-purpose device, use `qmake "CONFIG+=embedded" moonlight-qt.pro` and build normally.
|
||||||
* This build will lack windowed mode, Discord/Help links, and other features that don't make sense on an embedded device.
|
* This build will lack windowed mode, Discord/Help links, and other features that don't make sense on an embedded device.
|
||||||
* For platforms with poor GL performance, add `"CONFIG+=glslow"` to prefer direct KMSDRM rendering over EGL/GLES renderers. Direct KMSDRM rendering can use dedicated YUV/RGB conversion and scaling hardware rather than slower GPU shaders for these operations.
|
* For platforms with poor GPU performance, add `"CONFIG+=gpuslow"` to prefer direct KMSDRM rendering over GL/Vulkan renderers. Direct KMSDRM rendering can use dedicated YUV/RGB conversion and scaling hardware rather than slower GPU shaders for these operations.
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
1. Fork us
|
1. Fork us
|
||||||
|
|||||||
@@ -31,8 +31,6 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>GCSupportsControllerUserInteraction</key>
|
<key>GCSupportsControllerUserInteraction</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>LSApplicationCategoryType</key>
|
|
||||||
<string>public.app-category.games</string>
|
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
|||||||
+1
-1
Submodule app/SDL_GameControllerDB updated: dbcf31a670...e5a5fa2ac6.
+38
-6
@@ -120,6 +120,13 @@ unix:!macx {
|
|||||||
CONFIG += cuda
|
CONFIG += cuda
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
!disable-libplacebo {
|
||||||
|
packagesExist(libplacebo) {
|
||||||
|
PKGCONFIG += libplacebo
|
||||||
|
CONFIG += libplacebo
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
!disable-wayland {
|
!disable-wayland {
|
||||||
@@ -145,8 +152,8 @@ win32:!winrt {
|
|||||||
CONFIG += soundio discord-rpc
|
CONFIG += soundio discord-rpc
|
||||||
}
|
}
|
||||||
macx {
|
macx {
|
||||||
LIBS += -lssl -lcrypto -lavcodec.60 -lavutil.58 -lopus -framework SDL2 -framework SDL2_ttf
|
LIBS += -lssl -lcrypto -lavcodec.61 -lavutil.59 -lopus -framework SDL2 -framework SDL2_ttf
|
||||||
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal
|
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal -framework QuartzCore
|
||||||
|
|
||||||
# For libsoundio
|
# For libsoundio
|
||||||
LIBS += -framework CoreAudio -framework AudioUnit
|
LIBS += -framework CoreAudio -framework AudioUnit
|
||||||
@@ -267,7 +274,7 @@ libva-wayland {
|
|||||||
PKGCONFIG += libva-wayland
|
PKGCONFIG += libva-wayland
|
||||||
DEFINES += HAVE_LIBVA_WAYLAND
|
DEFINES += HAVE_LIBVA_WAYLAND
|
||||||
}
|
}
|
||||||
libva-wayland {
|
libva-drm {
|
||||||
message(VAAPI DRM support enabled)
|
message(VAAPI DRM support enabled)
|
||||||
|
|
||||||
PKGCONFIG += libva-drm
|
PKGCONFIG += libva-drm
|
||||||
@@ -291,7 +298,7 @@ mmal {
|
|||||||
# significantly better performance than EGL on the Pi. Setting
|
# significantly better performance than EGL on the Pi. Setting
|
||||||
# this option allows EGL usage even if built with MMAL support.
|
# this option allows EGL usage even if built with MMAL support.
|
||||||
#
|
#
|
||||||
# It is highly recommended to also build with 'glslow' to avoid
|
# It is highly recommended to also build with 'gpuslow' to avoid
|
||||||
# EGL being preferred if direct DRM rendering is available.
|
# EGL being preferred if direct DRM rendering is available.
|
||||||
allow-egl-with-mmal {
|
allow-egl-with-mmal {
|
||||||
message(Allowing EGL usage with MMAL enabled)
|
message(Allowing EGL usage with MMAL enabled)
|
||||||
@@ -318,6 +325,19 @@ cuda {
|
|||||||
DEFINES += HAVE_CUDA
|
DEFINES += HAVE_CUDA
|
||||||
SOURCES += streaming/video/ffmpeg-renderers/cuda.cpp
|
SOURCES += streaming/video/ffmpeg-renderers/cuda.cpp
|
||||||
HEADERS += streaming/video/ffmpeg-renderers/cuda.h
|
HEADERS += streaming/video/ffmpeg-renderers/cuda.h
|
||||||
|
|
||||||
|
# ffnvcodec uses libdl in cuda_load_functions()/cuda_free_functions()
|
||||||
|
LIBS += -ldl
|
||||||
|
}
|
||||||
|
libplacebo {
|
||||||
|
message(Vulkan support enabled via libplacebo)
|
||||||
|
|
||||||
|
DEFINES += HAVE_LIBPLACEBO_VULKAN
|
||||||
|
SOURCES += \
|
||||||
|
streaming/video/ffmpeg-renderers/plvk.cpp \
|
||||||
|
streaming/video/ffmpeg-renderers/plvk_c.c
|
||||||
|
HEADERS += \
|
||||||
|
streaming/video/ffmpeg-renderers/plvk.h
|
||||||
}
|
}
|
||||||
config_EGL {
|
config_EGL {
|
||||||
message(EGL renderer selected)
|
message(EGL renderer selected)
|
||||||
@@ -365,7 +385,8 @@ macx {
|
|||||||
message(VideoToolbox renderer selected)
|
message(VideoToolbox renderer selected)
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
streaming/video/ffmpeg-renderers/vt.mm
|
streaming/video/ffmpeg-renderers/vt_avsamplelayer.mm \
|
||||||
|
streaming/video/ffmpeg-renderers/vt_metal.mm
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
streaming/video/ffmpeg-renderers/vt.h
|
streaming/video/ffmpeg-renderers/vt.h
|
||||||
@@ -393,6 +414,16 @@ glslow {
|
|||||||
|
|
||||||
DEFINES += GL_IS_SLOW
|
DEFINES += GL_IS_SLOW
|
||||||
}
|
}
|
||||||
|
vkslow {
|
||||||
|
message(Vulkan slow build)
|
||||||
|
|
||||||
|
DEFINES += VULKAN_IS_SLOW
|
||||||
|
}
|
||||||
|
gpuslow {
|
||||||
|
message(GPU slow build)
|
||||||
|
|
||||||
|
DEFINES += GL_IS_SLOW VULKAN_IS_SLOW
|
||||||
|
}
|
||||||
wayland {
|
wayland {
|
||||||
message(Wayland extensions enabled)
|
message(Wayland extensions enabled)
|
||||||
|
|
||||||
@@ -429,7 +460,8 @@ TRANSLATIONS += \
|
|||||||
languages/qml_pt_BR.ts \
|
languages/qml_pt_BR.ts \
|
||||||
languages/qml_pl.ts \
|
languages/qml_pl.ts \
|
||||||
languages/qml_cs.ts \
|
languages/qml_cs.ts \
|
||||||
languages/qml_he.ts
|
languages/qml_he.ts \
|
||||||
|
languages/qml_ckb.ts
|
||||||
|
|
||||||
# Additional import path used to resolve QML modules in Qt Creator's code model
|
# Additional import path used to resolve QML modules in Qt Creator's code model
|
||||||
QML_IMPORT_PATH =
|
QML_IMPORT_PATH =
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "computermanager.h"
|
#include "computermanager.h"
|
||||||
#include "boxartmanager.h"
|
#include "boxartmanager.h"
|
||||||
#include "nvhttp.h"
|
#include "nvhttp.h"
|
||||||
#include "settings/streamingpreferences.h"
|
#include "nvpairingmanager.h"
|
||||||
|
|
||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
#include <QtEndian>
|
#include <QtEndian>
|
||||||
@@ -144,8 +144,8 @@ private:
|
|||||||
NvComputer* m_Computer;
|
NvComputer* m_Computer;
|
||||||
};
|
};
|
||||||
|
|
||||||
ComputerManager::ComputerManager(QObject *parent)
|
ComputerManager::ComputerManager(StreamingPreferences* prefs)
|
||||||
: QObject(parent),
|
: m_Prefs(prefs),
|
||||||
m_PollingRef(0),
|
m_PollingRef(0),
|
||||||
m_MdnsBrowser(nullptr),
|
m_MdnsBrowser(nullptr),
|
||||||
m_CompatFetcher(nullptr),
|
m_CompatFetcher(nullptr),
|
||||||
@@ -352,9 +352,7 @@ void ComputerManager::startPolling()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamingPreferences prefs;
|
if (m_Prefs->enableMdns) {
|
||||||
|
|
||||||
if (prefs.enableMdns) {
|
|
||||||
// Start an MDNS query for GameStream hosts
|
// Start an MDNS query for GameStream hosts
|
||||||
m_MdnsServer.reset(new QMdnsEngine::Server());
|
m_MdnsServer.reset(new QMdnsEngine::Server());
|
||||||
m_MdnsBrowser = new QMdnsEngine::Browser(m_MdnsServer.data(), "_nvstream._tcp.local.");
|
m_MdnsBrowser = new QMdnsEngine::Browser(m_MdnsServer.data(), "_nvstream._tcp.local.");
|
||||||
@@ -784,10 +782,9 @@ private:
|
|||||||
return serverInfo;
|
return serverInfo;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
if (!m_Mdns) {
|
if (!m_Mdns) {
|
||||||
StreamingPreferences prefs;
|
unsigned int portTestResult;
|
||||||
int portTestResult;
|
|
||||||
|
|
||||||
if (prefs.detectNetworkBlocking) {
|
if (m_ComputerManager->m_Prefs->detectNetworkBlocking) {
|
||||||
// We failed to connect to the specified PC. Let's test to make sure this network
|
// We failed to connect to the specified PC. Let's test to make sure this network
|
||||||
// isn't blocking Moonlight, so we can tell the user about it.
|
// isn't blocking Moonlight, so we can tell the user about it.
|
||||||
portTestResult = LiTestClientConnectivity("qt.conntest.moonlight-stream.org", 443,
|
portTestResult = LiTestClientConnectivity("qt.conntest.moonlight-stream.org", 443,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "nvcomputer.h"
|
#include "nvcomputer.h"
|
||||||
#include "nvpairingmanager.h"
|
#include "settings/streamingpreferences.h"
|
||||||
#include "settings/compatfetcher.h"
|
#include "settings/compatfetcher.h"
|
||||||
|
|
||||||
#include <qmdnsengine/server.h>
|
#include <qmdnsengine/server.h>
|
||||||
@@ -72,9 +72,15 @@ private slots:
|
|||||||
void handleResolvedTimeout()
|
void handleResolvedTimeout()
|
||||||
{
|
{
|
||||||
if (m_Addresses.isEmpty()) {
|
if (m_Addresses.isEmpty()) {
|
||||||
// Try again
|
if (m_Retries-- > 0) {
|
||||||
qInfo() << "Resolving" << hostname() << "timed out. Retrying...";
|
// Try again
|
||||||
resolve();
|
qInfo() << "Resolving" << hostname() << "timed out. Retrying...";
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning() << "Giving up on resolving" << hostname() << "after repeated failures";
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Q_ASSERT(!m_Addresses.isEmpty());
|
Q_ASSERT(!m_Addresses.isEmpty());
|
||||||
@@ -92,7 +98,7 @@ signals:
|
|||||||
void resolvedHost(MdnsPendingComputer*,QVector<QHostAddress>&);
|
void resolvedHost(MdnsPendingComputer*,QVector<QHostAddress>&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void resolve()
|
void cleanup()
|
||||||
{
|
{
|
||||||
// Delete our resolver, so we're guaranteed that nothing is referencing m_Server.
|
// Delete our resolver, so we're guaranteed that nothing is referencing m_Server.
|
||||||
delete m_Resolver;
|
delete m_Resolver;
|
||||||
@@ -101,6 +107,12 @@ private:
|
|||||||
// Now delete our strong reference that we held on behalf of m_Resolver.
|
// Now delete our strong reference that we held on behalf of m_Resolver.
|
||||||
// The server may be destroyed after we make this call.
|
// The server may be destroyed after we make this call.
|
||||||
m_Server.reset();
|
m_Server.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void resolve()
|
||||||
|
{
|
||||||
|
// Clean up any existing resolver object and server references
|
||||||
|
cleanup();
|
||||||
|
|
||||||
// Re-acquire a strong reference if the server still exists.
|
// Re-acquire a strong reference if the server still exists.
|
||||||
m_Server = m_ServerWeak.toStrongRef();
|
m_Server = m_ServerWeak.toStrongRef();
|
||||||
@@ -120,6 +132,7 @@ private:
|
|||||||
QSharedPointer<QMdnsEngine::Server> m_Server;
|
QSharedPointer<QMdnsEngine::Server> m_Server;
|
||||||
QMdnsEngine::Resolver* m_Resolver;
|
QMdnsEngine::Resolver* m_Resolver;
|
||||||
QVector<QHostAddress> m_Addresses;
|
QVector<QHostAddress> m_Addresses;
|
||||||
|
int m_Retries = 10;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ComputerPollingEntry
|
class ComputerPollingEntry
|
||||||
@@ -205,7 +218,7 @@ class ComputerManager : public QObject
|
|||||||
friend class DelayedFlushThread;
|
friend class DelayedFlushThread;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ComputerManager(QObject *parent = nullptr);
|
explicit ComputerManager(StreamingPreferences* prefs);
|
||||||
|
|
||||||
virtual ~ComputerManager();
|
virtual ~ComputerManager();
|
||||||
|
|
||||||
@@ -257,6 +270,7 @@ private:
|
|||||||
|
|
||||||
void startPollingComputer(NvComputer* computer);
|
void startPollingComputer(NvComputer* computer);
|
||||||
|
|
||||||
|
StreamingPreferences* m_Prefs;
|
||||||
int m_PollingRef;
|
int m_PollingRef;
|
||||||
QReadWriteLock m_Lock;
|
QReadWriteLock m_Lock;
|
||||||
QMap<QString, NvComputer*> m_KnownHosts;
|
QMap<QString, NvComputer*> m_KnownHosts;
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ ComputerSeeker::ComputerSeeker(ComputerManager *manager, QString computerName, Q
|
|||||||
: QObject(parent), m_ComputerManager(manager), m_ComputerName(computerName),
|
: QObject(parent), m_ComputerManager(manager), m_ComputerName(computerName),
|
||||||
m_TimeoutTimer(new QTimer(this))
|
m_TimeoutTimer(new QTimer(this))
|
||||||
{
|
{
|
||||||
|
// If we know this computer, send a WOL packet to wake it up in case it is asleep.
|
||||||
|
for (NvComputer * computer: m_ComputerManager->getComputers()) {
|
||||||
|
if (this->matchComputer(computer)) {
|
||||||
|
computer->wake();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_TimeoutTimer->setSingleShot(true);
|
m_TimeoutTimer->setSingleShot(true);
|
||||||
connect(m_TimeoutTimer, &QTimer::timeout,
|
connect(m_TimeoutTimer, &QTimer::timeout,
|
||||||
this, &ComputerSeeker::onTimeout);
|
this, &ComputerSeeker::onTimeout);
|
||||||
|
|||||||
@@ -373,6 +373,7 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
|
|||||||
s.connectToHost(copyOfActiveAddress.address(), copyOfActiveAddress.port());
|
s.connectToHost(copyOfActiveAddress.address(), copyOfActiveAddress.port());
|
||||||
if (s.waitForConnected(3000)) {
|
if (s.waitForConnected(3000)) {
|
||||||
Q_ASSERT(!s.localAddress().isNull());
|
Q_ASSERT(!s.localAddress().isNull());
|
||||||
|
Q_ASSERT(!s.peerAddress().isNull());
|
||||||
|
|
||||||
for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) {
|
for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) {
|
||||||
// Ensure the interface is up
|
// Ensure the interface is up
|
||||||
@@ -406,6 +407,19 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
|
|||||||
return ReachabilityType::RI_VPN;
|
return ReachabilityType::RI_VPN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_WINDOWS
|
||||||
|
if (nic.name().startsWith("iftype53_") || nic.name().startsWith("iftype131_")) {
|
||||||
|
// Match by NDIS interface type. These values are Microsoft's recommended values for VPN connections:
|
||||||
|
// https://learn.microsoft.com/en-US/troubleshoot/windows-client/networking/windows-connection-manager-disconnects-wlan#more-information
|
||||||
|
//
|
||||||
|
// The following VPNs use IF_TYPE_PROP_VIRTUAL under Windows:
|
||||||
|
// - WireguardNT VPNs
|
||||||
|
// - All WinTun-based VPNs (such as Slack Nebula)
|
||||||
|
// - OpenVPN with tap-windows6
|
||||||
|
return ReachabilityType::RI_VPN;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (nic.hardwareAddress().startsWith("00:FF", Qt::CaseInsensitive)) {
|
if (nic.hardwareAddress().startsWith("00:FF", Qt::CaseInsensitive)) {
|
||||||
// OpenVPN TAP interfaces have a MAC address starting with 00:FF on Windows
|
// OpenVPN TAP interfaces have a MAC address starting with 00:FF on Windows
|
||||||
return ReachabilityType::RI_VPN;
|
return ReachabilityType::RI_VPN;
|
||||||
@@ -421,9 +435,9 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
|
|||||||
return ReachabilityType::RI_VPN;
|
return ReachabilityType::RI_VPN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Didn't meet any of our VPN heuristics. Let's see if it's on-link.
|
// Didn't meet any of our VPN heuristics. Let's see if the peer address is on-link.
|
||||||
Q_ASSERT(addr.prefixLength() >= 0);
|
Q_ASSERT(addr.prefixLength() >= 0);
|
||||||
if (addr.prefixLength() >= 0 && s.localAddress().isInSubnet(addr.ip(), addr.prefixLength())) {
|
if (addr.prefixLength() >= 0 && s.localAddress().isInSubnet(s.peerAddress(), addr.prefixLength())) {
|
||||||
return ReachabilityType::RI_LAN;
|
return ReachabilityType::RI_LAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -223,7 +223,8 @@ NvHTTP::startApp(QString verb,
|
|||||||
"&surroundAudioInfo="+QString::number(SURROUNDAUDIOINFO_FROM_AUDIO_CONFIGURATION(streamConfig->audioConfiguration))+
|
"&surroundAudioInfo="+QString::number(SURROUNDAUDIOINFO_FROM_AUDIO_CONFIGURATION(streamConfig->audioConfiguration))+
|
||||||
"&remoteControllersBitmap="+QString::number(gamepadMask)+
|
"&remoteControllersBitmap="+QString::number(gamepadMask)+
|
||||||
"&gcmap="+QString::number(gamepadMask)+
|
"&gcmap="+QString::number(gamepadMask)+
|
||||||
"&gcpersist="+QString::number(persistGameControllersOnDisconnect ? 1 : 0),
|
"&gcpersist="+QString::number(persistGameControllersOnDisconnect ? 1 : 0)+
|
||||||
|
LiGetLaunchUrlQueryParameters(),
|
||||||
LAUNCH_TIMEOUT_MS);
|
LAUNCH_TIMEOUT_MS);
|
||||||
|
|
||||||
qInfo() << "Launch response:" << response;
|
qInfo() << "Launch response:" << response;
|
||||||
|
|||||||
@@ -298,8 +298,8 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
|
|||||||
}
|
}
|
||||||
|
|
||||||
QByteArray pairingSecret = NvHTTP::getXmlStringFromHex(respXml, "pairingsecret");
|
QByteArray pairingSecret = NvHTTP::getXmlStringFromHex(respXml, "pairingsecret");
|
||||||
QByteArray serverSecret = QByteArray(pairingSecret.data(), 16);
|
QByteArray serverSecret = pairingSecret.left(16);
|
||||||
QByteArray serverSignature = QByteArray(&pairingSecret.data()[16], 256);
|
QByteArray serverSignature = pairingSecret.mid(16);
|
||||||
|
|
||||||
if (!verifySignature(serverSecret,
|
if (!verifySignature(serverSecret,
|
||||||
serverSignature,
|
serverSignature,
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ SystemProperties::SystemProperties()
|
|||||||
|
|
||||||
Q_ASSERT(!monitorRefreshRates.isEmpty());
|
Q_ASSERT(!monitorRefreshRates.isEmpty());
|
||||||
Q_ASSERT(!monitorNativeResolutions.isEmpty());
|
Q_ASSERT(!monitorNativeResolutions.isEmpty());
|
||||||
|
Q_ASSERT(!monitorSafeAreaResolutions.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
QRect SystemProperties::getNativeResolution(int displayIndex)
|
QRect SystemProperties::getNativeResolution(int displayIndex)
|
||||||
@@ -82,6 +83,12 @@ QRect SystemProperties::getNativeResolution(int displayIndex)
|
|||||||
return monitorNativeResolutions.value(displayIndex);
|
return monitorNativeResolutions.value(displayIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QRect SystemProperties::getSafeAreaResolution(int displayIndex)
|
||||||
|
{
|
||||||
|
// Returns default constructed QRect if out of bounds
|
||||||
|
return monitorSafeAreaResolutions.value(displayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
int SystemProperties::getRefreshRate(int displayIndex)
|
int SystemProperties::getRefreshRate(int displayIndex)
|
||||||
{
|
{
|
||||||
// Returns 0 if out of bounds
|
// Returns 0 if out of bounds
|
||||||
@@ -199,10 +206,12 @@ void SystemProperties::refreshDisplaysInternal()
|
|||||||
SDL_DisplayMode bestMode;
|
SDL_DisplayMode bestMode;
|
||||||
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
|
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
|
||||||
SDL_DisplayMode desktopMode;
|
SDL_DisplayMode desktopMode;
|
||||||
|
SDL_Rect safeArea;
|
||||||
|
|
||||||
if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode)) {
|
if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode, &safeArea)) {
|
||||||
if (desktopMode.w <= 8192 && desktopMode.h <= 8192) {
|
if (desktopMode.w <= 8192 && desktopMode.h <= 8192) {
|
||||||
monitorNativeResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
|
monitorNativeResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
|
||||||
|
monitorSafeAreaResolutions.insert(displayIndex, QRect(0, 0, safeArea.w, safeArea.h));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void refreshDisplays();
|
Q_INVOKABLE void refreshDisplays();
|
||||||
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
|
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
|
||||||
|
Q_INVOKABLE QRect getSafeAreaResolution(int displayIndex);
|
||||||
Q_INVOKABLE int getRefreshRate(int displayIndex);
|
Q_INVOKABLE int getRefreshRate(int displayIndex);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -52,6 +53,7 @@ private:
|
|||||||
QString unmappedGamepads;
|
QString unmappedGamepads;
|
||||||
QSize maximumResolution;
|
QSize maximumResolution;
|
||||||
QList<QRect> monitorNativeResolutions;
|
QList<QRect> monitorNativeResolutions;
|
||||||
|
QList<QRect> monitorSafeAreaResolutions;
|
||||||
QList<int> monitorRefreshRates;
|
QList<int> monitorRefreshRates;
|
||||||
QString versionString;
|
QString versionString;
|
||||||
bool supportsHdr;
|
bool supportsHdr;
|
||||||
|
|||||||
@@ -316,6 +316,7 @@ StreamCommandLineParser::StreamCommandLineParser()
|
|||||||
{"auto", StreamingPreferences::VCC_AUTO},
|
{"auto", StreamingPreferences::VCC_AUTO},
|
||||||
{"H.264", StreamingPreferences::VCC_FORCE_H264},
|
{"H.264", StreamingPreferences::VCC_FORCE_H264},
|
||||||
{"HEVC", StreamingPreferences::VCC_FORCE_HEVC},
|
{"HEVC", StreamingPreferences::VCC_FORCE_HEVC},
|
||||||
|
{"AV1", StreamingPreferences::VCC_FORCE_AV1},
|
||||||
};
|
};
|
||||||
m_VideoDecoderMap = {
|
m_VideoDecoderMap = {
|
||||||
{"auto", StreamingPreferences::VDS_AUTO},
|
{"auto", StreamingPreferences::VDS_AUTO},
|
||||||
@@ -371,6 +372,8 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
|||||||
parser.addToggleOption("reverse-scroll-direction", "inverted scroll direction");
|
parser.addToggleOption("reverse-scroll-direction", "inverted scroll direction");
|
||||||
parser.addToggleOption("swap-gamepad-buttons", "swap A/B and X/Y gamepad buttons (Nintendo-style)");
|
parser.addToggleOption("swap-gamepad-buttons", "swap A/B and X/Y gamepad buttons (Nintendo-style)");
|
||||||
parser.addToggleOption("keep-awake", "prevent display sleep while streaming");
|
parser.addToggleOption("keep-awake", "prevent display sleep while streaming");
|
||||||
|
parser.addToggleOption("performance-overlay", "show performance overlay");
|
||||||
|
parser.addToggleOption("hdr", "HDR streaming");
|
||||||
parser.addChoiceOption("capture-system-keys", "capture system key combos", m_CaptureSysKeysModeMap.keys());
|
parser.addChoiceOption("capture-system-keys", "capture system key combos", m_CaptureSysKeysModeMap.keys());
|
||||||
parser.addChoiceOption("video-codec", "video codec", m_VideoCodecMap.keys());
|
parser.addChoiceOption("video-codec", "video codec", m_VideoCodecMap.keys());
|
||||||
parser.addChoiceOption("video-decoder", "video decoder", m_VideoDecoderMap.keys());
|
parser.addChoiceOption("video-decoder", "video decoder", m_VideoDecoderMap.keys());
|
||||||
@@ -485,6 +488,12 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
|||||||
// Resolve --keep-awake and --no-keep-awake options
|
// Resolve --keep-awake and --no-keep-awake options
|
||||||
preferences->keepAwake = parser.getToggleOptionValue("keep-awake", preferences->keepAwake);
|
preferences->keepAwake = parser.getToggleOptionValue("keep-awake", preferences->keepAwake);
|
||||||
|
|
||||||
|
// Resolve --performance-overlay and --no-performance-overlay options
|
||||||
|
preferences->showPerformanceOverlay = parser.getToggleOptionValue("performance-overlay", preferences->showPerformanceOverlay);
|
||||||
|
|
||||||
|
// Resolve --hdr and --no-hdr options
|
||||||
|
preferences->enableHdr = parser.getToggleOptionValue("hdr", preferences->enableHdr);
|
||||||
|
|
||||||
// Resolve --capture-system-keys option
|
// Resolve --capture-system-keys option
|
||||||
if (parser.isSet("capture-system-keys")) {
|
if (parser.isSet("capture-system-keys")) {
|
||||||
preferences->captureSysKeysMode = mapValue(m_CaptureSysKeysModeMap, parser.getChoiceOptionValue("capture-system-keys"));
|
preferences->captureSysKeysMode = mapValue(m_CaptureSysKeysModeMap, parser.getChoiceOptionValue("capture-system-keys"));
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
From 78261eb6e2fe728fa5d3843b5b8962b1ca559b7e Mon Sep 17 00:00:00 2001
|
||||||
|
From: David Rosca <nowrep@gmail.com>
|
||||||
|
Date: Sat, 16 Dec 2023 14:52:01 +0100
|
||||||
|
Subject: [PATCH] Vulkan: Don't try to reuse old swapchain
|
||||||
|
|
||||||
|
---
|
||||||
|
src/vulkan/swapchain.c | 12 ++++++++++--
|
||||||
|
1 file changed, 10 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/vulkan/swapchain.c b/src/vulkan/swapchain.c
|
||||||
|
index 0646e57c..c0b07ce4 100644
|
||||||
|
--- a/src/vulkan/swapchain.c
|
||||||
|
+++ b/src/vulkan/swapchain.c
|
||||||
|
@@ -612,13 +612,21 @@ static bool vk_sw_recreate(pl_swapchain sw, int w, int h)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
+ {
|
||||||
|
+ pl_gpu_flush(gpu);
|
||||||
|
+ vk_wait_idle(vk);
|
||||||
|
+ for (int i = 0; i < vk->pool_graphics->num_queues; i++)
|
||||||
|
+ vk->QueueWaitIdle(vk->pool_graphics->queues[i]);
|
||||||
|
+ vk->DestroySwapchainKHR(vk->dev, p->swapchain, PL_VK_ALLOC);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
// Calling `vkCreateSwapchainKHR` puts sinfo.oldSwapchain into a retired
|
||||||
|
// state whether the call succeeds or not, so we always need to garbage
|
||||||
|
// collect it afterwards - asynchronously as it may still be in use
|
||||||
|
- sinfo.oldSwapchain = p->swapchain;
|
||||||
|
+ /* sinfo.oldSwapchain = p->swapchain; */
|
||||||
|
p->swapchain = VK_NULL_HANDLE;
|
||||||
|
VkResult res = vk->CreateSwapchainKHR(vk->dev, &sinfo, PL_VK_ALLOC, &p->swapchain);
|
||||||
|
- vk_dev_callback(vk, VK_CB_FUNC(destroy_swapchain), vk, vk_wrap_handle(sinfo.oldSwapchain));
|
||||||
|
+ /* vk_dev_callback(vk, VK_CB_FUNC(destroy_swapchain), vk, vk_wrap_handle(sinfo.oldSwapchain)); */
|
||||||
|
PL_VK_ASSERT(res, "vk->CreateSwapchainKHR(...)");
|
||||||
|
|
||||||
|
// Get the new swapchain images
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -34,6 +34,53 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="6.0.0" date="2024-06-06">
|
||||||
|
<description>
|
||||||
|
<p>New features:</p>
|
||||||
|
<ul>
|
||||||
|
<li>HDR is now supported on Steam Deck and other HDR-capable Linux systems using a new Vulkan-based renderer</li>
|
||||||
|
<li>Vulkan Video decoding of H.264, HEVC, and AV1 is now supported using the new Vulkan renderer</li>
|
||||||
|
<li>Full E2E stream encryption is now supported when streaming from Sunshine v0.22 or later</li>
|
||||||
|
<li>Initiating a stream via the command-line will now automatically wake the target machine if it is sleeping</li>
|
||||||
|
<li>Error codes are now displayed in the connection termination dialog</li>
|
||||||
|
<li>Detailed PC state information is now available via the new "View Details" context menu option (similar to the Android client)</li>
|
||||||
|
<li>The performance overlay can now be configured to be shown by default when starting a stream</li>
|
||||||
|
</ul>
|
||||||
|
<p>Behavior Changes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>The selected bitrate is no longer silently adjusted by Moonlight based upon the video codec or HDR mode</li>
|
||||||
|
<li>The selected mouse mode when starting a stream no longer influences the acceleration curve when the mouse mode is toggled using the Ctrl+Alt+Shift+M key combo while streaming</li>
|
||||||
|
<li>Hardcoded fallback paths for VAAPI and VDPAU drivers are no longer used</li>
|
||||||
|
<li>Per-session keys (rikey and rikeyid values) are now automatically stripped from logs</li>
|
||||||
|
<li>Log messages will now be logged to stderr rather than stdout</li>
|
||||||
|
<li>EGL is now always used instead of GLX for loading OpenGL in X11 environments on non-x86 platforms</li>
|
||||||
|
</ul>
|
||||||
|
<p>Bugfixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Improved reliability of streaming during momentary drops of WiFi connections</li>
|
||||||
|
<li>Improved performance with high polling rate mice</li>
|
||||||
|
<li>Improved input latency when a gamepad is connected</li>
|
||||||
|
<li>Added missing command-line options to control HDR and AV1 codec usage</li>
|
||||||
|
<li>Fixed instant connection termination error when streaming over the Internet behind certain Carrier-Grade NAT devices</li>
|
||||||
|
<li>Fixed incorrect MTU detection when streaming over the Internet without a VPN</li>
|
||||||
|
<li>Fixed recovery of audio when no audio output device is present when the stream starts</li>
|
||||||
|
<li>Fixed merging of gamepad state when multi-controller mode is disabled</li>
|
||||||
|
<li>Fixed indefinitely resending mDNS queries when a host could not be resolved</li>
|
||||||
|
<li>Fixed inability to pair when the host is not using a RSA-2048 key</li>
|
||||||
|
<li>Updated included gamepad mappings</li>
|
||||||
|
<li>Updated community-contributed translations from Weblate</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="5.0.1" date="2023-11-02">
|
||||||
|
<description>
|
||||||
|
<p>Bugfixes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Fixed connection issue streaming from some hosts running GeForce Experience or Sunshine v0.20 and earlier</li>
|
||||||
|
<li>Updated community-contributed translations from Weblate</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="5.0.0" date="2023-10-18">
|
<release version="5.0.0" date="2023-10-18">
|
||||||
<description>
|
<description>
|
||||||
<p>New features:</p>
|
<p>New features:</p>
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
# pairing data will be lost each reboot.
|
# pairing data will be lost each reboot.
|
||||||
HOME=/usr/local/moonlight
|
HOME=/usr/local/moonlight
|
||||||
|
|
||||||
|
# Enable Steam Controller detection in SDL
|
||||||
|
# https://github.com/moonlight-stream/moonlight-qt/issues/697
|
||||||
|
export SDL_ENABLE_STEAM_CONTROLLERS=1
|
||||||
|
|
||||||
# Renice PE_Single_CPU which seems to host A/V stuff
|
# Renice PE_Single_CPU which seems to host A/V stuff
|
||||||
renice -10 -p $(pidof PE_Single_CPU)
|
renice -10 -p $(pidof PE_Single_CPU)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -256,7 +256,7 @@ CenteredGridView {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.RightButton;
|
acceptedButtons: Qt.RightButton;
|
||||||
onClicked: {
|
onClicked: {
|
||||||
parent.onPressAndHold()
|
parent.pressAndHold()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,7 +350,7 @@ CenteredGridView {
|
|||||||
|
|
||||||
function quitApp() {
|
function quitApp() {
|
||||||
var component = Qt.createComponent("QuitSegue.qml")
|
var component = Qt.createComponent("QuitSegue.qml")
|
||||||
var params = {"appName": appName, "quitRunningAppFn": appModel.quitRunningApp}
|
var params = {"appName": appName, "quitRunningAppFn": () => appModel.quitRunningApp()}
|
||||||
if (segueToStream) {
|
if (segueToStream) {
|
||||||
// Store the session and app name if we're going to stream after
|
// Store the session and app name if we're going to stream after
|
||||||
// successfully quitting the old app.
|
// successfully quitting the old app.
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ Item {
|
|||||||
|
|
||||||
function quitApp() {
|
function quitApp() {
|
||||||
var component = Qt.createComponent("QuitSegue.qml")
|
var component = Qt.createComponent("QuitSegue.qml")
|
||||||
var params = {"appName": appName, "quitRunningAppFn": launcher.quitRunningApp}
|
var params = {"appName": appName, "quitRunningAppFn": () => launcher.quitRunningApp()}
|
||||||
stackView.push(component.createObject(stackView, params))
|
stackView.push(component.createObject(stackView, params))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-1
@@ -6,6 +6,7 @@ import ComputerModel 1.0
|
|||||||
|
|
||||||
import ComputerManager 1.0
|
import ComputerManager 1.0
|
||||||
import StreamingPreferences 1.0
|
import StreamingPreferences 1.0
|
||||||
|
import SystemProperties 1.0
|
||||||
import SdlGamepadKeyNavigation 1.0
|
import SdlGamepadKeyNavigation 1.0
|
||||||
|
|
||||||
CenteredGridView {
|
CenteredGridView {
|
||||||
@@ -215,6 +216,14 @@ CenteredGridView {
|
|||||||
deletePcDialog.open()
|
deletePcDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
NavigableMenuItem {
|
||||||
|
parentMenu: pcContextMenu
|
||||||
|
text: qsTr("View Details")
|
||||||
|
onTriggered: {
|
||||||
|
showPcDetailsDialog.pcDetails = model.details
|
||||||
|
showPcDetailsDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +271,7 @@ CenteredGridView {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.RightButton;
|
acceptedButtons: Qt.RightButton;
|
||||||
onClicked: {
|
onClicked: {
|
||||||
parent.onPressAndHold()
|
parent.pressAndHold()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,5 +404,13 @@ CenteredGridView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NavigableMessageDialog {
|
||||||
|
id: showPcDetailsDialog
|
||||||
|
property string pcDetails : "";
|
||||||
|
text: showPcDetailsDialog.pcDetails
|
||||||
|
imageSrc: "qrc:/res/baseline-help_outline-24px.svg"
|
||||||
|
standardButtons: Dialog.Ok
|
||||||
|
}
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {}
|
ScrollBar.vertical: ScrollBar {}
|
||||||
}
|
}
|
||||||
+60
-52
@@ -133,15 +133,45 @@ Flickable {
|
|||||||
AutoResizingComboBox {
|
AutoResizingComboBox {
|
||||||
property int lastIndexValue
|
property int lastIndexValue
|
||||||
|
|
||||||
|
function addDetectedResolution(friendlyNamePrefix, rect) {
|
||||||
|
var indexToAdd = 0
|
||||||
|
for (var j = 0; j < resolutionComboBox.count; j++) {
|
||||||
|
var existing_width = parseInt(resolutionListModel.get(j).video_width);
|
||||||
|
var existing_height = parseInt(resolutionListModel.get(j).video_height);
|
||||||
|
|
||||||
|
if (rect.width === existing_width && rect.height === existing_height) {
|
||||||
|
// Duplicate entry, skip
|
||||||
|
indexToAdd = -1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
else if (rect.width * rect.height > existing_width * existing_height) {
|
||||||
|
// Candidate entrypoint after this entry
|
||||||
|
indexToAdd = j + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert this display's resolution if it's not a duplicate
|
||||||
|
if (indexToAdd >= 0) {
|
||||||
|
resolutionListModel.insert(indexToAdd,
|
||||||
|
{
|
||||||
|
"text": friendlyNamePrefix+" ("+rect.width+"x"+rect.height+")",
|
||||||
|
"video_width": ""+rect.width,
|
||||||
|
"video_height": ""+rect.height,
|
||||||
|
"is_custom": false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ignore setting the index at first, and actually set it when the component is loaded
|
// ignore setting the index at first, and actually set it when the component is loaded
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// Refresh display data before using it to build the list
|
// Refresh display data before using it to build the list
|
||||||
SystemProperties.refreshDisplays()
|
SystemProperties.refreshDisplays()
|
||||||
|
|
||||||
// Add native resolutions for all attached displays
|
// Add native and safe area 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);
|
var screenRect = SystemProperties.getNativeResolution(displayIndex);
|
||||||
|
var safeAreaRect = SystemProperties.getSafeAreaResolution(displayIndex);
|
||||||
|
|
||||||
if (screenRect.width === 0) {
|
if (screenRect.width === 0) {
|
||||||
// Exceeded max count of displays
|
// Exceeded max count of displays
|
||||||
@@ -149,32 +179,8 @@ Flickable {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
var indexToAdd = 0
|
addDetectedResolution(qsTr("Native"), screenRect)
|
||||||
for (var j = 0; j < resolutionComboBox.count; j++) {
|
addDetectedResolution(qsTr("Native (Excluding Notch)"), safeAreaRect)
|
||||||
var existing_width = parseInt(resolutionListModel.get(j).video_width);
|
|
||||||
var existing_height = parseInt(resolutionListModel.get(j).video_height);
|
|
||||||
|
|
||||||
if (screenRect.width === existing_width && screenRect.height === existing_height) {
|
|
||||||
// Duplicate entry, skip
|
|
||||||
indexToAdd = -1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
else if (screenRect.width * screenRect.height > existing_width * existing_height) {
|
|
||||||
// Candidate entrypoint after this entry
|
|
||||||
indexToAdd = j + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert this display's resolution if it's not a duplicate
|
|
||||||
if (indexToAdd >= 0) {
|
|
||||||
resolutionListModel.insert(indexToAdd,
|
|
||||||
{
|
|
||||||
"text": "Native ("+screenRect.width+"x"+screenRect.height+")",
|
|
||||||
"video_width": ""+screenRect.width,
|
|
||||||
"video_height": ""+screenRect.height,
|
|
||||||
"is_custom": false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prune resolutions that are over the decoder's maximum
|
// Prune resolutions that are over the decoder's maximum
|
||||||
@@ -210,7 +216,7 @@ Flickable {
|
|||||||
if (!index_set) {
|
if (!index_set) {
|
||||||
// We did not find a match. This must be a custom resolution.
|
// We did not find a match. This must be a custom resolution.
|
||||||
resolutionListModel.append({
|
resolutionListModel.append({
|
||||||
"text": "Custom ("+StreamingPreferences.width+"x"+StreamingPreferences.height+")",
|
"text": qsTr("Custom")+" ("+StreamingPreferences.width+"x"+StreamingPreferences.height+")",
|
||||||
"video_width": ""+StreamingPreferences.width,
|
"video_width": ""+StreamingPreferences.width,
|
||||||
"video_height": ""+StreamingPreferences.height,
|
"video_height": ""+StreamingPreferences.height,
|
||||||
"is_custom": true
|
"is_custom": true
|
||||||
@@ -219,7 +225,7 @@ Flickable {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
resolutionListModel.append({
|
resolutionListModel.append({
|
||||||
"text": "Custom",
|
"text": qsTr("Custom"),
|
||||||
"video_width": "",
|
"video_width": "",
|
||||||
"video_height": "",
|
"video_height": "",
|
||||||
"is_custom": true
|
"is_custom": true
|
||||||
@@ -689,7 +695,7 @@ Flickable {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// Refresh the text after translations change
|
// Refresh the text after translations change
|
||||||
languageChanged.connect(onValueChanged)
|
languageChanged.connect(valueChanged)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1052,6 +1058,10 @@ Flickable {
|
|||||||
text: "עִבְרִית" // Hebrew
|
text: "עִבְרִית" // Hebrew
|
||||||
val: StreamingPreferences.LANG_HE
|
val: StreamingPreferences.LANG_HE
|
||||||
} */
|
} */
|
||||||
|
/* ListElement {
|
||||||
|
text: "کرمانجیی خواروو" // Central Kurdish
|
||||||
|
val: StreamingPreferences.LANG_CKB
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||||
onActivated : {
|
onActivated : {
|
||||||
@@ -1349,15 +1359,7 @@ Flickable {
|
|||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: StreamingPreferences.swapFaceButtons
|
checked: StreamingPreferences.swapFaceButtons
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
// Check if the value changed (this is called on init too)
|
StreamingPreferences.swapFaceButtons = checked
|
||||||
if (StreamingPreferences.swapFaceButtons !== checked) {
|
|
||||||
StreamingPreferences.swapFaceButtons = checked
|
|
||||||
|
|
||||||
// Save and restart SdlGamepadKeyNavigation so it can pull the new value
|
|
||||||
StreamingPreferences.save()
|
|
||||||
SdlGamepadKeyNavigation.disable()
|
|
||||||
SdlGamepadKeyNavigation.enable()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
@@ -1621,10 +1623,6 @@ Flickable {
|
|||||||
if (StreamingPreferences.enableMdns != checked) {
|
if (StreamingPreferences.enableMdns != checked) {
|
||||||
StreamingPreferences.enableMdns = checked
|
StreamingPreferences.enableMdns = checked
|
||||||
|
|
||||||
// We must save the updated preference to ensure
|
|
||||||
// ComputerManager can observe the change internally.
|
|
||||||
StreamingPreferences.save()
|
|
||||||
|
|
||||||
// Restart polling so the mDNS change takes effect
|
// Restart polling so the mDNS change takes effect
|
||||||
if (window.pollingActive) {
|
if (window.pollingActive) {
|
||||||
ComputerManager.stopPollingAsync()
|
ComputerManager.stopPollingAsync()
|
||||||
@@ -1641,17 +1639,27 @@ Flickable {
|
|||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
checked: StreamingPreferences.detectNetworkBlocking
|
checked: StreamingPreferences.detectNetworkBlocking
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
// This is called on init, so only do the work if we've
|
StreamingPreferences.detectNetworkBlocking = checked
|
||||||
// actually changed the value.
|
|
||||||
if (StreamingPreferences.detectNetworkBlocking != checked) {
|
|
||||||
StreamingPreferences.detectNetworkBlocking = checked
|
|
||||||
|
|
||||||
// We must save the updated preference to ensure
|
|
||||||
// ComputerManager can observe the change internally.
|
|
||||||
StreamingPreferences.save()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: showPerformanceOverlay
|
||||||
|
width: parent.width
|
||||||
|
text: qsTr("Show performance stats while streaming")
|
||||||
|
font.pointSize: 12
|
||||||
|
checked: StreamingPreferences.showPerformanceOverlay
|
||||||
|
onCheckedChanged: {
|
||||||
|
StreamingPreferences.showPerformanceOverlay = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 5000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Display real-time stream performance information while streaming.") + "\n\n" +
|
||||||
|
qsTr("You can toggle it at any time while streaming using Ctrl+Alt+Shift+S or Select+L1+R1+X.") + "\n\n" +
|
||||||
|
qsTr("The performance overlay is not supported on Steam Link or Raspberry Pi.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ Item {
|
|||||||
gc()
|
gc()
|
||||||
|
|
||||||
// Run the streaming session to completion
|
// Run the streaming session to completion
|
||||||
session.exec(Screen.virtualX, Screen.virtualY)
|
session.exec(Window.window)
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceComponent: Item {}
|
sourceComponent: Item {}
|
||||||
|
|||||||
@@ -42,6 +42,46 @@ QVariant ComputerModel::data(const QModelIndex& index, int role) const
|
|||||||
return computer->state == NvComputer::CS_UNKNOWN;
|
return computer->state == NvComputer::CS_UNKNOWN;
|
||||||
case ServerSupportedRole:
|
case ServerSupportedRole:
|
||||||
return computer->isSupportedServerVersion;
|
return computer->isSupportedServerVersion;
|
||||||
|
case DetailsRole: {
|
||||||
|
QString state, pairState;
|
||||||
|
|
||||||
|
switch (computer->state) {
|
||||||
|
case NvComputer::CS_ONLINE:
|
||||||
|
state = tr("Online");
|
||||||
|
break;
|
||||||
|
case NvComputer::CS_OFFLINE:
|
||||||
|
state = tr("Offline");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
state = tr("Unknown");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (computer->pairState) {
|
||||||
|
case NvComputer::PS_PAIRED:
|
||||||
|
pairState = tr("Paired");
|
||||||
|
break;
|
||||||
|
case NvComputer::PS_NOT_PAIRED:
|
||||||
|
pairState = tr("Unpaired");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pairState = tr("Unknown");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tr("Name: %1").arg(computer->name) + '\n' +
|
||||||
|
tr("Status: %1").arg(state) + '\n' +
|
||||||
|
tr("Active Address: %1").arg(computer->activeAddress.toString()) + '\n' +
|
||||||
|
tr("UUID: %1").arg(computer->uuid) + '\n' +
|
||||||
|
tr("Local Address: %1").arg(computer->localAddress.toString()) + '\n' +
|
||||||
|
tr("Remote Address: %1").arg(computer->remoteAddress.toString()) + '\n' +
|
||||||
|
tr("IPv6 Address: %1").arg(computer->ipv6Address.toString()) + '\n' +
|
||||||
|
tr("Manual Address: %1").arg(computer->manualAddress.toString()) + '\n' +
|
||||||
|
tr("MAC Address: %1").arg(computer->macAddress.isEmpty() ? tr("Unknown") : QString(computer->macAddress.toHex(':'))) + '\n' +
|
||||||
|
tr("Pair State: %1").arg(pairState) + '\n' +
|
||||||
|
tr("Running Game ID: %1").arg(computer->state == NvComputer::CS_ONLINE ? QString::number(computer->currentGameId) : tr("Unknown")) + '\n' +
|
||||||
|
tr("HTTPS Port: %1").arg(computer->state == NvComputer::CS_ONLINE ? QString::number(computer->activeHttpsPort) : tr("Unknown"));
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
@@ -69,6 +109,7 @@ QHash<int, QByteArray> ComputerModel::roleNames() const
|
|||||||
names[WakeableRole] = "wakeable";
|
names[WakeableRole] = "wakeable";
|
||||||
names[StatusUnknownRole] = "statusUnknown";
|
names[StatusUnknownRole] = "statusUnknown";
|
||||||
names[ServerSupportedRole] = "serverSupported";
|
names[ServerSupportedRole] = "serverSupported";
|
||||||
|
names[DetailsRole] = "details";
|
||||||
|
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ class ComputerModel : public QAbstractListModel
|
|||||||
BusyRole,
|
BusyRole,
|
||||||
WakeableRole,
|
WakeableRole,
|
||||||
StatusUnknownRole,
|
StatusUnknownRole,
|
||||||
ServerSupportedRole
|
ServerSupportedRole,
|
||||||
|
DetailsRole
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
+33
-46
@@ -19,26 +19,48 @@ ApplicationWindow {
|
|||||||
property bool clearOnBack: false
|
property bool clearOnBack: false
|
||||||
|
|
||||||
id: window
|
id: window
|
||||||
visible: true
|
|
||||||
width: 1280
|
width: 1280
|
||||||
height: 600
|
height: 600
|
||||||
|
|
||||||
// Override the background color to Material 2 colors for Qt 6.5+
|
|
||||||
// in order to improve contrast between GFE's placeholder box art
|
|
||||||
// and the background of the app grid.
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
// Override the background color to Material 2 colors for Qt 6.5+
|
||||||
|
// in order to improve contrast between GFE's placeholder box art
|
||||||
|
// and the background of the app grid.
|
||||||
if (SystemProperties.usesMaterial3Theme) {
|
if (SystemProperties.usesMaterial3Theme) {
|
||||||
Material.background = "#303030"
|
Material.background = "#303030"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
visibility: {
|
// Show the window according to the user's preferences
|
||||||
if (SystemProperties.hasDesktopEnvironment) {
|
if (SystemProperties.hasDesktopEnvironment) {
|
||||||
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_WINDOWED) return "Windowed"
|
if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) {
|
||||||
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_MAXIMIZED) return "Maximized"
|
window.showMaximized()
|
||||||
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_FULLSCREEN) return "FullScreen"
|
}
|
||||||
|
else if (StreamingPreferences.uiDisplayMode == StreamingPreferences.UI_FULLSCREEN) {
|
||||||
|
window.showFullScreen()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.show()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return "FullScreen"
|
window.showFullScreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display any modal dialogs for configuration warnings
|
||||||
|
if (SystemProperties.isWow64) {
|
||||||
|
wow64Dialog.open()
|
||||||
|
}
|
||||||
|
else if (!SystemProperties.hasHardwareAcceleration) {
|
||||||
|
if (SystemProperties.isRunningXWayland) {
|
||||||
|
xWaylandDialog.open()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
noHwDecoderDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SystemProperties.unmappedGamepads) {
|
||||||
|
unmappedGamepadDialog.unmappedGamepads = SystemProperties.unmappedGamepads
|
||||||
|
unmappedGamepadDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,41 +178,6 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property bool initialized: false
|
|
||||||
|
|
||||||
// BUG: Using onAfterSynchronizing: here causes very strange
|
|
||||||
// failures on Linux. Many shaders fail to compile and we
|
|
||||||
// eventually segfault deep inside the Qt OpenGL code.
|
|
||||||
onAfterRendering: {
|
|
||||||
// We use this callback to trigger dialog display because
|
|
||||||
// it only happens once the window is fully constructed.
|
|
||||||
// Doing it earlier can lead to the dialog appearing behind
|
|
||||||
// the window or otherwise without input focus.
|
|
||||||
if (!initialized) {
|
|
||||||
// Set initialized before calling anything else, because
|
|
||||||
// pumping the event loop can cause us to get another
|
|
||||||
// onAfterRendering call and potentially reenter this code.
|
|
||||||
initialized = true;
|
|
||||||
|
|
||||||
if (SystemProperties.isWow64) {
|
|
||||||
wow64Dialog.open()
|
|
||||||
}
|
|
||||||
else if (!SystemProperties.hasHardwareAcceleration) {
|
|
||||||
if (SystemProperties.isRunningXWayland) {
|
|
||||||
xWaylandDialog.open()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
noHwDecoderDialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SystemProperties.unmappedGamepads) {
|
|
||||||
unmappedGamepadDialog.unmappedGamepads = SystemProperties.unmappedGamepads
|
|
||||||
unmappedGamepadDialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround for lack of instanceof in Qt 5.9.
|
// Workaround for lack of instanceof in Qt 5.9.
|
||||||
//
|
//
|
||||||
// Based on https://stackoverflow.com/questions/13923794/how-to-do-a-is-a-typeof-or-instanceof-in-qml
|
// Based on https://stackoverflow.com/questions/13923794/how-to-do-a-is-a-typeof-or-instanceof-in-qml
|
||||||
@@ -283,7 +270,7 @@ ApplicationWindow {
|
|||||||
visible: SystemProperties.hasBrowser &&
|
visible: SystemProperties.hasBrowser &&
|
||||||
qmltypeof(stackView.currentItem, "SettingsView")
|
qmltypeof(stackView.currentItem, "SettingsView")
|
||||||
|
|
||||||
iconSource: "qrc:/res/Discord-Logo-White.svg"
|
iconSource: "qrc:/res/discord.svg"
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
ToolTip.timeout: 3000
|
ToolTip.timeout: 3000
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
|
|
||||||
#include "settings/streamingpreferences.h"
|
|
||||||
#include "settings/mappingmanager.h"
|
#include "settings/mappingmanager.h"
|
||||||
|
|
||||||
#define AXIS_NAVIGATION_REPEAT_DELAY 150
|
#define AXIS_NAVIGATION_REPEAT_DELAY 150
|
||||||
|
|
||||||
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation()
|
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation(StreamingPreferences* prefs)
|
||||||
: m_Enabled(false),
|
: m_Prefs(prefs),
|
||||||
|
m_Enabled(false),
|
||||||
m_UiNavMode(false),
|
m_UiNavMode(false),
|
||||||
m_FirstPoll(false),
|
m_FirstPoll(false),
|
||||||
m_LastAxisNavigationEventTime(0)
|
m_LastAxisNavigationEventTime(0)
|
||||||
@@ -93,7 +93,6 @@ void SdlGamepadKeyNavigation::disable()
|
|||||||
void SdlGamepadKeyNavigation::onPollingTimerFired()
|
void SdlGamepadKeyNavigation::onPollingTimerFired()
|
||||||
{
|
{
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
StreamingPreferences prefs;
|
|
||||||
|
|
||||||
// Discard any pending button events on the first poll to avoid picking up
|
// Discard any pending button events on the first poll to avoid picking up
|
||||||
// stale input data from the stream session (like the quit combo).
|
// stale input data from the stream session (like the quit combo).
|
||||||
@@ -121,7 +120,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
QEvent::Type::KeyPress : QEvent::Type::KeyRelease;
|
QEvent::Type::KeyPress : QEvent::Type::KeyRelease;
|
||||||
|
|
||||||
// Swap face buttons if needed
|
// Swap face buttons if needed
|
||||||
if (prefs.swapFaceButtons) {
|
if (m_Prefs->swapFaceButtons) {
|
||||||
switch (event.cbutton.button) {
|
switch (event.cbutton.button) {
|
||||||
case SDL_CONTROLLER_BUTTON_A:
|
case SDL_CONTROLLER_BUTTON_A:
|
||||||
event.cbutton.button = SDL_CONTROLLER_BUTTON_B;
|
event.cbutton.button = SDL_CONTROLLER_BUTTON_B;
|
||||||
|
|||||||
@@ -5,12 +5,14 @@
|
|||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
|
|
||||||
|
#include "settings/streamingpreferences.h"
|
||||||
|
|
||||||
class SdlGamepadKeyNavigation : public QObject
|
class SdlGamepadKeyNavigation : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SdlGamepadKeyNavigation();
|
SdlGamepadKeyNavigation(StreamingPreferences* prefs);
|
||||||
|
|
||||||
~SdlGamepadKeyNavigation();
|
~SdlGamepadKeyNavigation();
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ private slots:
|
|||||||
void onPollingTimerFired();
|
void onPollingTimerFired();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
StreamingPreferences* m_Prefs;
|
||||||
QTimer* m_PollingTimer;
|
QTimer* m_PollingTimer;
|
||||||
QList<SDL_GameController*> m_Gamepads;
|
QList<SDL_GameController*> m_Gamepads;
|
||||||
bool m_Enabled;
|
bool m_Enabled;
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+381
-247
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+322
-188
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+355
-221
File diff suppressed because it is too large.
Load diff
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+338
-204
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+319
-185
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large.
Load diff
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+316
-182
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+378
-244
File diff suppressed because it is too large.
Load diff
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+326
-192
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+328
-194
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+315
-181
File diff suppressed because it is too large.
Load diff
+101
-37
@@ -10,7 +10,8 @@
|
|||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QCursor>
|
#include <QCursor>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QFile>
|
#include <QTemporaryFile>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
// Don't let SDL hook our main function, since Qt is already
|
// Don't let SDL hook our main function, since Qt is already
|
||||||
// doing the same thing. This needs to be before any headers
|
// doing the same thing. This needs to be before any headers
|
||||||
@@ -44,26 +45,24 @@
|
|||||||
#include "settings/streamingpreferences.h"
|
#include "settings/streamingpreferences.h"
|
||||||
#include "gui/sdlgamepadkeynavigation.h"
|
#include "gui/sdlgamepadkeynavigation.h"
|
||||||
|
|
||||||
#if !defined(QT_DEBUG) && defined(Q_OS_WIN32)
|
#if defined(Q_OS_WIN32)
|
||||||
// Log to file for release Windows builds
|
#define IS_UNSPECIFIED_HANDLE(x) ((x) == INVALID_HANDLE_VALUE || (x) == NULL)
|
||||||
#define USE_CUSTOM_LOGGER
|
|
||||||
|
// Log to file or console dynamically for Windows builds
|
||||||
#define LOG_TO_FILE
|
#define LOG_TO_FILE
|
||||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
|
|
||||||
// Use stdout logger on all Linux/BSD builds
|
|
||||||
#define USE_CUSTOM_LOGGER
|
|
||||||
#elif !defined(QT_DEBUG) && defined(Q_OS_DARWIN)
|
#elif !defined(QT_DEBUG) && defined(Q_OS_DARWIN)
|
||||||
// Log to file for release Mac builds
|
// Log to file for release Mac builds
|
||||||
#define USE_CUSTOM_LOGGER
|
|
||||||
#define LOG_TO_FILE
|
#define LOG_TO_FILE
|
||||||
#else
|
#else
|
||||||
// For debug Windows and Mac builds, use default logger
|
// Log to console for debug Mac builds
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_CUSTOM_LOGGER
|
|
||||||
static QElapsedTimer s_LoggerTime;
|
static QElapsedTimer s_LoggerTime;
|
||||||
static QTextStream s_LoggerStream(stdout);
|
static QTextStream s_LoggerStream(stderr);
|
||||||
static QMutex s_LoggerLock;
|
static QMutex s_LoggerLock;
|
||||||
static bool s_SuppressVerboseOutput;
|
static bool s_SuppressVerboseOutput;
|
||||||
|
static QRegularExpression k_RikeyRegex("&rikey=\\w+");
|
||||||
|
static QRegularExpression k_RikeyIdRegex("&rikeyid=[\\d-]+");
|
||||||
#ifdef LOG_TO_FILE
|
#ifdef LOG_TO_FILE
|
||||||
// Max log file size of 10 MB
|
// Max log file size of 10 MB
|
||||||
#define MAX_LOG_SIZE_BYTES (10 * 1024 * 1024)
|
#define MAX_LOG_SIZE_BYTES (10 * 1024 * 1024)
|
||||||
@@ -76,6 +75,10 @@ void logToLoggerStream(QString& message)
|
|||||||
{
|
{
|
||||||
QMutexLocker lock(&s_LoggerLock);
|
QMutexLocker lock(&s_LoggerLock);
|
||||||
|
|
||||||
|
// Strip session encryption keys and IVs from the logs
|
||||||
|
message.replace(k_RikeyRegex, "&rikey=REDACTED");
|
||||||
|
message.replace(k_RikeyIdRegex, "&rikeyid=REDACTED");
|
||||||
|
|
||||||
#ifdef LOG_TO_FILE
|
#ifdef LOG_TO_FILE
|
||||||
if (s_LogLimitReached) {
|
if (s_LogLimitReached) {
|
||||||
return;
|
return;
|
||||||
@@ -216,8 +219,6 @@ void ffmpegLogToDiskHandler(void* ptr, int level, const char* fmt, va_list vl)
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
@@ -306,13 +307,25 @@ int main(int argc, char *argv[])
|
|||||||
qputenv("QML_DISK_CACHE_PATH", Path::getQmlCacheDir().toUtf8());
|
qputenv("QML_DISK_CACHE_PATH", Path::getQmlCacheDir().toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_CUSTOM_LOGGER
|
#ifdef Q_OS_WIN32
|
||||||
|
// Grab the original std handles before we potentially redirect them later
|
||||||
|
HANDLE oldConOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
HANDLE oldConErr = GetStdHandle(STD_ERROR_HANDLE);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef 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())));
|
|
||||||
if (s_LoggerFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
|
#ifdef Q_OS_WIN32
|
||||||
QTextStream(stderr) << "Redirecting log output to " << s_LoggerFile->fileName() << Qt::endl;
|
// Only log to a file if the user didn't redirect stderr somewhere else
|
||||||
s_LoggerStream.setDevice(s_LoggerFile);
|
if (IS_UNSPECIFIED_HANDLE(oldConErr))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
s_LoggerFile = new QFile(tempDir.filePath(QString("Moonlight-%1.log").arg(QDateTime::currentSecsSinceEpoch())));
|
||||||
|
if (s_LoggerFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
|
QTextStream(stderr) << "Redirecting log output to " << s_LoggerFile->fileName() << Qt::endl;
|
||||||
|
s_LoggerStream.setDevice(s_LoggerFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -324,8 +337,6 @@ int main(int argc, char *argv[])
|
|||||||
av_log_set_callback(ffmpegLogToDiskHandler);
|
av_log_set_callback(ffmpegLogToDiskHandler);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
// Create a crash dump when we crash on Windows
|
// Create a crash dump when we crash on Windows
|
||||||
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
|
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
|
||||||
@@ -351,6 +362,11 @@ int main(int argc, char *argv[])
|
|||||||
SSL_free(nullptr);
|
SSL_free(nullptr);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// We keep this at function scope to ensure it stays around while we're running,
|
||||||
|
// becaue the Qt QPA will need to read it. Since the temporary file is only
|
||||||
|
// created when open() is called, this doesn't do any harm for other platforms.
|
||||||
|
QTemporaryFile eglfsConfigFile("eglfs_override_XXXXXX.conf");
|
||||||
|
|
||||||
// Avoid using High DPI on EGLFS. It breaks font rendering.
|
// Avoid using High DPI on EGLFS. It breaks font rendering.
|
||||||
// https://bugreports.qt.io/browse/QTBUG-64377
|
// https://bugreports.qt.io/browse/QTBUG-64377
|
||||||
//
|
//
|
||||||
@@ -384,6 +400,17 @@ int main(int argc, char *argv[])
|
|||||||
qWarning() << "Unable to find a KMSDRM display device!";
|
qWarning() << "Unable to find a KMSDRM display device!";
|
||||||
qWarning() << "On the Raspberry Pi, you must enable the 'fake KMS' driver in raspi-config to use Moonlight outside of the GUI environment.";
|
qWarning() << "On the Raspberry Pi, you must enable the 'fake KMS' driver in raspi-config to use Moonlight outside of the GUI environment.";
|
||||||
}
|
}
|
||||||
|
else if (!qEnvironmentVariableIsSet("QT_QPA_EGLFS_KMS_CONFIG")) {
|
||||||
|
// HACK: Remove this when Qt is fixed to properly check for display support before picking a card
|
||||||
|
QString cardOverride = WMUtils::getDrmCardOverride();
|
||||||
|
if (!cardOverride.isEmpty()) {
|
||||||
|
if (eglfsConfigFile.open()) {
|
||||||
|
qInfo() << "Overriding default Qt EGLFS card selection to" << cardOverride;
|
||||||
|
QTextStream(&eglfsConfigFile) << "{ \"device\": \"" << cardOverride << "\" }";
|
||||||
|
qputenv("QT_QPA_EGLFS_KMS_CONFIG", eglfsConfigFile.fileName().toUtf8());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EGLFS uses OpenGLES 2.0, so we will too. Some embedded platforms may not
|
// EGLFS uses OpenGLES 2.0, so we will too. Some embedded platforms may not
|
||||||
@@ -393,9 +420,18 @@ int main(int argc, char *argv[])
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(Q_PROCESSOR_X86) && defined(SDL_HINT_VIDEO_X11_FORCE_EGL)
|
||||||
|
// Some ARM and RISC-V embedded devices don't have working GLX which can cause
|
||||||
|
// SDL to fail to find a working OpenGL implementation at all. Let's force EGL
|
||||||
|
// on non-x86 platforms, since GLX is deprecated anyway.
|
||||||
|
SDL_SetHint(SDL_HINT_VIDEO_X11_FORCE_EGL, "1");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
// This avoids using the default keychain for SSL, which may cause
|
// This avoids using the default keychain for SSL, which may cause
|
||||||
// password prompts on macOS.
|
// password prompts on macOS.
|
||||||
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
|
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_WIN32) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
#if defined(Q_OS_WIN32) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
if (!qEnvironmentVariableIsSet("QT_OPENGL")) {
|
if (!qEnvironmentVariableIsSet("QT_OPENGL")) {
|
||||||
@@ -418,6 +454,12 @@ int main(int argc, char *argv[])
|
|||||||
qputenv("QSG_RENDER_LOOP", "basic");
|
qputenv("QSG_RENDER_LOOP", "basic");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_DARWIN) && defined(QT_DEBUG)
|
||||||
|
// Enable Metal valiation for debug builds
|
||||||
|
qputenv("MTL_DEBUG_LAYER", "1");
|
||||||
|
qputenv("MTL_SHADER_VALIDATION", "1");
|
||||||
|
#endif
|
||||||
|
|
||||||
// We don't want system proxies to apply to us
|
// We don't want system proxies to apply to us
|
||||||
QNetworkProxyFactory::setUseSystemConfiguration(false);
|
QNetworkProxyFactory::setUseSystemConfiguration(false);
|
||||||
|
|
||||||
@@ -496,6 +538,13 @@ int main(int argc, char *argv[])
|
|||||||
// SDL doing it for us behind our backs.
|
// SDL doing it for us behind our backs.
|
||||||
SDL_SetHint("SDL_MOUSE_AUTO_CAPTURE", "0");
|
SDL_SetHint("SDL_MOUSE_AUTO_CAPTURE", "0");
|
||||||
|
|
||||||
|
// SDL will try to lock the mouse cursor on Wayland if it's not visible in order to
|
||||||
|
// support applications that assume they can warp the cursor (which isn't possible
|
||||||
|
// on Wayland). We don't want this behavior because it interferes with seamless mouse
|
||||||
|
// mode when toggling between windowed and fullscreen modes by unexpectedly locking
|
||||||
|
// the mouse cursor.
|
||||||
|
SDL_SetHint("SDL_VIDEO_WAYLAND_EMULATE_MOUSE_WARP", "0");
|
||||||
|
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
// Allow thread naming using exceptions on debug builds. SDL doesn't use SEH
|
// Allow thread naming using exceptions on debug builds. SDL doesn't use SEH
|
||||||
// when throwing the exceptions, so we don't enable it for release builds out
|
// when throwing the exceptions, so we don't enable it for release builds out
|
||||||
@@ -505,25 +554,34 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
QGuiApplication app(argc, argv);
|
QGuiApplication app(argc, argv);
|
||||||
|
|
||||||
|
#ifndef STEAM_LINK
|
||||||
|
// Force use of the KMSDRM backend for SDL when using Qt platform plugins
|
||||||
|
// that directly draw to the display without a windowing system.
|
||||||
|
if (QGuiApplication::platformName() == "eglfs" || QGuiApplication::platformName() == "linuxfb") {
|
||||||
|
qputenv("SDL_VIDEODRIVER", "kmsdrm");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
GlobalCommandLineParser parser;
|
GlobalCommandLineParser parser;
|
||||||
GlobalCommandLineParser::ParseResult commandLineParserResult = parser.parse(app.arguments());
|
GlobalCommandLineParser::ParseResult commandLineParserResult = parser.parse(app.arguments());
|
||||||
switch (commandLineParserResult) {
|
switch (commandLineParserResult) {
|
||||||
case GlobalCommandLineParser::ListRequested:
|
case GlobalCommandLineParser::ListRequested:
|
||||||
#ifdef USE_CUSTOM_LOGGER
|
|
||||||
// Don't log to the console since it will jumble the command output
|
// Don't log to the console since it will jumble the command output
|
||||||
s_SuppressVerboseOutput = true;
|
s_SuppressVerboseOutput = true;
|
||||||
#endif
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
// Attach to the console to be able to print output.
|
// If we don't have stdout or stderr handles (which will normally be the case
|
||||||
// Since we're a /SUBSYSTEM:WINDOWS app, we won't be attached by default.
|
// since we're a /SUBSYSTEM:WINDOWS app), attach to our parent console and use
|
||||||
|
// that for stdout and stderr.
|
||||||
|
//
|
||||||
|
// If we do have stdout or stderr handles, that means the user has used standard
|
||||||
|
// handle redirection. In that case, we don't want to override those handles.
|
||||||
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||||
HANDLE conOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
// If we didn't have an old stdout/stderr handle, use the new CONOUT$ handle
|
||||||
if (conOut != INVALID_HANDLE_VALUE && conOut != NULL) {
|
if (IS_UNSPECIFIED_HANDLE(oldConOut)) {
|
||||||
freopen("CONOUT$", "w", stdout);
|
freopen("CONOUT$", "w", stdout);
|
||||||
setvbuf(stdout, NULL, _IONBF, 0);
|
setvbuf(stdout, NULL, _IONBF, 0);
|
||||||
}
|
}
|
||||||
HANDLE conErr = GetStdHandle(STD_ERROR_HANDLE);
|
if (IS_UNSPECIFIED_HANDLE(oldConErr)) {
|
||||||
if (conErr != INVALID_HANDLE_VALUE && conErr != NULL) {
|
|
||||||
freopen("CONOUT$", "w", stderr);
|
freopen("CONOUT$", "w", stderr);
|
||||||
setvbuf(stderr, NULL, _IONBF, 0);
|
setvbuf(stderr, NULL, _IONBF, 0);
|
||||||
}
|
}
|
||||||
@@ -547,8 +605,7 @@ int main(int argc, char *argv[])
|
|||||||
runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch);
|
runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch);
|
||||||
|
|
||||||
// Apply the initial translation based on user preference
|
// Apply the initial translation based on user preference
|
||||||
StreamingPreferences prefs;
|
StreamingPreferences::get()->retranslate();
|
||||||
prefs.retranslate();
|
|
||||||
|
|
||||||
// Trickily declare the translation for dialog buttons
|
// Trickily declare the translation for dialog buttons
|
||||||
QCoreApplication::translate("QPlatformTheme", "&Yes");
|
QCoreApplication::translate("QPlatformTheme", "&Yes");
|
||||||
@@ -613,8 +670,8 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterUncreatableType<Session>("Session", 1, 0, "Session", "Session cannot be created from QML");
|
qmlRegisterUncreatableType<Session>("Session", 1, 0, "Session", "Session cannot be created from QML");
|
||||||
qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0,
|
qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0,
|
||||||
"ComputerManager",
|
"ComputerManager",
|
||||||
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
|
||||||
return new ComputerManager();
|
return new ComputerManager(StreamingPreferences::get(qmlEngine));
|
||||||
});
|
});
|
||||||
qmlRegisterSingletonType<AutoUpdateChecker>("AutoUpdateChecker", 1, 0,
|
qmlRegisterSingletonType<AutoUpdateChecker>("AutoUpdateChecker", 1, 0,
|
||||||
"AutoUpdateChecker",
|
"AutoUpdateChecker",
|
||||||
@@ -628,13 +685,13 @@ int main(int argc, char *argv[])
|
|||||||
});
|
});
|
||||||
qmlRegisterSingletonType<SdlGamepadKeyNavigation>("SdlGamepadKeyNavigation", 1, 0,
|
qmlRegisterSingletonType<SdlGamepadKeyNavigation>("SdlGamepadKeyNavigation", 1, 0,
|
||||||
"SdlGamepadKeyNavigation",
|
"SdlGamepadKeyNavigation",
|
||||||
[](QQmlEngine*, QJSEngine*) -> QObject* {
|
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
|
||||||
return new SdlGamepadKeyNavigation();
|
return new SdlGamepadKeyNavigation(StreamingPreferences::get(qmlEngine));
|
||||||
});
|
});
|
||||||
qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0,
|
qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0,
|
||||||
"StreamingPreferences",
|
"StreamingPreferences",
|
||||||
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
|
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
|
||||||
return new StreamingPreferences(qmlEngine);
|
return StreamingPreferences::get(qmlEngine);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create the identity manager on the main thread
|
// Create the identity manager on the main thread
|
||||||
@@ -665,7 +722,7 @@ int main(int argc, char *argv[])
|
|||||||
case GlobalCommandLineParser::StreamRequested:
|
case GlobalCommandLineParser::StreamRequested:
|
||||||
{
|
{
|
||||||
initialView = "qrc:/gui/CliStartStreamSegue.qml";
|
initialView = "qrc:/gui/CliStartStreamSegue.qml";
|
||||||
StreamingPreferences* preferences = new StreamingPreferences(&app);
|
StreamingPreferences* preferences = StreamingPreferences::get();
|
||||||
StreamCommandLineParser streamParser;
|
StreamCommandLineParser streamParser;
|
||||||
streamParser.parse(app.arguments(), preferences);
|
streamParser.parse(app.arguments(), preferences);
|
||||||
QString host = streamParser.getHost();
|
QString host = streamParser.getHost();
|
||||||
@@ -697,7 +754,7 @@ int main(int argc, char *argv[])
|
|||||||
ListCommandLineParser listParser;
|
ListCommandLineParser listParser;
|
||||||
listParser.parse(app.arguments());
|
listParser.parse(app.arguments());
|
||||||
auto launcher = new CliListApps::Launcher(listParser.getHost(), listParser, &app);
|
auto launcher = new CliListApps::Launcher(listParser.getHost(), listParser, &app);
|
||||||
launcher->execute(new ComputerManager(&app));
|
launcher->execute(new ComputerManager(StreamingPreferences::get()));
|
||||||
hasGUI = false;
|
hasGUI = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -718,5 +775,12 @@ int main(int argc, char *argv[])
|
|||||||
// sometimes freezing and blocking process exit.
|
// sometimes freezing and blocking process exit.
|
||||||
QThreadPool::globalInstance()->waitForDone(30000);
|
QThreadPool::globalInstance()->waitForDone(30000);
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
// Without an explicit flush, console redirection for the list command
|
||||||
|
// doesn't work reliably (sometimes the target file contains no text).
|
||||||
|
fflush(stderr);
|
||||||
|
fflush(stdout);
|
||||||
|
#endif
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
File renamed without changes.
@@ -1 +0,0 @@
|
|||||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 245 240" fill="white"><path class="st0" d="M104.4 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1.1-6.1-4.5-11.1-10.2-11.1zM140.9 103.9c-5.7 0-10.2 5-10.2 11.1s4.6 11.1 10.2 11.1c5.7 0 10.2-5 10.2-11.1s-4.5-11.1-10.2-11.1z"/><path class="st0" d="M189.5 20h-134C44.2 20 35 29.2 35 40.6v135.2c0 11.4 9.2 20.6 20.5 20.6h113.4l-5.3-18.5 12.8 11.9 12.1 11.2 21.5 19V40.6c0-11.4-9.2-20.6-20.5-20.6zm-38.6 130.6s-3.6-4.3-6.6-8.1c13.1-3.7 18.1-11.9 18.1-11.9-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.5-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.7-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.3-1.8-1-2.8-1.7-2.8-1.7s4.8 8 17.5 11.8c-3 3.8-6.7 8.3-6.7 8.3-22.1-.7-30.5-15.2-30.5-15.2 0-32.2 14.4-58.3 14.4-58.3 14.4-10.8 28.1-10.5 28.1-10.5l1 1.2c-18 5.2-26.3 13.1-26.3 13.1s2.2-1.2 5.9-2.9c10.7-4.7 19.2-6 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.6 0 0-7.9-7.5-24.9-12.7l1.4-1.6s13.7-.3 28.1 10.5c0 0 14.4 26.1 14.4 58.3 0 0-8.5 14.5-30.6 15.2z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="white"><path d="M20.317 4.3698a19.7913 19.7913 0 0 0-4.8851-1.5152.0741.0741 0 0 0-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 0 0-.0785-.037 19.7363 19.7363 0 0 0-4.8852 1.515.0699.0699 0 0 0-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 0 0 .0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 0 0 .0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 0 0-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 0 1-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 0 1 .0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 0 1 .0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 0 1-.0066.1276 12.2986 12.2986 0 0 1-1.873.8914.0766.0766 0 0 0-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 0 0 .0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 0 0 .0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 0 0-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
+4
-1
@@ -17,7 +17,7 @@
|
|||||||
<file>res/baseline-help_outline-24px.svg</file>
|
<file>res/baseline-help_outline-24px.svg</file>
|
||||||
<file>res/baseline-error_outline-24px.svg</file>
|
<file>res/baseline-error_outline-24px.svg</file>
|
||||||
<file>res/baseline-check_circle_outline-24px.svg</file>
|
<file>res/baseline-check_circle_outline-24px.svg</file>
|
||||||
<file>res/Discord-Logo-White.svg</file>
|
<file>res/discord.svg</file>
|
||||||
<file>languages/qml_de.ts</file>
|
<file>languages/qml_de.ts</file>
|
||||||
<file>languages/qml_de.qm</file>
|
<file>languages/qml_de.qm</file>
|
||||||
<file>languages/qml_fr.ts</file>
|
<file>languages/qml_fr.ts</file>
|
||||||
@@ -64,6 +64,8 @@
|
|||||||
<file>languages/qml_cs.qm</file>
|
<file>languages/qml_cs.qm</file>
|
||||||
<file>languages/qml_he.ts</file>
|
<file>languages/qml_he.ts</file>
|
||||||
<file>languages/qml_he.qm</file>
|
<file>languages/qml_he.qm</file>
|
||||||
|
<file>languages/qml_ckb.ts</file>
|
||||||
|
<file>languages/qml_ckb.qm</file>
|
||||||
<!-- Don't include pt_BR until it is more complete -->
|
<!-- Don't include pt_BR until it is more complete -->
|
||||||
<!--file>languages/qml_pt_BR.qm</file-->
|
<!--file>languages/qml_pt_BR.qm</file-->
|
||||||
<!--file>languages/qml_pt_BR.ts</file-->
|
<!--file>languages/qml_pt_BR.ts</file-->
|
||||||
@@ -82,5 +84,6 @@
|
|||||||
<file alias="d3d11_genyuv_pixel.fxc">shaders/d3d11_genyuv_pixel.fxc</file>
|
<file alias="d3d11_genyuv_pixel.fxc">shaders/d3d11_genyuv_pixel.fxc</file>
|
||||||
<file alias="d3d11_bt601lim_pixel.fxc">shaders/d3d11_bt601lim_pixel.fxc</file>
|
<file alias="d3d11_bt601lim_pixel.fxc">shaders/d3d11_bt601lim_pixel.fxc</file>
|
||||||
<file alias="d3d11_bt2020lim_pixel.fxc">shaders/d3d11_bt2020lim_pixel.fxc</file>
|
<file alias="d3d11_bt2020lim_pixel.fxc">shaders/d3d11_bt2020lim_pixel.fxc</file>
|
||||||
|
<file alias="vt_renderer.metal">shaders/vt_renderer.metal</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
|
#include <QReadWriteLock>
|
||||||
#include <QtMath>
|
#include <QtMath>
|
||||||
|
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
#define SER_DEFAULTVER "defaultver"
|
#define SER_DEFAULTVER "defaultver"
|
||||||
#define SER_PACKETSIZE "packetsize"
|
#define SER_PACKETSIZE "packetsize"
|
||||||
#define SER_DETECTNETBLOCKING "detectnetblocking"
|
#define SER_DETECTNETBLOCKING "detectnetblocking"
|
||||||
|
#define SER_SHOWPERFOVERLAY "showperfoverlay"
|
||||||
#define SER_SWAPMOUSEBUTTONS "swapmousebuttons"
|
#define SER_SWAPMOUSEBUTTONS "swapmousebuttons"
|
||||||
#define SER_MUTEONFOCUSLOSS "muteonfocusloss"
|
#define SER_MUTEONFOCUSLOSS "muteonfocusloss"
|
||||||
#define SER_BACKGROUNDGAMEPAD "backgroundgamepad"
|
#define SER_BACKGROUNDGAMEPAD "backgroundgamepad"
|
||||||
@@ -48,18 +50,50 @@
|
|||||||
|
|
||||||
#define CURRENT_DEFAULT_VER 2
|
#define CURRENT_DEFAULT_VER 2
|
||||||
|
|
||||||
StreamingPreferences::StreamingPreferences(QObject *parent)
|
static StreamingPreferences* s_GlobalPrefs;
|
||||||
: QObject(parent),
|
static QReadWriteLock s_GlobalPrefsLock;
|
||||||
m_QmlEngine(nullptr)
|
|
||||||
|
StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine)
|
||||||
|
: m_QmlEngine(qmlEngine)
|
||||||
{
|
{
|
||||||
reload();
|
reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent)
|
StreamingPreferences* StreamingPreferences::get(QQmlEngine *qmlEngine)
|
||||||
: QObject(parent),
|
|
||||||
m_QmlEngine(qmlEngine)
|
|
||||||
{
|
{
|
||||||
reload();
|
{
|
||||||
|
QReadLocker readGuard(&s_GlobalPrefsLock);
|
||||||
|
|
||||||
|
// If we have a preference object and it's associated with a QML engine or
|
||||||
|
// if the caller didn't specify a QML engine, return the existing object.
|
||||||
|
if (s_GlobalPrefs && (s_GlobalPrefs->m_QmlEngine || !qmlEngine)) {
|
||||||
|
// The lifetime logic here relies on the QML engine also being a singleton.
|
||||||
|
Q_ASSERT(!qmlEngine || s_GlobalPrefs->m_QmlEngine == qmlEngine);
|
||||||
|
return s_GlobalPrefs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QWriteLocker writeGuard(&s_GlobalPrefsLock);
|
||||||
|
|
||||||
|
// If we already have an preference object but the QML engine is now available,
|
||||||
|
// associate the QML engine with the preferences.
|
||||||
|
if (s_GlobalPrefs) {
|
||||||
|
if (!s_GlobalPrefs->m_QmlEngine) {
|
||||||
|
s_GlobalPrefs->m_QmlEngine = qmlEngine;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We could reach this codepath if another thread raced with us
|
||||||
|
// and created the object while we were outside the pref lock.
|
||||||
|
Q_ASSERT(!qmlEngine || s_GlobalPrefs->m_QmlEngine == qmlEngine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s_GlobalPrefs = new StreamingPreferences(qmlEngine);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s_GlobalPrefs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamingPreferences::reload()
|
void StreamingPreferences::reload()
|
||||||
@@ -97,6 +131,7 @@ void StreamingPreferences::reload()
|
|||||||
richPresence = settings.value(SER_RICHPRESENCE, true).toBool();
|
richPresence = settings.value(SER_RICHPRESENCE, true).toBool();
|
||||||
gamepadMouse = settings.value(SER_GAMEPADMOUSE, true).toBool();
|
gamepadMouse = settings.value(SER_GAMEPADMOUSE, true).toBool();
|
||||||
detectNetworkBlocking = settings.value(SER_DETECTNETBLOCKING, true).toBool();
|
detectNetworkBlocking = settings.value(SER_DETECTNETBLOCKING, true).toBool();
|
||||||
|
showPerformanceOverlay = settings.value(SER_SHOWPERFOVERLAY, false).toBool();
|
||||||
packetSize = settings.value(SER_PACKETSIZE, 0).toInt();
|
packetSize = settings.value(SER_PACKETSIZE, 0).toInt();
|
||||||
swapMouseButtons = settings.value(SER_SWAPMOUSEBUTTONS, false).toBool();
|
swapMouseButtons = settings.value(SER_SWAPMOUSEBUTTONS, false).toBool();
|
||||||
muteOnFocusLoss = settings.value(SER_MUTEONFOCUSLOSS, false).toBool();
|
muteOnFocusLoss = settings.value(SER_MUTEONFOCUSLOSS, false).toBool();
|
||||||
@@ -252,6 +287,8 @@ QString StreamingPreferences::getSuffixFromLanguage(StreamingPreferences::Langua
|
|||||||
return "cs";
|
return "cs";
|
||||||
case LANG_HE:
|
case LANG_HE:
|
||||||
return "he";
|
return "he";
|
||||||
|
case LANG_CKB:
|
||||||
|
return "ckb";
|
||||||
case LANG_AUTO:
|
case LANG_AUTO:
|
||||||
default:
|
default:
|
||||||
return QLocale::system().name();
|
return QLocale::system().name();
|
||||||
@@ -280,6 +317,7 @@ void StreamingPreferences::save()
|
|||||||
settings.setValue(SER_GAMEPADMOUSE, gamepadMouse);
|
settings.setValue(SER_GAMEPADMOUSE, gamepadMouse);
|
||||||
settings.setValue(SER_PACKETSIZE, packetSize);
|
settings.setValue(SER_PACKETSIZE, packetSize);
|
||||||
settings.setValue(SER_DETECTNETBLOCKING, detectNetworkBlocking);
|
settings.setValue(SER_DETECTNETBLOCKING, detectNetworkBlocking);
|
||||||
|
settings.setValue(SER_SHOWPERFOVERLAY, showPerformanceOverlay);
|
||||||
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
settings.setValue(SER_AUDIOCFG, static_cast<int>(audioConfig));
|
||||||
settings.setValue(SER_HDR, enableHdr);
|
settings.setValue(SER_HDR, enableHdr);
|
||||||
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
|
settings.setValue(SER_VIDEOCFG, static_cast<int>(videoCodecConfig));
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ class StreamingPreferences : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StreamingPreferences(QObject *parent = nullptr);
|
static StreamingPreferences* get(QQmlEngine *qmlEngine = nullptr);
|
||||||
StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
Q_INVOKABLE static int
|
Q_INVOKABLE static int
|
||||||
getDefaultBitrate(int width, int height, int fps);
|
getDefaultBitrate(int width, int height, int fps);
|
||||||
@@ -92,6 +91,7 @@ public:
|
|||||||
LANG_PL,
|
LANG_PL,
|
||||||
LANG_CS,
|
LANG_CS,
|
||||||
LANG_HE,
|
LANG_HE,
|
||||||
|
LANG_CKB,
|
||||||
};
|
};
|
||||||
Q_ENUM(Language);
|
Q_ENUM(Language);
|
||||||
|
|
||||||
@@ -120,6 +120,7 @@ public:
|
|||||||
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged)
|
Q_PROPERTY(bool richPresence MEMBER richPresence NOTIFY richPresenceChanged)
|
||||||
Q_PROPERTY(bool gamepadMouse MEMBER gamepadMouse NOTIFY gamepadMouseChanged)
|
Q_PROPERTY(bool gamepadMouse MEMBER gamepadMouse NOTIFY gamepadMouseChanged)
|
||||||
Q_PROPERTY(bool detectNetworkBlocking MEMBER detectNetworkBlocking NOTIFY detectNetworkBlockingChanged)
|
Q_PROPERTY(bool detectNetworkBlocking MEMBER detectNetworkBlocking NOTIFY detectNetworkBlockingChanged)
|
||||||
|
Q_PROPERTY(bool showPerformanceOverlay MEMBER showPerformanceOverlay NOTIFY showPerformanceOverlayChanged)
|
||||||
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
Q_PROPERTY(AudioConfig audioConfig MEMBER audioConfig NOTIFY audioConfigChanged)
|
||||||
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
|
Q_PROPERTY(VideoCodecConfig videoCodecConfig MEMBER videoCodecConfig NOTIFY videoCodecConfigChanged)
|
||||||
Q_PROPERTY(bool enableHdr MEMBER enableHdr NOTIFY enableHdrChanged)
|
Q_PROPERTY(bool enableHdr MEMBER enableHdr NOTIFY enableHdrChanged)
|
||||||
@@ -156,6 +157,7 @@ public:
|
|||||||
bool richPresence;
|
bool richPresence;
|
||||||
bool gamepadMouse;
|
bool gamepadMouse;
|
||||||
bool detectNetworkBlocking;
|
bool detectNetworkBlocking;
|
||||||
|
bool showPerformanceOverlay;
|
||||||
bool swapMouseButtons;
|
bool swapMouseButtons;
|
||||||
bool muteOnFocusLoss;
|
bool muteOnFocusLoss;
|
||||||
bool backgroundGamepad;
|
bool backgroundGamepad;
|
||||||
@@ -196,6 +198,7 @@ signals:
|
|||||||
void richPresenceChanged();
|
void richPresenceChanged();
|
||||||
void gamepadMouseChanged();
|
void gamepadMouseChanged();
|
||||||
void detectNetworkBlockingChanged();
|
void detectNetworkBlockingChanged();
|
||||||
|
void showPerformanceOverlayChanged();
|
||||||
void mouseButtonsChanged();
|
void mouseButtonsChanged();
|
||||||
void muteOnFocusLossChanged();
|
void muteOnFocusLossChanged();
|
||||||
void backgroundGamepadChanged();
|
void backgroundGamepadChanged();
|
||||||
@@ -206,6 +209,8 @@ signals:
|
|||||||
void languageChanged();
|
void languageChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
explicit StreamingPreferences(QQmlEngine *qmlEngine);
|
||||||
|
|
||||||
QString getSuffixFromLanguage(Language lang);
|
QString getSuffixFromLanguage(Language lang);
|
||||||
|
|
||||||
QQmlEngine* m_QmlEngine;
|
QQmlEngine* m_QmlEngine;
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using namespace metal;
|
||||||
|
|
||||||
|
struct Vertex
|
||||||
|
{
|
||||||
|
float4 position [[ position ]];
|
||||||
|
float2 texCoords;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CscParams
|
||||||
|
{
|
||||||
|
float3 matrix[3];
|
||||||
|
float3 offsets;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr sampler s(coord::normalized, address::clamp_to_edge, filter::linear);
|
||||||
|
|
||||||
|
vertex Vertex vs_draw(constant Vertex *vertices [[ buffer(0) ]], uint id [[ vertex_id ]])
|
||||||
|
{
|
||||||
|
return vertices[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 ps_draw_biplanar(Vertex v [[ stage_in ]],
|
||||||
|
constant CscParams &cscParams [[ buffer(0) ]],
|
||||||
|
texture2d<float> luminancePlane [[ texture(0) ]],
|
||||||
|
texture2d<float> chrominancePlane [[ texture(1) ]])
|
||||||
|
{
|
||||||
|
float3 yuv = float3(luminancePlane.sample(s, v.texCoords).r, chrominancePlane.sample(s, v.texCoords).rg);
|
||||||
|
yuv -= cscParams.offsets;
|
||||||
|
|
||||||
|
float3 rgb;
|
||||||
|
rgb.r = dot(yuv, cscParams.matrix[0]);
|
||||||
|
rgb.g = dot(yuv, cscParams.matrix[1]);
|
||||||
|
rgb.b = dot(yuv, cscParams.matrix[2]);
|
||||||
|
return float4(rgb, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 ps_draw_rgb(Vertex v [[ stage_in ]],
|
||||||
|
texture2d<float> rgbTexture [[ texture(0) ]])
|
||||||
|
{
|
||||||
|
return rgbTexture.sample(s, v.texCoords);
|
||||||
|
}
|
||||||
@@ -64,6 +64,48 @@ IAudioRenderer* Session::createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATI
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Session::initializeAudioRenderer()
|
||||||
|
{
|
||||||
|
int error;
|
||||||
|
|
||||||
|
SDL_assert(m_OriginalAudioConfig.channelCount > 0);
|
||||||
|
SDL_assert(m_AudioRenderer == nullptr);
|
||||||
|
SDL_assert(m_OpusDecoder == nullptr);
|
||||||
|
|
||||||
|
m_AudioRenderer = createAudioRenderer(&m_OriginalAudioConfig);
|
||||||
|
|
||||||
|
// We may be unable to create an audio renderer right now
|
||||||
|
if (m_AudioRenderer == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow the chosen renderer to remap Opus channels as needed to ensure proper output
|
||||||
|
m_ActiveAudioConfig = m_OriginalAudioConfig;
|
||||||
|
m_AudioRenderer->remapChannels(&m_ActiveAudioConfig);
|
||||||
|
|
||||||
|
// Create the Opus decoder with the renderer's preferred channel mapping
|
||||||
|
m_OpusDecoder =
|
||||||
|
opus_multistream_decoder_create(m_ActiveAudioConfig.sampleRate,
|
||||||
|
m_ActiveAudioConfig.channelCount,
|
||||||
|
m_ActiveAudioConfig.streams,
|
||||||
|
m_ActiveAudioConfig.coupledStreams,
|
||||||
|
m_ActiveAudioConfig.mapping,
|
||||||
|
&error);
|
||||||
|
if (m_OpusDecoder == nullptr) {
|
||||||
|
delete m_AudioRenderer;
|
||||||
|
m_AudioRenderer = nullptr;
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Failed to create decoder: %d",
|
||||||
|
error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Audio stream has %d channels",
|
||||||
|
m_ActiveAudioConfig.channelCount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int Session::getAudioRendererCapabilities(int audioConfiguration)
|
int Session::getAudioRendererCapabilities(int audioConfiguration)
|
||||||
{
|
{
|
||||||
// Build a fake OPUS_MULTISTREAM_CONFIGURATION to give
|
// Build a fake OPUS_MULTISTREAM_CONFIGURATION to give
|
||||||
@@ -108,39 +150,8 @@ int Session::arInit(int /* audioConfiguration */,
|
|||||||
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
|
||||||
void* /* arContext */, int /* arFlags */)
|
void* /* arContext */, int /* arFlags */)
|
||||||
{
|
{
|
||||||
int error;
|
SDL_memcpy(&s_ActiveSession->m_OriginalAudioConfig, opusConfig, sizeof(*opusConfig));
|
||||||
|
s_ActiveSession->initializeAudioRenderer();
|
||||||
SDL_memcpy(&s_ActiveSession->m_AudioConfig, opusConfig, sizeof(*opusConfig));
|
|
||||||
|
|
||||||
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(&s_ActiveSession->m_AudioConfig);
|
|
||||||
if (s_ActiveSession->m_AudioRenderer == nullptr) {
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow the chosen renderer to remap Opus channels as needed to ensure proper output
|
|
||||||
s_ActiveSession->m_AudioRenderer->remapChannels(&s_ActiveSession->m_AudioConfig);
|
|
||||||
|
|
||||||
// Create the Opus decoder with the renderer's preferred channel mapping
|
|
||||||
s_ActiveSession->m_OpusDecoder =
|
|
||||||
opus_multistream_decoder_create(s_ActiveSession->m_AudioConfig.sampleRate,
|
|
||||||
s_ActiveSession->m_AudioConfig.channelCount,
|
|
||||||
s_ActiveSession->m_AudioConfig.streams,
|
|
||||||
s_ActiveSession->m_AudioConfig.coupledStreams,
|
|
||||||
s_ActiveSession->m_AudioConfig.mapping,
|
|
||||||
&error);
|
|
||||||
if (s_ActiveSession->m_OpusDecoder == NULL) {
|
|
||||||
delete s_ActiveSession->m_AudioRenderer;
|
|
||||||
s_ActiveSession->m_AudioRenderer = nullptr;
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to create decoder: %d",
|
|
||||||
error);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Audio stream has %d channels",
|
|
||||||
s_ActiveSession->m_AudioConfig.channelCount);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +205,7 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (s_ActiveSession->m_AudioRenderer != nullptr) {
|
if (s_ActiveSession->m_AudioRenderer != nullptr) {
|
||||||
int desiredSize = sizeof(short) * s_ActiveSession->m_AudioConfig.samplesPerFrame * s_ActiveSession->m_AudioConfig.channelCount;
|
int desiredSize = sizeof(short) * s_ActiveSession->m_ActiveAudioConfig.samplesPerFrame * s_ActiveSession->m_ActiveAudioConfig.channelCount;
|
||||||
void* buffer = s_ActiveSession->m_AudioRenderer->getAudioBuffer(&desiredSize);
|
void* buffer = s_ActiveSession->m_AudioRenderer->getAudioBuffer(&desiredSize);
|
||||||
if (buffer == nullptr) {
|
if (buffer == nullptr) {
|
||||||
return;
|
return;
|
||||||
@@ -204,13 +215,13 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
|||||||
(unsigned char*)sampleData,
|
(unsigned char*)sampleData,
|
||||||
sampleLength,
|
sampleLength,
|
||||||
(short*)buffer,
|
(short*)buffer,
|
||||||
desiredSize / sizeof(short) / s_ActiveSession->m_AudioConfig.channelCount,
|
desiredSize / sizeof(short) / s_ActiveSession->m_ActiveAudioConfig.channelCount,
|
||||||
0);
|
0);
|
||||||
|
|
||||||
// Update desiredSize with the number of bytes actually populated by the decoding operation
|
// Update desiredSize with the number of bytes actually populated by the decoding operation
|
||||||
if (samplesDecoded > 0) {
|
if (samplesDecoded > 0) {
|
||||||
SDL_assert(desiredSize >= (int)(sizeof(short) * samplesDecoded * s_ActiveSession->m_AudioConfig.channelCount));
|
SDL_assert(desiredSize >= (int)(sizeof(short) * samplesDecoded * s_ActiveSession->m_ActiveAudioConfig.channelCount));
|
||||||
desiredSize = sizeof(short) * samplesDecoded * s_ActiveSession->m_AudioConfig.channelCount;
|
desiredSize = sizeof(short) * samplesDecoded * s_ActiveSession->m_ActiveAudioConfig.channelCount;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
desiredSize = 0;
|
desiredSize = 0;
|
||||||
@@ -220,6 +231,9 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
|||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Reinitializing audio renderer after failure");
|
"Reinitializing audio renderer after failure");
|
||||||
|
|
||||||
|
opus_multistream_decoder_destroy(s_ActiveSession->m_OpusDecoder);
|
||||||
|
s_ActiveSession->m_OpusDecoder = nullptr;
|
||||||
|
|
||||||
delete s_ActiveSession->m_AudioRenderer;
|
delete s_ActiveSession->m_AudioRenderer;
|
||||||
s_ActiveSession->m_AudioRenderer = nullptr;
|
s_ActiveSession->m_AudioRenderer = nullptr;
|
||||||
}
|
}
|
||||||
@@ -234,14 +248,13 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
|
|||||||
// to drop samples to account for the time we've spent blocking audio rendering
|
// to drop samples to account for the time we've spent blocking audio rendering
|
||||||
// so we return to real-time playback and don't accumulate latency.
|
// so we return to real-time playback and don't accumulate latency.
|
||||||
Uint32 audioReinitStartTime = SDL_GetTicks();
|
Uint32 audioReinitStartTime = SDL_GetTicks();
|
||||||
|
if (s_ActiveSession->initializeAudioRenderer()) {
|
||||||
|
Uint32 audioReinitStopTime = SDL_GetTicks();
|
||||||
|
|
||||||
s_ActiveSession->m_AudioRenderer = s_ActiveSession->createAudioRenderer(&s_ActiveSession->m_AudioConfig);
|
s_ActiveSession->m_DropAudioEndTime = audioReinitStopTime + (audioReinitStopTime - audioReinitStartTime);
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
Uint32 audioReinitStopTime = SDL_GetTicks();
|
"Audio reinitialization took %d ms - starting drop window",
|
||||||
|
audioReinitStopTime - audioReinitStartTime);
|
||||||
s_ActiveSession->m_DropAudioEndTime = audioReinitStopTime + (audioReinitStopTime - audioReinitStartTime);
|
}
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Audio reinitialization took %d ms - starting drop window",
|
|
||||||
audioReinitStopTime - audioReinitStartTime);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,12 +100,6 @@ void* SdlAudioRenderer::getAudioBuffer(int*)
|
|||||||
|
|
||||||
bool SdlAudioRenderer::submitAudio(int bytesWritten)
|
bool SdlAudioRenderer::submitAudio(int bytesWritten)
|
||||||
{
|
{
|
||||||
// Our device may enter a permanent error status upon removal, so we need
|
|
||||||
// to recreate the audio device to pick up the new default audio device.
|
|
||||||
if (SDL_GetAudioDeviceStatus(m_AudioDevice) == SDL_AUDIO_STOPPED) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytesWritten == 0) {
|
if (bytesWritten == 0) {
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
return true;
|
return true;
|
||||||
@@ -118,8 +112,20 @@ bool SdlAudioRenderer::submitAudio(int bytesWritten)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Provide backpressure on the queue to ensure too many frames don't build up
|
// Provide backpressure on the queue to ensure too many frames don't build up
|
||||||
// in SDL's audio queue.
|
// in SDL's audio queue, but don't wait forever to avoid a deadlock if the
|
||||||
while (SDL_GetQueuedAudioSize(m_AudioDevice) / m_FrameSize > 10) {
|
// audio device fails.
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
// Our device may enter a permanent error status upon removal, so we need
|
||||||
|
// to recreate the audio device to pick up the new default audio device.
|
||||||
|
if (SDL_GetAudioDeviceStatus(m_AudioDevice) == SDL_AUDIO_STOPPED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only queue more samples where there are 10 frames or less in SDL's queue
|
||||||
|
if (SDL_GetQueuedAudioSize(m_AudioDevice) / m_FrameSize <= 10) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
SDL_Delay(1);
|
SDL_Delay(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,15 +112,42 @@ void SdlInputHandler::handleAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
|
|||||||
pointerId = (uint32_t)event->fingerId;
|
pointerId = (uint32_t)event->fingerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to send it as a native touch event, otherwise fall back to our touch emulation
|
// Try to send it as a native pen/touch event, otherwise fall back to our touch emulation
|
||||||
if (LiSendTouchEvent(eventType, pointerId, vidrelx / dst.w, vidrely / dst.h, event->pressure,
|
if (LiGetHostFeatureFlags() & LI_FF_PEN_TOUCH_EVENTS) {
|
||||||
0.0f, 0.0f, LI_ROT_UNKNOWN) == LI_ERR_UNSUPPORTED) {
|
#if SDL_VERSION_ATLEAST(2, 0, 22)
|
||||||
emulateAbsoluteFingerEvent(event);
|
bool isPen = false;
|
||||||
|
|
||||||
|
int numTouchDevices = SDL_GetNumTouchDevices();
|
||||||
|
for (int i = 0; i < numTouchDevices; i++) {
|
||||||
|
if (event->touchId == SDL_GetTouchDevice(i)) {
|
||||||
|
const char* touchName = SDL_GetTouchName(i);
|
||||||
|
|
||||||
|
// SDL will report "pen" as the name of pen input devices on Windows.
|
||||||
|
// https://github.com/libsdl-org/SDL/pull/5926
|
||||||
|
isPen = touchName && SDL_strcmp(touchName, "pen") == 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPen) {
|
||||||
|
LiSendPenEvent(eventType, LI_TOOL_TYPE_PEN, 0, vidrelx / dst.w, vidrely / dst.h, event->pressure,
|
||||||
|
0.0f, 0.0f, LI_ROT_UNKNOWN, LI_TILT_UNKNOWN);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
LiSendTouchEvent(eventType, pointerId, vidrelx / dst.w, vidrely / dst.h, event->pressure,
|
||||||
|
0.0f, 0.0f, LI_ROT_UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_DisabledTouchFeedback) {
|
||||||
|
// Disable touch feedback when passing touch natively
|
||||||
|
disableTouchFeedback();
|
||||||
|
m_DisabledTouchFeedback = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!m_DisabledTouchFeedback) {
|
else {
|
||||||
// Disable touch feedback when passing touch natively
|
emulateAbsoluteFingerEvent(event);
|
||||||
disableTouchFeedback();
|
|
||||||
m_DisabledTouchFeedback = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,15 +68,48 @@ void SdlInputHandler::sendGamepadState(GamepadState* state)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned char lt = state->lt;
|
||||||
|
unsigned char rt = state->rt;
|
||||||
|
short lsX = state->lsX;
|
||||||
|
short lsY = state->lsY;
|
||||||
|
short rsX = state->rsX;
|
||||||
|
short rsY = state->rsY;
|
||||||
|
|
||||||
|
// When in single controller mode, merge all gamepad state together
|
||||||
|
if (!m_MultiController) {
|
||||||
|
for (int i = 0; i < MAX_GAMEPADS; i++) {
|
||||||
|
if (m_GamepadState[i].index == state->index) {
|
||||||
|
buttons |= m_GamepadState[i].buttons;
|
||||||
|
if (lt < m_GamepadState[i].lt) {
|
||||||
|
lt = m_GamepadState[i].lt;
|
||||||
|
}
|
||||||
|
if (rt < m_GamepadState[i].rt) {
|
||||||
|
rt = m_GamepadState[i].rt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use abs() here instead of qAbs() for get proper integer promotion to
|
||||||
|
// correctly handle abs(-32768), which is not representable in a short.
|
||||||
|
if (abs(lsX) < abs(m_GamepadState[i].lsX) || abs(lsY) < abs(m_GamepadState[i].lsY)) {
|
||||||
|
lsX = m_GamepadState[i].lsX;
|
||||||
|
lsY = m_GamepadState[i].lsY;
|
||||||
|
}
|
||||||
|
if (abs(rsX) < abs(m_GamepadState[i].rsX) || abs(rsY) < abs(m_GamepadState[i].rsY)) {
|
||||||
|
rsX = m_GamepadState[i].rsX;
|
||||||
|
rsY = m_GamepadState[i].rsY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LiSendMultiControllerEvent(state->index,
|
LiSendMultiControllerEvent(state->index,
|
||||||
m_GamepadMask,
|
m_GamepadMask,
|
||||||
buttons,
|
buttons,
|
||||||
state->lt,
|
lt,
|
||||||
state->rt,
|
rt,
|
||||||
state->lsX,
|
lsX,
|
||||||
state->lsY,
|
lsY,
|
||||||
state->rsX,
|
rsX,
|
||||||
state->rsY);
|
rsY);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlInputHandler::sendGamepadBatteryState(GamepadState* state, SDL_JoystickPowerLevel level)
|
void SdlInputHandler::sendGamepadBatteryState(GamepadState* state, SDL_JoystickPowerLevel level)
|
||||||
@@ -124,11 +157,11 @@ Uint32 SdlInputHandler::mouseEmulationTimerCallback(Uint32 interval, void *param
|
|||||||
{
|
{
|
||||||
auto gamepad = reinterpret_cast<GamepadState*>(param);
|
auto gamepad = reinterpret_cast<GamepadState*>(param);
|
||||||
|
|
||||||
short rawX;
|
int rawX;
|
||||||
short rawY;
|
int rawY;
|
||||||
|
|
||||||
// Determine which analog stick is currently receiving the strongest input
|
// Determine which analog stick is currently receiving the strongest input
|
||||||
if ((uint32_t)qAbs(gamepad->lsX) + qAbs(gamepad->lsY) > (uint32_t)qAbs(gamepad->rsX) + qAbs(gamepad->rsY)) {
|
if (abs(gamepad->lsX) + abs(gamepad->lsY) > abs(gamepad->rsX) + abs(gamepad->rsY)) {
|
||||||
rawX = gamepad->lsX;
|
rawX = gamepad->lsX;
|
||||||
rawY = -gamepad->lsY;
|
rawY = -gamepad->lsY;
|
||||||
}
|
}
|
||||||
@@ -922,6 +955,10 @@ QString SdlInputHandler::getUnmappedGamepads()
|
|||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
||||||
|
|
||||||
|
// Flush stale events so they aren't processed by the main session event loop
|
||||||
|
SDL_FlushEvents(SDL_JOYDEVICEADDED, SDL_JOYDEVICEREMOVED);
|
||||||
|
SDL_FlushEvents(SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEREMAPPED);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,14 +42,6 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i
|
|||||||
// Allow gamepad input when the app doesn't have focus if requested
|
// Allow gamepad input when the app doesn't have focus if requested
|
||||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, prefs.backgroundGamepad ? "1" : "0");
|
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, prefs.backgroundGamepad ? "1" : "0");
|
||||||
|
|
||||||
// If absolute mouse mode is enabled, use relative mode warp (which
|
|
||||||
// is via normal motion events that are influenced by mouse acceleration).
|
|
||||||
// Otherwise, we'll use raw input capture which is straight from the device
|
|
||||||
// without modification by the OS.
|
|
||||||
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP,
|
|
||||||
prefs.absoluteMouseMode ? "1" : "0",
|
|
||||||
SDL_HINT_OVERRIDE);
|
|
||||||
|
|
||||||
#if !SDL_VERSION_ATLEAST(2, 0, 15)
|
#if !SDL_VERSION_ATLEAST(2, 0, 15)
|
||||||
// For older versions of SDL (2.0.14 and earlier), use SDL_HINT_GRAB_KEYBOARD
|
// For older versions of SDL (2.0.14 and earlier), use SDL_HINT_GRAB_KEYBOARD
|
||||||
SDL_SetHintWithPriority(SDL_HINT_GRAB_KEYBOARD,
|
SDL_SetHintWithPriority(SDL_HINT_GRAB_KEYBOARD,
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send this text to the PC
|
// Send this text to the PC
|
||||||
LiSendUtf8TextEvent(text, strlen(text));
|
LiSendUtf8TextEvent(text, (unsigned int)strlen(text));
|
||||||
|
|
||||||
// SDL_GetClipboardText() allocates, so we must free
|
// SDL_GetClipboardText() allocates, so we must free
|
||||||
SDL_free((void*)text);
|
SDL_free((void*)text);
|
||||||
|
|||||||
@@ -79,6 +79,24 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Batch all pending mouse motion events to save CPU time
|
||||||
|
Sint32 x = event->x, y = event->y, xrel = event->xrel, yrel = event->yrel;
|
||||||
|
SDL_Event nextEvent;
|
||||||
|
while (SDL_PeepEvents(&nextEvent, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION) > 0) {
|
||||||
|
event = &nextEvent.motion;
|
||||||
|
|
||||||
|
// Ignore synthetic mouse events
|
||||||
|
if (event->which != SDL_TOUCH_MOUSEID) {
|
||||||
|
x = event->x;
|
||||||
|
y = event->y;
|
||||||
|
xrel += event->xrel;
|
||||||
|
yrel += event->yrel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should not reference the original event anymore
|
||||||
|
event = nullptr;
|
||||||
|
|
||||||
if (m_AbsoluteMouseMode) {
|
if (m_AbsoluteMouseMode) {
|
||||||
int windowWidth, windowHeight;
|
int windowWidth, windowHeight;
|
||||||
SDL_GetWindowSize(m_Window, &windowWidth, &windowHeight);
|
SDL_GetWindowSize(m_Window, &windowWidth, &windowHeight);
|
||||||
@@ -97,14 +115,11 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
|||||||
// Use the stream and window sizes to determine the video region
|
// Use the stream and window sizes to determine the video region
|
||||||
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
|
||||||
|
|
||||||
mouseInVideoRegion = isMouseInVideoRegion(event->x,
|
mouseInVideoRegion = isMouseInVideoRegion(x, y, windowWidth, windowHeight);
|
||||||
event->y,
|
|
||||||
windowWidth,
|
|
||||||
windowHeight);
|
|
||||||
|
|
||||||
// Clamp motion to the video region
|
// Clamp motion to the video region
|
||||||
short x = qMin(qMax(event->x - dst.x, 0), dst.w);
|
x = qMin(qMax(x - dst.x, 0), dst.w);
|
||||||
short y = qMin(qMax(event->y - dst.y, 0), dst.h);
|
y = qMin(qMax(y - dst.y, 0), dst.h);
|
||||||
|
|
||||||
// Send the mouse position update if one of the following is true:
|
// Send the mouse position update if one of the following is true:
|
||||||
// a) it is in the video region now
|
// a) it is in the video region now
|
||||||
@@ -119,7 +134,7 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mouseInVideoRegion || m_MouseWasInVideoRegion || m_PendingMouseButtonsAllUpOnVideoRegionLeave) {
|
if (mouseInVideoRegion || m_MouseWasInVideoRegion || m_PendingMouseButtonsAllUpOnVideoRegionLeave) {
|
||||||
LiSendMousePositionEvent(x, y, dst.w, dst.h);
|
LiSendMousePositionEvent((short)x, (short)y, dst.w, dst.h);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust the cursor visibility if applicable
|
// Adjust the cursor visibility if applicable
|
||||||
@@ -135,7 +150,7 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
|
|||||||
m_MouseWasInVideoRegion = mouseInVideoRegion;
|
m_MouseWasInVideoRegion = mouseInVideoRegion;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
LiSendMouseMoveEvent(event->xrel, event->yrel);
|
LiSendMouseMoveEvent(xrel, yrel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+201
-61
@@ -52,6 +52,7 @@
|
|||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QCursor>
|
#include <QCursor>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
|
#include <QScreen>
|
||||||
|
|
||||||
#define CONN_TEST_SERVER "qt.conntest.moonlight-stream.org"
|
#define CONN_TEST_SERVER "qt.conntest.moonlight-stream.org"
|
||||||
|
|
||||||
@@ -132,7 +133,11 @@ void Session::clConnectionTerminated(int errorCode)
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
s_ActiveSession->m_UnexpectedTermination = true;
|
s_ActiveSession->m_UnexpectedTermination = true;
|
||||||
emit s_ActiveSession->displayLaunchError(tr("Connection terminated"));
|
|
||||||
|
// We'll assume large errors are hex values
|
||||||
|
bool hexError = qAbs(errorCode) > 1000;
|
||||||
|
emit s_ActiveSession->displayLaunchError(tr("Connection terminated") + "\n\n" +
|
||||||
|
tr("Error code: %1").arg(errorCode, hexError ? 8 : 0, hexError ? 16 : 10, QChar('0')));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,17 +541,15 @@ bool Session::populateDecoderProperties(SDL_Window* window)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences)
|
Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences)
|
||||||
: m_Preferences(preferences ? preferences : new StreamingPreferences(this)),
|
: m_Preferences(preferences ? preferences : StreamingPreferences::get()),
|
||||||
m_IsFullScreen(m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED || !WMUtils::isRunningDesktopEnvironment()),
|
m_IsFullScreen(m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED || !WMUtils::isRunningDesktopEnvironment()),
|
||||||
m_Computer(computer),
|
m_Computer(computer),
|
||||||
m_App(app),
|
m_App(app),
|
||||||
m_Window(nullptr),
|
m_Window(nullptr),
|
||||||
m_VideoDecoder(nullptr),
|
m_VideoDecoder(nullptr),
|
||||||
m_DecoderLock(0),
|
m_DecoderLock(0),
|
||||||
m_AudioDisabled(false),
|
|
||||||
m_AudioMuted(false),
|
m_AudioMuted(false),
|
||||||
m_DisplayOriginX(0),
|
m_QtWindow(nullptr),
|
||||||
m_DisplayOriginY(0),
|
|
||||||
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
|
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
|
||||||
m_InputHandler(nullptr),
|
m_InputHandler(nullptr),
|
||||||
m_MouseEmulationRefCount(0),
|
m_MouseEmulationRefCount(0),
|
||||||
@@ -562,6 +565,40 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
|
|||||||
|
|
||||||
bool Session::initialize()
|
bool Session::initialize()
|
||||||
{
|
{
|
||||||
|
#ifdef Q_OS_DARWIN
|
||||||
|
// If we have a notch and the user specified one of the two native display modes
|
||||||
|
// (notched or notchless), override the fullscreen mode to ensure it works as expected.
|
||||||
|
// - SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES=0 will place the video underneath the notch
|
||||||
|
// - SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES=1 will place the video below the notch
|
||||||
|
bool shouldUseFullScreenSpaces = true;
|
||||||
|
SDL_DisplayMode desktopMode;
|
||||||
|
SDL_Rect safeArea;
|
||||||
|
for (int displayIndex = 0; StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode, &safeArea); displayIndex++) {
|
||||||
|
// Check if this display has a notch (safeArea != desktopMode)
|
||||||
|
if (desktopMode.h != safeArea.h || desktopMode.w != safeArea.w) {
|
||||||
|
// Check if we're trying to stream at the full native resolution (including notch)
|
||||||
|
if (m_Preferences->width == desktopMode.w && m_Preferences->height == desktopMode.h) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Overriding default fullscreen mode for native fullscreen resolution");
|
||||||
|
shouldUseFullScreenSpaces = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using modesetting on modern versions of macOS is extremely unreliable
|
||||||
|
// and leads to hangs, deadlocks, and other nasty stuff. The only time
|
||||||
|
// people seem to use it is to get the full screen on notched Macs,
|
||||||
|
// which setting SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES=1 also accomplishes
|
||||||
|
// with much less headache.
|
||||||
|
//
|
||||||
|
// https://github.com/moonlight-stream/moonlight-qt/issues/973
|
||||||
|
// https://github.com/moonlight-stream/moonlight-qt/issues/999
|
||||||
|
// https://github.com/moonlight-stream/moonlight-qt/issues/1211
|
||||||
|
// https://github.com/moonlight-stream/moonlight-qt/issues/1218
|
||||||
|
SDL_SetHint(SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES, shouldUseFullScreenSpaces ? "1" : "0");
|
||||||
|
#endif
|
||||||
|
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||||
@@ -598,13 +635,18 @@ bool Session::initialize()
|
|||||||
m_StreamConfig.height = m_Preferences->height;
|
m_StreamConfig.height = m_Preferences->height;
|
||||||
m_StreamConfig.fps = m_Preferences->fps;
|
m_StreamConfig.fps = m_Preferences->fps;
|
||||||
m_StreamConfig.bitrate = m_Preferences->bitrateKbps;
|
m_StreamConfig.bitrate = m_Preferences->bitrateKbps;
|
||||||
m_StreamConfig.hevcBitratePercentageMultiplier = 75;
|
|
||||||
m_StreamConfig.av1BitratePercentageMultiplier = 75;
|
|
||||||
|
|
||||||
#ifndef STEAM_LINK
|
#ifndef STEAM_LINK
|
||||||
// Enable audio encryption as long as we're not on Steam Link.
|
// Opt-in to all encryption features if we detect that the platform
|
||||||
// That hardware can hardly handle Opus decoding at all.
|
// has AES cryptography acceleration instructions and more than 2 cores.
|
||||||
m_StreamConfig.encryptionFlags = ENCFLG_AUDIO;
|
if (StreamUtils::hasFastAes() && SDL_GetCPUCount() > 2) {
|
||||||
|
m_StreamConfig.encryptionFlags = ENCFLG_ALL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Enable audio encryption as long as we're not on Steam Link.
|
||||||
|
// That hardware can hardly handle Opus decoding at all.
|
||||||
|
m_StreamConfig.encryptionFlags = ENCFLG_AUDIO;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -718,6 +760,15 @@ bool Session::initialize()
|
|||||||
if (m_Preferences->enableHdr) {
|
if (m_Preferences->enableHdr) {
|
||||||
m_StreamConfig.supportedVideoFormats |= VIDEO_FORMAT_AV1_MAIN10;
|
m_StreamConfig.supportedVideoFormats |= VIDEO_FORMAT_AV1_MAIN10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We'll try to fall back to HEVC first if AV1 fails. We'd rather not fall back
|
||||||
|
// straight to H.264 if the user asked for AV1 and the host doesn't support it.
|
||||||
|
if (m_StreamConfig.supportedVideoFormats & VIDEO_FORMAT_AV1_MAIN8) {
|
||||||
|
m_StreamConfig.supportedVideoFormats |= VIDEO_FORMAT_H265;
|
||||||
|
}
|
||||||
|
if (m_StreamConfig.supportedVideoFormats & VIDEO_FORMAT_AV1_MAIN10) {
|
||||||
|
m_StreamConfig.supportedVideoFormats |= VIDEO_FORMAT_H265_MAIN10;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -732,7 +783,12 @@ bool Session::initialize()
|
|||||||
}
|
}
|
||||||
// Fall-through
|
// Fall-through
|
||||||
case StreamingPreferences::WM_FULLSCREEN:
|
case StreamingPreferences::WM_FULLSCREEN:
|
||||||
|
#ifdef Q_OS_DARWIN
|
||||||
|
// Don't use "real" fullscreen on macOS. See comments above.
|
||||||
|
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||||
|
#else
|
||||||
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
|
m_FullScreenFlag = SDL_WINDOW_FULLSCREEN;
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -814,21 +870,13 @@ bool Session::validateLaunch(SDL_Window* testWindow)
|
|||||||
emitLaunchWarning(tr("Your host software or GPU doesn't support encoding AV1."));
|
emitLaunchWarning(tr("Your host software or GPU doesn't support encoding AV1."));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll try to fall back to HEVC first if AV1 fails. We'd rather not fall back
|
|
||||||
// straight to H.264 if the user asked for AV1 and the host doesn't support it.
|
|
||||||
if (m_StreamConfig.supportedVideoFormats & VIDEO_FORMAT_AV1_MAIN8) {
|
|
||||||
m_StreamConfig.supportedVideoFormats |= VIDEO_FORMAT_H265;
|
|
||||||
}
|
|
||||||
if (m_StreamConfig.supportedVideoFormats & VIDEO_FORMAT_AV1_MAIN10) {
|
|
||||||
m_StreamConfig.supportedVideoFormats |= VIDEO_FORMAT_H265_MAIN10;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Moonlight-common-c will handle this case already, but we want
|
// Moonlight-common-c will handle this case already, but we want
|
||||||
// to set this explicitly here so we can do our hardware acceleration
|
// to set this explicitly here so we can do our hardware acceleration
|
||||||
// check below.
|
// check below.
|
||||||
m_StreamConfig.supportedVideoFormats &= ~VIDEO_FORMAT_MASK_AV1;
|
m_StreamConfig.supportedVideoFormats &= ~VIDEO_FORMAT_MASK_AV1;
|
||||||
}
|
}
|
||||||
else if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
|
else if (!m_Preferences->enableHdr && // HDR is checked below
|
||||||
|
m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
|
||||||
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Auto VCC is already checked in initialize()
|
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Auto VCC is already checked in initialize()
|
||||||
!isHardwareDecodeAvailable(testWindow,
|
!isHardwareDecodeAvailable(testWindow,
|
||||||
m_Preferences->videoDecoderSelection,
|
m_Preferences->videoDecoderSelection,
|
||||||
@@ -851,7 +899,8 @@ bool Session::validateLaunch(SDL_Window* testWindow)
|
|||||||
// check below.
|
// check below.
|
||||||
m_StreamConfig.supportedVideoFormats &= ~VIDEO_FORMAT_MASK_H265;
|
m_StreamConfig.supportedVideoFormats &= ~VIDEO_FORMAT_MASK_H265;
|
||||||
}
|
}
|
||||||
else if (m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
|
else if (!m_Preferences->enableHdr && // HDR is checked below
|
||||||
|
m_Preferences->videoDecoderSelection == StreamingPreferences::VDS_AUTO && // Force hardware decoding checked below
|
||||||
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Auto VCC is already checked in initialize()
|
m_Preferences->videoCodecConfig != StreamingPreferences::VCC_AUTO && // Auto VCC is already checked in initialize()
|
||||||
!isHardwareDecodeAvailable(testWindow,
|
!isHardwareDecodeAvailable(testWindow,
|
||||||
m_Preferences->videoDecoderSelection,
|
m_Preferences->videoDecoderSelection,
|
||||||
@@ -962,8 +1011,7 @@ bool Session::validateLaunch(SDL_Window* testWindow)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If nothing worked, warn the user that audio will not work
|
// If nothing worked, warn the user that audio will not work
|
||||||
m_AudioDisabled = !audioTestPassed;
|
if (!audioTestPassed) {
|
||||||
if (m_AudioDisabled) {
|
|
||||||
emitLaunchWarning(tr("Failed to open audio device. Audio will be unavailable during this session."));
|
emitLaunchWarning(tr("Failed to open audio device. Audio will be unavailable during this session."));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1086,26 +1134,38 @@ void Session::getWindowDimensions(int& x, int& y,
|
|||||||
// Create our window on the same display that Qt's UI
|
// Create our window on the same display that Qt's UI
|
||||||
// was being displayed on.
|
// was being displayed on.
|
||||||
else {
|
else {
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
Q_ASSERT(m_QtWindow != nullptr);
|
||||||
"Qt UI screen is at (%d,%d)",
|
if (m_QtWindow != nullptr) {
|
||||||
m_DisplayOriginX, m_DisplayOriginY);
|
QScreen* screen = m_QtWindow->screen();
|
||||||
for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
|
if (screen != nullptr) {
|
||||||
SDL_Rect displayBounds;
|
QRect displayRect = screen->geometry();
|
||||||
|
|
||||||
if (SDL_GetDisplayBounds(i, &displayBounds) == 0) {
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
if (displayBounds.x == m_DisplayOriginX &&
|
"Qt UI screen is at (%d,%d)",
|
||||||
displayBounds.y == m_DisplayOriginY) {
|
displayRect.x(), displayRect.y());
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
for (int i = 0; i < SDL_GetNumVideoDisplays(); i++) {
|
||||||
"SDL found matching display %d",
|
SDL_Rect displayBounds;
|
||||||
i);
|
|
||||||
displayIndex = i;
|
if (SDL_GetDisplayBounds(i, &displayBounds) == 0) {
|
||||||
break;
|
if (displayBounds.x == displayRect.x() &&
|
||||||
|
displayBounds.y == displayRect.y()) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL found matching display %d",
|
||||||
|
i);
|
||||||
|
displayIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"SDL_GetDisplayBounds(%d) failed: %s",
|
||||||
|
i, SDL_GetError());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_GetDisplayBounds(%d) failed: %s",
|
"Qt window is not associated with a QScreen!");
|
||||||
i, SDL_GetError());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1159,7 +1219,8 @@ 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)) {
|
SDL_Rect safeArea;
|
||||||
|
if (!StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode, &safeArea)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1424,8 +1485,7 @@ bool Session::startConnectionAsync()
|
|||||||
}
|
}
|
||||||
|
|
||||||
int err = LiStartConnection(&hostInfo, &m_StreamConfig, &k_ConnCallbacks,
|
int err = LiStartConnection(&hostInfo, &m_StreamConfig, &k_ConnCallbacks,
|
||||||
&m_VideoCallbacks,
|
&m_VideoCallbacks, &m_AudioCallbacks,
|
||||||
m_AudioDisabled ? nullptr : &m_AudioCallbacks,
|
|
||||||
NULL, 0, NULL, 0);
|
NULL, 0, NULL, 0);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
// We already displayed an error dialog in the stage failure
|
// We already displayed an error dialog in the stage failure
|
||||||
@@ -1472,10 +1532,9 @@ public:
|
|||||||
Session* m_Session;
|
Session* m_Session;
|
||||||
};
|
};
|
||||||
|
|
||||||
void Session::exec(int displayOriginX, int displayOriginY)
|
void Session::exec(QWindow* qtWindow)
|
||||||
{
|
{
|
||||||
m_DisplayOriginX = displayOriginX;
|
m_QtWindow = qtWindow;
|
||||||
m_DisplayOriginY = displayOriginY;
|
|
||||||
|
|
||||||
// Use a separate thread for the streaming session on X11 or Wayland
|
// Use a separate thread for the streaming session on X11 or Wayland
|
||||||
// to ensure we don't stomp on Qt's GL context. This breaks when using
|
// to ensure we don't stomp on Qt's GL context. This breaks when using
|
||||||
@@ -1579,16 +1638,26 @@ void Session::execInternal()
|
|||||||
// We always want a resizable window with High DPI enabled
|
// We always want a resizable window with High DPI enabled
|
||||||
Uint32 defaultWindowFlags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE;
|
Uint32 defaultWindowFlags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE;
|
||||||
|
|
||||||
// If we're starting in windowed mode and the Moonlight GUI is maximized,
|
// If we're starting in windowed mode and the Moonlight GUI is maximized or
|
||||||
// match that with the streaming window.
|
// minimized, match that with the streaming window.
|
||||||
if (!m_IsFullScreen) {
|
if (!m_IsFullScreen && m_QtWindow != nullptr) {
|
||||||
QWindowList windows = QGuiApplication::topLevelWindows();
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
for (const QWindow* window : windows) {
|
// Qt 5.10+ can propagate multiple states together
|
||||||
if (window->windowState() & Qt::WindowMaximized) {
|
if (m_QtWindow->windowStates() & Qt::WindowMaximized) {
|
||||||
defaultWindowFlags |= SDL_WINDOW_MAXIMIZED;
|
defaultWindowFlags |= SDL_WINDOW_MAXIMIZED;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (m_QtWindow->windowStates() & Qt::WindowMinimized) {
|
||||||
|
defaultWindowFlags |= SDL_WINDOW_MINIMIZED;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Qt 5.9 only supports a single state at a time
|
||||||
|
if (m_QtWindow->windowState() == Qt::WindowMaximized) {
|
||||||
|
defaultWindowFlags |= SDL_WINDOW_MAXIMIZED;
|
||||||
|
}
|
||||||
|
else if (m_QtWindow->windowState() == Qt::WindowMinimized) {
|
||||||
|
defaultWindowFlags |= SDL_WINDOW_MINIMIZED;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// We use only the computer name on macOS to match Apple conventions where the
|
// We use only the computer name on macOS to match Apple conventions where the
|
||||||
@@ -1631,16 +1700,13 @@ void Session::execInternal()
|
|||||||
|
|
||||||
// HACK: Remove once proper Dark Mode support lands in SDL
|
// HACK: Remove once proper Dark Mode support lands in SDL
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
{
|
if (m_QtWindow != nullptr) {
|
||||||
BOOL darkModeEnabled = FALSE;
|
BOOL darkModeEnabled;
|
||||||
|
|
||||||
// Query whether dark mode is enabled for our Qt window (which tracks the OS dark mode state)
|
// Query whether dark mode is enabled for our Qt window (which tracks the OS dark mode state)
|
||||||
QWindowList windows = QGuiApplication::topLevelWindows();
|
if (FAILED(DwmGetWindowAttribute((HWND)m_QtWindow->winId(), DWMWA_USE_IMMERSIVE_DARK_MODE, &darkModeEnabled, sizeof(darkModeEnabled))) &&
|
||||||
for (const QWindow* window : windows) {
|
FAILED(DwmGetWindowAttribute((HWND)m_QtWindow->winId(), DWMWA_USE_IMMERSIVE_DARK_MODE_OLD, &darkModeEnabled, sizeof(darkModeEnabled)))) {
|
||||||
if (SUCCEEDED(DwmGetWindowAttribute((HWND)window->winId(), DWMWA_USE_IMMERSIVE_DARK_MODE, &darkModeEnabled, sizeof(darkModeEnabled))) ||
|
darkModeEnabled = FALSE;
|
||||||
SUCCEEDED(DwmGetWindowAttribute((HWND)window->winId(), DWMWA_USE_IMMERSIVE_DARK_MODE_OLD, &darkModeEnabled, sizeof(darkModeEnabled)))) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_SysWMinfo info;
|
SDL_SysWMinfo info;
|
||||||
@@ -1772,6 +1838,9 @@ void Session::execInternal()
|
|||||||
// Start rich presence to indicate we're in game
|
// Start rich presence to indicate we're in game
|
||||||
RichPresenceManager presence(*m_Preferences, m_App.name);
|
RichPresenceManager presence(*m_Preferences, m_App.name);
|
||||||
|
|
||||||
|
// Toggle the stats overlay if requested by the user
|
||||||
|
m_OverlayManager.setOverlayState(Overlay::OverlayDebug, m_Preferences->showPerformanceOverlay);
|
||||||
|
|
||||||
// Hijack this thread to be the SDL main thread. We have to do this
|
// Hijack this thread to be the SDL main thread. We have to do this
|
||||||
// because we want to suspend all Qt processing until the stream is over.
|
// because we want to suspend all Qt processing until the stream is over.
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
@@ -1915,6 +1984,52 @@ void Session::execInternal()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow the renderer to handle the state change without being recreated
|
||||||
|
if (m_VideoDecoder) {
|
||||||
|
bool forceRecreation = false;
|
||||||
|
|
||||||
|
WINDOW_STATE_CHANGE_INFO windowChangeInfo = {};
|
||||||
|
windowChangeInfo.window = m_Window;
|
||||||
|
|
||||||
|
if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
||||||
|
windowChangeInfo.stateChangeFlags |= WINDOW_STATE_CHANGE_SIZE;
|
||||||
|
|
||||||
|
windowChangeInfo.width = event.window.data1;
|
||||||
|
windowChangeInfo.height = event.window.data2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newDisplayIndex = SDL_GetWindowDisplayIndex(m_Window);
|
||||||
|
if (newDisplayIndex != currentDisplayIndex) {
|
||||||
|
windowChangeInfo.stateChangeFlags |= WINDOW_STATE_CHANGE_DISPLAY;
|
||||||
|
|
||||||
|
windowChangeInfo.displayIndex = newDisplayIndex;
|
||||||
|
|
||||||
|
// If the refresh rates have changed, we will need to go through the full
|
||||||
|
// decoder recreation path to ensure Pacer is switched to the new display
|
||||||
|
// and that we apply any V-Sync disablement rules that may be needed for
|
||||||
|
// this display.
|
||||||
|
SDL_DisplayMode oldMode, newMode;
|
||||||
|
if (SDL_GetCurrentDisplayMode(currentDisplayIndex, &oldMode) < 0 ||
|
||||||
|
SDL_GetCurrentDisplayMode(newDisplayIndex, &newMode) < 0 ||
|
||||||
|
oldMode.refresh_rate != newMode.refresh_rate) {
|
||||||
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Forcing renderer recreation due to refresh rate change between displays");
|
||||||
|
forceRecreation = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!forceRecreation && m_VideoDecoder->notifyWindowChanged(&windowChangeInfo)) {
|
||||||
|
// Update the window display mode based on our current monitor
|
||||||
|
// NB: Avoid a useless modeset by only doing this if it changed.
|
||||||
|
if (newDisplayIndex != currentDisplayIndex) {
|
||||||
|
currentDisplayIndex = newDisplayIndex;
|
||||||
|
updateOptimalWindowDisplayMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Recreating renderer for window event: %d (%d %d)",
|
"Recreating renderer for window event: %d (%d %d)",
|
||||||
event.window.event,
|
event.window.event,
|
||||||
@@ -2087,6 +2202,31 @@ DispatchDeferredCleanup:
|
|||||||
m_VideoDecoder = nullptr;
|
m_VideoDecoder = nullptr;
|
||||||
SDL_AtomicUnlock(&m_DecoderLock);
|
SDL_AtomicUnlock(&m_DecoderLock);
|
||||||
|
|
||||||
|
// Propagate state changes from the SDL window back to the Qt window
|
||||||
|
//
|
||||||
|
// NB: We're making a conscious decision not to propagate the maximized
|
||||||
|
// or normal state of the window here. The thinking is that users may
|
||||||
|
// routinely maximize the streaming window simply to view the stream
|
||||||
|
// in a larger window, but they don't necessarily want the UI in such
|
||||||
|
// a large window.
|
||||||
|
if (!m_IsFullScreen && m_QtWindow != nullptr && m_Window != nullptr) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
if (SDL_GetWindowFlags(m_Window) & SDL_WINDOW_MINIMIZED) {
|
||||||
|
m_QtWindow->setWindowStates(m_QtWindow->windowStates() | Qt::WindowMinimized);
|
||||||
|
}
|
||||||
|
else if (m_QtWindow->windowStates() & Qt::WindowMinimized) {
|
||||||
|
m_QtWindow->setWindowStates(m_QtWindow->windowStates() & ~Qt::WindowMinimized);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (SDL_GetWindowFlags(m_Window) & SDL_WINDOW_MINIMIZED) {
|
||||||
|
m_QtWindow->setWindowState(Qt::WindowMinimized);
|
||||||
|
}
|
||||||
|
else if (m_QtWindow->windowState() & Qt::WindowMinimized) {
|
||||||
|
m_QtWindow->setWindowState(Qt::WindowNoState);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// This must be called after the decoder is deleted, because
|
// This must be called after the decoder is deleted, because
|
||||||
// the renderer may want to interact with the window
|
// the renderer may want to interact with the window
|
||||||
SDL_DestroyWindow(m_Window);
|
SDL_DestroyWindow(m_Window);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QSemaphore>
|
#include <QSemaphore>
|
||||||
|
#include <QWindow>
|
||||||
|
|
||||||
#include <Limelight.h>
|
#include <Limelight.h>
|
||||||
#include <opus_multistream.h>
|
#include <opus_multistream.h>
|
||||||
@@ -26,7 +27,7 @@ public:
|
|||||||
// Use Session::exec() or DeferredSessionCleanupTask instead.
|
// Use Session::exec() or DeferredSessionCleanupTask instead.
|
||||||
virtual ~Session() {};
|
virtual ~Session() {};
|
||||||
|
|
||||||
Q_INVOKABLE void exec(int displayOriginX, int displayOriginY);
|
Q_INVOKABLE void exec(QWindow* qtWindow);
|
||||||
|
|
||||||
static
|
static
|
||||||
void getDecoderInfo(SDL_Window* window,
|
void getDecoderInfo(SDL_Window* window,
|
||||||
@@ -78,6 +79,8 @@ private:
|
|||||||
|
|
||||||
IAudioRenderer* createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATION opusConfig);
|
IAudioRenderer* createAudioRenderer(const POPUS_MULTISTREAM_CONFIGURATION opusConfig);
|
||||||
|
|
||||||
|
bool initializeAudioRenderer();
|
||||||
|
|
||||||
bool testAudio(int audioConfiguration);
|
bool testAudio(int audioConfiguration);
|
||||||
|
|
||||||
int getAudioRendererCapabilities(int audioConfiguration);
|
int getAudioRendererCapabilities(int audioConfiguration);
|
||||||
@@ -166,8 +169,7 @@ private:
|
|||||||
bool m_AudioDisabled;
|
bool m_AudioDisabled;
|
||||||
bool m_AudioMuted;
|
bool m_AudioMuted;
|
||||||
Uint32 m_FullScreenFlag;
|
Uint32 m_FullScreenFlag;
|
||||||
int m_DisplayOriginX;
|
QWindow* m_QtWindow;
|
||||||
int m_DisplayOriginY;
|
|
||||||
bool m_ThreadedExec;
|
bool m_ThreadedExec;
|
||||||
bool m_UnexpectedTermination;
|
bool m_UnexpectedTermination;
|
||||||
SdlInputHandler* m_InputHandler;
|
SdlInputHandler* m_InputHandler;
|
||||||
@@ -184,7 +186,8 @@ private:
|
|||||||
|
|
||||||
OpusMSDecoder* m_OpusDecoder;
|
OpusMSDecoder* m_OpusDecoder;
|
||||||
IAudioRenderer* m_AudioRenderer;
|
IAudioRenderer* m_AudioRenderer;
|
||||||
OPUS_MULTISTREAM_CONFIGURATION m_AudioConfig;
|
OPUS_MULTISTREAM_CONFIGURATION m_ActiveAudioConfig;
|
||||||
|
OPUS_MULTISTREAM_CONFIGURATION m_OriginalAudioConfig;
|
||||||
int m_AudioSampleCount;
|
int m_AudioSampleCount;
|
||||||
Uint32 m_DropAudioEndTime;
|
Uint32 m_DropAudioEndTime;
|
||||||
|
|
||||||
|
|||||||
+157
-12
@@ -6,10 +6,67 @@
|
|||||||
#include <ApplicationServices/ApplicationServices.h>
|
#include <ApplicationServices/ApplicationServices.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WINDOWS
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
#include <sys/auxv.h>
|
||||||
|
|
||||||
|
#if defined(Q_PROCESSOR_ARM)
|
||||||
|
|
||||||
|
#ifndef HWCAP2_AES
|
||||||
|
#define HWCAP2_AES (1 << 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#elif defined(Q_PROCESSOR_RISCV)
|
||||||
|
|
||||||
|
#if __has_include(<sys/hwprobe.h>)
|
||||||
|
#include <sys/hwprobe.h>
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#if __has_include(<asm/hwprobe.h>)
|
||||||
|
#include <asm/hwprobe.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#else
|
||||||
|
#define __NR_riscv_hwprobe 258
|
||||||
|
struct riscv_hwprobe {
|
||||||
|
int64_t key;
|
||||||
|
uint64_t value;
|
||||||
|
};
|
||||||
|
#define RISCV_HWPROBE_KEY_IMA_EXT_0 4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// RISC-V Scalar AES [E]ncryption and [D]ecryption
|
||||||
|
#ifndef RISCV_HWPROBE_EXT_ZKND
|
||||||
|
#define RISCV_HWPROBE_EXT_ZKND (1 << 11)
|
||||||
|
#define RISCV_HWPROBE_EXT_ZKNE (1 << 12)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// RISC-V Vector AES
|
||||||
|
#ifndef RISCV_HWPROBE_EXT_ZVKNED
|
||||||
|
#define RISCV_HWPROBE_EXT_ZVKNED (1 << 21)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int __riscv_hwprobe(struct riscv_hwprobe *pairs, size_t pair_count,
|
||||||
|
size_t cpu_count, unsigned long *cpus,
|
||||||
|
unsigned int flags)
|
||||||
|
{
|
||||||
|
return syscall(__NR_riscv_hwprobe, pairs, pair_count, cpu_count, cpus, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
Uint32 StreamUtils::getPlatformWindowFlags()
|
Uint32 StreamUtils::getPlatformWindowFlags()
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_DARWIN
|
#if defined(Q_OS_DARWIN)
|
||||||
return SDL_WINDOW_METAL;
|
return SDL_WINDOW_METAL;
|
||||||
|
#elif defined(HAVE_LIBPLACEBO_VULKAN)
|
||||||
|
// We'll fall back to GL if Vulkan fails
|
||||||
|
return SDL_WINDOW_VULKAN;
|
||||||
#else
|
#else
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
@@ -92,7 +149,51 @@ int StreamUtils::getDisplayRefreshRate(SDL_Window* window)
|
|||||||
return mode.refresh_rate;
|
return mode.refresh_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode)
|
bool StreamUtils::hasFastAes()
|
||||||
|
{
|
||||||
|
#ifndef __has_builtin
|
||||||
|
#define __has_builtin(x) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (__has_builtin(__builtin_cpu_supports) || (defined(__GNUC__) && __GNUC__ >= 6)) && defined(Q_PROCESSOR_X86)
|
||||||
|
return __builtin_cpu_supports("aes");
|
||||||
|
#elif defined(__BUILTIN_CPU_SUPPORTS__) && defined(Q_PROCESSOR_POWER)
|
||||||
|
return __builtin_cpu_supports("vcrypto");
|
||||||
|
#elif defined(Q_OS_WINDOWS) && defined(Q_PROCESSOR_ARM)
|
||||||
|
return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE);
|
||||||
|
#elif defined(_MSC_VER) && defined(Q_PROCESSOR_X86)
|
||||||
|
int regs[4];
|
||||||
|
__cpuid(regs, 1);
|
||||||
|
return regs[2] & (1 << 25); // AES-NI
|
||||||
|
#elif defined(Q_OS_DARWIN)
|
||||||
|
// Everything that runs Catalina and later has AES-NI or ARMv8 crypto instructions
|
||||||
|
return true;
|
||||||
|
#elif defined(Q_OS_LINUX) && defined(Q_PROCESSOR_ARM) && QT_POINTER_SIZE == 4
|
||||||
|
return getauxval(AT_HWCAP2) & HWCAP2_AES;
|
||||||
|
#elif defined(Q_OS_LINUX) && defined(Q_PROCESSOR_ARM) && QT_POINTER_SIZE == 8
|
||||||
|
return getauxval(AT_HWCAP) & HWCAP_AES;
|
||||||
|
#elif defined(Q_PROCESSOR_RISCV)
|
||||||
|
riscv_hwprobe pairs[1] = {
|
||||||
|
{ RISCV_HWPROBE_KEY_IMA_EXT_0, 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
// If this syscall is not implemented, we'll get -ENOSYS
|
||||||
|
// and the value field will remain zero.
|
||||||
|
__riscv_hwprobe(pairs, SDL_arraysize(pairs), 0, nullptr, 0);
|
||||||
|
|
||||||
|
return (pairs[0].value & (RISCV_HWPROBE_EXT_ZKNE | RISCV_HWPROBE_EXT_ZKND)) ==
|
||||||
|
(RISCV_HWPROBE_EXT_ZKNE | RISCV_HWPROBE_EXT_ZKND) ||
|
||||||
|
(pairs[0].value & RISCV_HWPROBE_EXT_ZVKNED);
|
||||||
|
#elif QT_POINTER_SIZE == 4
|
||||||
|
#warning Unknown 32-bit platform. Assuming AES is slow on this CPU.
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
#warning Unknown 64-bit platform. Assuming AES is fast on this CPU.
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode, SDL_Rect* safeArea)
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_DARWIN
|
#ifdef Q_OS_DARWIN
|
||||||
#define MAX_DISPLAYS 16
|
#define MAX_DISPLAYS 16
|
||||||
@@ -100,9 +201,6 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode)
|
|||||||
uint32_t displayCount = 0;
|
uint32_t displayCount = 0;
|
||||||
CGGetActiveDisplayList(MAX_DISPLAYS, displayIds, &displayCount);
|
CGGetActiveDisplayList(MAX_DISPLAYS, displayIds, &displayCount);
|
||||||
if (displayIndex >= displayCount) {
|
if (displayIndex >= displayCount) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Too many displays: %d vs %d",
|
|
||||||
displayIndex, displayCount);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,20 +220,61 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
safeArea->x = 0;
|
||||||
|
safeArea->y = 0;
|
||||||
|
safeArea->w = mode->w;
|
||||||
|
safeArea->h = mode->h;
|
||||||
|
|
||||||
|
#if TARGET_CPU_ARM64
|
||||||
|
// Now that we found the native full-screen mode, let's look for one that matches along
|
||||||
|
// the width but not the height and we'll assume that's the safe area full-screen mode.
|
||||||
|
//
|
||||||
|
// There doesn't appear to be a CG API or flag that will tell us that a given mode
|
||||||
|
// is a "safe area" mode, so we have to use our own (brittle) heuristics. :(
|
||||||
|
//
|
||||||
|
// To avoid potential false positives, let's avoid checking for external displays, since
|
||||||
|
// we might have scenarios like a 1920x1200 display with an alternate 1920x1080 mode
|
||||||
|
// which would falsely trigger our notch detection here.
|
||||||
|
if (CGDisplayIsBuiltin(displayIds[displayIndex])) {
|
||||||
|
for (CFIndex i = 0; i < count; i++) {
|
||||||
|
auto cgMode = (CGDisplayModeRef)(CFArrayGetValueAtIndex(modeList, i));
|
||||||
|
auto cgModeWidth = static_cast<int>(CGDisplayModeGetWidth(cgMode));
|
||||||
|
auto cgModeHeight = static_cast<int>(CGDisplayModeGetHeight(cgMode));
|
||||||
|
|
||||||
|
// If the modes differ by more than 100, we'll assume it's not a notch mode
|
||||||
|
if (mode->w == cgModeWidth && mode->h != cgModeHeight && mode->h <= cgModeHeight + 100) {
|
||||||
|
safeArea->w = cgModeWidth;
|
||||||
|
safeArea->h = cgModeHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
CFRelease(modeList);
|
CFRelease(modeList);
|
||||||
|
|
||||||
// Now find the SDL mode that matches the CG native mode
|
// Special case for probing for notched displays prior to video subsystem initialization
|
||||||
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
// in Session::initialize() for Darwin only!
|
||||||
SDL_DisplayMode thisMode;
|
if (SDL_WasInit(SDL_INIT_VIDEO)) {
|
||||||
if (SDL_GetDisplayMode(displayIndex, i, &thisMode) == 0) {
|
// Now find the SDL mode that matches the CG native mode
|
||||||
if (thisMode.w == mode->w && thisMode.h == mode->h &&
|
for (int i = 0; i < SDL_GetNumDisplayModes(displayIndex); i++) {
|
||||||
|
SDL_DisplayMode thisMode;
|
||||||
|
if (SDL_GetDisplayMode(displayIndex, i, &thisMode) == 0) {
|
||||||
|
if (thisMode.w == mode->w && thisMode.h == mode->h &&
|
||||||
thisMode.refresh_rate >= mode->refresh_rate) {
|
thisMode.refresh_rate >= mode->refresh_rate) {
|
||||||
*mode = thisMode;
|
*mode = thisMode;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
SDL_assert(SDL_WasInit(SDL_INIT_VIDEO));
|
||||||
|
|
||||||
|
if (displayIndex >= SDL_GetNumVideoDisplays()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// We need to get the true display resolution without DPI scaling (since we use High DPI).
|
// We need to get the true display resolution without DPI scaling (since we use High DPI).
|
||||||
// Windows returns the real display resolution here, even if DPI scaling is enabled.
|
// Windows returns the real display resolution here, even if DPI scaling is enabled.
|
||||||
// macOS and Wayland report a resolution that includes the DPI scaling factor. Picking
|
// macOS and Wayland report a resolution that includes the DPI scaling factor. Picking
|
||||||
@@ -157,7 +296,13 @@ bool StreamUtils::getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
safeArea->x = 0;
|
||||||
|
safeArea->y = 0;
|
||||||
|
safeArea->w = mode->w;
|
||||||
|
safeArea->h = mode->h;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,8 +29,11 @@ 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 getNativeDesktopMode(int displayIndex, SDL_DisplayMode* mode, SDL_Rect* safeArea);
|
||||||
|
|
||||||
static
|
static
|
||||||
int getDisplayRefreshRate(SDL_Window* window);
|
int getDisplayRefreshRate(SDL_Window* window);
|
||||||
|
|
||||||
|
static
|
||||||
|
bool hasFastAes();
|
||||||
};
|
};
|
||||||
@@ -45,6 +45,21 @@ typedef struct _DECODER_PARAMETERS {
|
|||||||
bool testOnly;
|
bool testOnly;
|
||||||
} DECODER_PARAMETERS, *PDECODER_PARAMETERS;
|
} DECODER_PARAMETERS, *PDECODER_PARAMETERS;
|
||||||
|
|
||||||
|
#define WINDOW_STATE_CHANGE_SIZE 0x01
|
||||||
|
#define WINDOW_STATE_CHANGE_DISPLAY 0x02
|
||||||
|
|
||||||
|
typedef struct _WINDOW_STATE_CHANGE_INFO {
|
||||||
|
SDL_Window* window;
|
||||||
|
uint32_t stateChangeFlags;
|
||||||
|
|
||||||
|
// Populated if WINDOW_STATE_CHANGE_SIZE is set
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
|
||||||
|
// Populated if WINDOW_STATE_CHANGE_DISPLAY is set
|
||||||
|
int displayIndex;
|
||||||
|
} WINDOW_STATE_CHANGE_INFO, *PWINDOW_STATE_CHANGE_INFO;
|
||||||
|
|
||||||
class IVideoDecoder {
|
class IVideoDecoder {
|
||||||
public:
|
public:
|
||||||
virtual ~IVideoDecoder() {}
|
virtual ~IVideoDecoder() {}
|
||||||
@@ -59,4 +74,5 @@ public:
|
|||||||
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
virtual int submitDecodeUnit(PDECODE_UNIT du) = 0;
|
||||||
virtual void renderFrameOnMainThread() = 0;
|
virtual void renderFrameOnMainThread() = 0;
|
||||||
virtual void setHdrMode(bool enabled) = 0;
|
virtual void setHdrMode(bool enabled) = 0;
|
||||||
|
virtual bool notifyWindowChanged(PWINDOW_STATE_CHANGE_INFO info) = 0;
|
||||||
};
|
};
|
||||||
@@ -75,6 +75,8 @@ static_assert(sizeof(CSC_CONST_BUF) % 16 == 0, "Constant buffer sizes must be a
|
|||||||
|
|
||||||
D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
|
D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
|
||||||
: m_DecoderSelectionPass(decoderSelectionPass),
|
: m_DecoderSelectionPass(decoderSelectionPass),
|
||||||
|
m_DevicesWithFL11Support(0),
|
||||||
|
m_DevicesWithCodecSupport(0),
|
||||||
m_Factory(nullptr),
|
m_Factory(nullptr),
|
||||||
m_Device(nullptr),
|
m_Device(nullptr),
|
||||||
m_SwapChain(nullptr),
|
m_SwapChain(nullptr),
|
||||||
@@ -82,6 +84,7 @@ D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
|
|||||||
m_RenderTargetView(nullptr),
|
m_RenderTargetView(nullptr),
|
||||||
m_LastColorSpace(-1),
|
m_LastColorSpace(-1),
|
||||||
m_LastFullRange(false),
|
m_LastFullRange(false),
|
||||||
|
m_LastColorTrc(AVCOL_TRC_UNSPECIFIED),
|
||||||
m_AllowTearing(false),
|
m_AllowTearing(false),
|
||||||
m_VideoGenericPixelShader(nullptr),
|
m_VideoGenericPixelShader(nullptr),
|
||||||
m_VideoBt601LimPixelShader(nullptr),
|
m_VideoBt601LimPixelShader(nullptr),
|
||||||
@@ -90,8 +93,7 @@ D3D11VARenderer::D3D11VARenderer(int decoderSelectionPass)
|
|||||||
m_VideoTexture(nullptr),
|
m_VideoTexture(nullptr),
|
||||||
m_OverlayLock(0),
|
m_OverlayLock(0),
|
||||||
m_OverlayPixelShader(nullptr),
|
m_OverlayPixelShader(nullptr),
|
||||||
m_HwDeviceContext(nullptr),
|
m_HwDeviceContext(nullptr)
|
||||||
m_HwFramesContext(nullptr)
|
|
||||||
{
|
{
|
||||||
RtlZeroMemory(m_OverlayVertexBuffers, sizeof(m_OverlayVertexBuffers));
|
RtlZeroMemory(m_OverlayVertexBuffers, sizeof(m_OverlayVertexBuffers));
|
||||||
RtlZeroMemory(m_OverlayTextures, sizeof(m_OverlayTextures));
|
RtlZeroMemory(m_OverlayTextures, sizeof(m_OverlayTextures));
|
||||||
@@ -137,10 +139,6 @@ D3D11VARenderer::~D3D11VARenderer()
|
|||||||
SAFE_COM_RELEASE(m_RenderTargetView);
|
SAFE_COM_RELEASE(m_RenderTargetView);
|
||||||
SAFE_COM_RELEASE(m_SwapChain);
|
SAFE_COM_RELEASE(m_SwapChain);
|
||||||
|
|
||||||
if (m_HwFramesContext != nullptr) {
|
|
||||||
av_buffer_unref(&m_HwFramesContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force destruction of the swapchain immediately
|
// Force destruction of the swapchain immediately
|
||||||
if (m_DeviceContext != nullptr) {
|
if (m_DeviceContext != nullptr) {
|
||||||
m_DeviceContext->ClearState();
|
m_DeviceContext->ClearState();
|
||||||
@@ -164,6 +162,7 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter
|
|||||||
bool success = false;
|
bool success = false;
|
||||||
IDXGIAdapter1* adapter = nullptr;
|
IDXGIAdapter1* adapter = nullptr;
|
||||||
DXGI_ADAPTER_DESC1 adapterDesc;
|
DXGI_ADAPTER_DESC1 adapterDesc;
|
||||||
|
D3D_FEATURE_LEVEL featureLevel;
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
SDL_assert(m_Device == nullptr);
|
SDL_assert(m_Device == nullptr);
|
||||||
@@ -213,7 +212,7 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter
|
|||||||
0,
|
0,
|
||||||
D3D11_SDK_VERSION,
|
D3D11_SDK_VERSION,
|
||||||
&m_Device,
|
&m_Device,
|
||||||
nullptr,
|
&featureLevel,
|
||||||
&m_DeviceContext);
|
&m_DeviceContext);
|
||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -221,6 +220,11 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter
|
|||||||
hr);
|
hr);
|
||||||
goto Exit;
|
goto Exit;
|
||||||
}
|
}
|
||||||
|
else if (featureLevel >= D3D_FEATURE_LEVEL_11_0) {
|
||||||
|
// Remember that we found a non-software D3D11 devices with support for
|
||||||
|
// feature level 11.0 or later (Fermi, Terascale 2, or Ivy Bridge and later)
|
||||||
|
m_DevicesWithFL11Support++;
|
||||||
|
}
|
||||||
|
|
||||||
if (!checkDecoderSupport(adapter)) {
|
if (!checkDecoderSupport(adapter)) {
|
||||||
m_DeviceContext->Release();
|
m_DeviceContext->Release();
|
||||||
@@ -230,6 +234,10 @@ bool D3D11VARenderer::createDeviceByAdapterIndex(int adapterIndex, bool* adapter
|
|||||||
|
|
||||||
goto Exit;
|
goto Exit;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// Remember that we found a device with support for decoding this codec
|
||||||
|
m_DevicesWithCodecSupport++;
|
||||||
|
}
|
||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
|
|
||||||
@@ -421,10 +429,6 @@ bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Surfaces must be 16 pixel aligned for H.264 and 128 pixel aligned for everything else
|
|
||||||
// https://github.com/FFmpeg/FFmpeg/blob/a234e5cd80224c95a205c1f3e297d8c04a1374c3/libavcodec/dxva2.c#L609-L616
|
|
||||||
m_TextureAlignment = (params->videoFormat & VIDEO_FORMAT_MASK_H264) ? 16 : 128;
|
|
||||||
|
|
||||||
if (!setupRenderingResources()) {
|
if (!setupRenderingResources()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -458,43 +462,9 @@ bool D3D11VARenderer::initialize(PDECODER_PARAMETERS params)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// Create our video texture and SRVs
|
||||||
m_HwFramesContext = av_hwframe_ctx_alloc(m_HwDeviceContext);
|
if (!setupVideoTexture()) {
|
||||||
if (!m_HwFramesContext) {
|
return false;
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to allocate D3D11VA frame context");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
AVHWFramesContext* framesContext = (AVHWFramesContext*)m_HwFramesContext->data;
|
|
||||||
|
|
||||||
// We require NV12 or P010 textures for our shader
|
|
||||||
framesContext->format = AV_PIX_FMT_D3D11;
|
|
||||||
framesContext->sw_format = (params->videoFormat & VIDEO_FORMAT_MASK_10BIT) ?
|
|
||||||
AV_PIX_FMT_P010 : AV_PIX_FMT_NV12;
|
|
||||||
|
|
||||||
framesContext->width = FFALIGN(params->width, m_TextureAlignment);
|
|
||||||
framesContext->height = FFALIGN(params->height, m_TextureAlignment);
|
|
||||||
|
|
||||||
// We can have up to 16 reference frames plus a working surface
|
|
||||||
framesContext->initial_pool_size = 17;
|
|
||||||
|
|
||||||
AVD3D11VAFramesContext* d3d11vaFramesContext = (AVD3D11VAFramesContext*)framesContext->hwctx;
|
|
||||||
|
|
||||||
d3d11vaFramesContext->BindFlags = D3D11_BIND_DECODER;
|
|
||||||
|
|
||||||
int err = av_hwframe_ctx_init(m_HwFramesContext);
|
|
||||||
if (err < 0) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to initialize D3D11VA frame context: %d",
|
|
||||||
err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create our video texture and SRVs
|
|
||||||
if (!setupVideoTexture()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -510,87 +480,6 @@ bool D3D11VARenderer::prepareDecoderContext(AVCodecContext* context, AVDictionar
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool D3D11VARenderer::prepareDecoderContextInGetFormat(AVCodecContext *context, AVPixelFormat)
|
|
||||||
{
|
|
||||||
// hw_frames_ctx must be initialized in ffGetFormat().
|
|
||||||
context->hw_frames_ctx = av_buffer_ref(m_HwFramesContext);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void D3D11VARenderer::setHdrMode(bool enabled)
|
|
||||||
{
|
|
||||||
HRESULT hr;
|
|
||||||
|
|
||||||
// According to MSDN, we need to lock the context even if we're just using DXGI functions
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-render-multi-thread-intro
|
|
||||||
lockContext(this);
|
|
||||||
|
|
||||||
if (enabled) {
|
|
||||||
DXGI_HDR_METADATA_HDR10 hdr10Metadata;
|
|
||||||
SS_HDR_METADATA sunshineHdrMetadata;
|
|
||||||
|
|
||||||
// Sunshine will have HDR metadata but GFE will not
|
|
||||||
if (!LiGetHdrMetadata(&sunshineHdrMetadata)) {
|
|
||||||
RtlZeroMemory(&sunshineHdrMetadata, sizeof(sunshineHdrMetadata));
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr10Metadata.RedPrimary[0] = sunshineHdrMetadata.displayPrimaries[0].x;
|
|
||||||
hdr10Metadata.RedPrimary[1] = sunshineHdrMetadata.displayPrimaries[0].y;
|
|
||||||
hdr10Metadata.GreenPrimary[0] = sunshineHdrMetadata.displayPrimaries[1].x;
|
|
||||||
hdr10Metadata.GreenPrimary[1] = sunshineHdrMetadata.displayPrimaries[1].y;
|
|
||||||
hdr10Metadata.BluePrimary[0] = sunshineHdrMetadata.displayPrimaries[2].x;
|
|
||||||
hdr10Metadata.BluePrimary[1] = sunshineHdrMetadata.displayPrimaries[2].y;
|
|
||||||
hdr10Metadata.WhitePoint[0] = sunshineHdrMetadata.whitePoint.x;
|
|
||||||
hdr10Metadata.WhitePoint[1] = sunshineHdrMetadata.whitePoint.y;
|
|
||||||
hdr10Metadata.MaxMasteringLuminance = sunshineHdrMetadata.maxDisplayLuminance;
|
|
||||||
hdr10Metadata.MinMasteringLuminance = sunshineHdrMetadata.minDisplayLuminance;
|
|
||||||
hdr10Metadata.MaxContentLightLevel = sunshineHdrMetadata.maxContentLightLevel;
|
|
||||||
hdr10Metadata.MaxFrameAverageLightLevel = sunshineHdrMetadata.maxFrameAverageLightLevel;
|
|
||||||
|
|
||||||
hr = m_SwapChain->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(hdr10Metadata), &hdr10Metadata);
|
|
||||||
if (SUCCEEDED(hr)) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Set display HDR mode: enabled");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to enter HDR mode: %x",
|
|
||||||
hr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch to Rec 2020 PQ (SMPTE ST 2084) colorspace for HDR10 rendering
|
|
||||||
hr = m_SwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
|
|
||||||
if (FAILED(hr)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"IDXGISwapChain::SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) failed: %x",
|
|
||||||
hr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Restore default sRGB colorspace
|
|
||||||
hr = m_SwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
|
|
||||||
if (FAILED(hr)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"IDXGISwapChain::SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) failed: %x",
|
|
||||||
hr);
|
|
||||||
}
|
|
||||||
|
|
||||||
hr = m_SwapChain->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_NONE, 0, nullptr);
|
|
||||||
if (SUCCEEDED(hr)) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Set display HDR mode: disabled");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to exit HDR mode: %x",
|
|
||||||
hr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unlockContext(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void D3D11VARenderer::renderFrame(AVFrame* frame)
|
void D3D11VARenderer::renderFrame(AVFrame* frame)
|
||||||
{
|
{
|
||||||
// Acquire the context lock for rendering to prevent concurrent
|
// Acquire the context lock for rendering to prevent concurrent
|
||||||
@@ -629,8 +518,33 @@ void D3D11VARenderer::renderFrame(AVFrame* frame)
|
|||||||
flags = 0;
|
flags = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HRESULT hr;
|
||||||
|
|
||||||
|
if (frame->color_trc != m_LastColorTrc) {
|
||||||
|
if (frame->color_trc == AVCOL_TRC_SMPTE2084) {
|
||||||
|
// Switch to Rec 2020 PQ (SMPTE ST 2084) colorspace for HDR10 rendering
|
||||||
|
hr = m_SwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"IDXGISwapChain::SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) failed: %x",
|
||||||
|
hr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Restore default sRGB colorspace
|
||||||
|
hr = m_SwapChain->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"IDXGISwapChain::SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709) failed: %x",
|
||||||
|
hr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_LastColorTrc = frame->color_trc;
|
||||||
|
}
|
||||||
|
|
||||||
// Present according to the decoder parameters
|
// Present according to the decoder parameters
|
||||||
HRESULT hr = m_SwapChain->Present(0, flags);
|
hr = m_SwapChain->Present(0, flags);
|
||||||
|
|
||||||
// Release the context lock
|
// Release the context lock
|
||||||
unlockContext(this);
|
unlockContext(this);
|
||||||
@@ -813,7 +727,8 @@ void D3D11VARenderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
|||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type);
|
SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type);
|
||||||
if (newSurface == nullptr && Session::get()->getOverlayManager().isOverlayEnabled(type)) {
|
bool overlayEnabled = Session::get()->getOverlayManager().isOverlayEnabled(type);
|
||||||
|
if (newSurface == nullptr && overlayEnabled) {
|
||||||
// The overlay is enabled and there is no new surface. Leave the old texture alone.
|
// The overlay is enabled and there is no new surface. Leave the old texture alone.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -834,7 +749,7 @@ void D3D11VARenderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
|||||||
SAFE_COM_RELEASE(oldVertexBuffer);
|
SAFE_COM_RELEASE(oldVertexBuffer);
|
||||||
|
|
||||||
// If the overlay is disabled, we're done
|
// If the overlay is disabled, we're done
|
||||||
if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) {
|
if (!overlayEnabled) {
|
||||||
SDL_FreeSurface(newSurface);
|
SDL_FreeSurface(newSurface);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1094,6 +1009,29 @@ bool D3D11VARenderer::needsTestFrame()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IFFmpegRenderer::InitFailureReason D3D11VARenderer::getInitFailureReason()
|
||||||
|
{
|
||||||
|
// In the specific case where we found at least one D3D11 hardware device but none of the
|
||||||
|
// enumerated devices have support for the specified codec, tell the FFmpeg decoder not to
|
||||||
|
// bother trying other hwaccels. We don't want to try loading D3D9 if the device doesn't
|
||||||
|
// even have hardware support for the codec.
|
||||||
|
//
|
||||||
|
// NB: We use feature level 11.0 support as a gate here because we want to avoid returning
|
||||||
|
// this failure reason in cases where we might have an extremely old GPU with support for
|
||||||
|
// DXVA2 on D3D9 but not D3D11VA on D3D11. I'm unsure if any such drivers/hardware exists,
|
||||||
|
// but better be safe than sorry.
|
||||||
|
//
|
||||||
|
// NB2: We're also assuming that no GPU exists which lacks any D3D11 driver but has drivers
|
||||||
|
// for non-DX APIs like Vulkan. I believe this is a Windows Logo requirement so it should be
|
||||||
|
// safe to assume.
|
||||||
|
if (m_DevicesWithFL11Support != 0 && m_DevicesWithCodecSupport == 0) {
|
||||||
|
return InitFailureReason::NoHardwareSupport;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return InitFailureReason::Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void D3D11VARenderer::lockContext(void *lock_ctx)
|
void D3D11VARenderer::lockContext(void *lock_ctx)
|
||||||
{
|
{
|
||||||
auto me = (D3D11VARenderer*)lock_ctx;
|
auto me = (D3D11VARenderer*)lock_ctx;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
#include "pacer/pacer.h"
|
|
||||||
|
|
||||||
#include <d3d11_1.h>
|
#include <d3d11_1.h>
|
||||||
#include <dxgi1_5.h>
|
#include <dxgi1_5.h>
|
||||||
@@ -17,13 +16,12 @@ public:
|
|||||||
virtual ~D3D11VARenderer() override;
|
virtual ~D3D11VARenderer() override;
|
||||||
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
virtual bool initialize(PDECODER_PARAMETERS params) override;
|
||||||
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary**) override;
|
virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary**) override;
|
||||||
virtual bool prepareDecoderContextInGetFormat(AVCodecContext* context, AVPixelFormat pixelFormat) override;
|
|
||||||
virtual void renderFrame(AVFrame* frame) override;
|
virtual void renderFrame(AVFrame* frame) override;
|
||||||
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
virtual void notifyOverlayUpdated(Overlay::OverlayType) override;
|
||||||
virtual void setHdrMode(bool enabled) override;
|
|
||||||
virtual int getRendererAttributes() override;
|
virtual int getRendererAttributes() override;
|
||||||
virtual int getDecoderCapabilities() override;
|
virtual int getDecoderCapabilities() override;
|
||||||
virtual bool needsTestFrame() override;
|
virtual bool needsTestFrame() override;
|
||||||
|
virtual InitFailureReason getInitFailureReason() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void lockContext(void* lock_ctx);
|
static void lockContext(void* lock_ctx);
|
||||||
@@ -38,6 +36,8 @@ private:
|
|||||||
bool createDeviceByAdapterIndex(int adapterIndex, bool* adapterNotFound = nullptr);
|
bool createDeviceByAdapterIndex(int adapterIndex, bool* adapterNotFound = nullptr);
|
||||||
|
|
||||||
int m_DecoderSelectionPass;
|
int m_DecoderSelectionPass;
|
||||||
|
int m_DevicesWithFL11Support;
|
||||||
|
int m_DevicesWithCodecSupport;
|
||||||
|
|
||||||
IDXGIFactory5* m_Factory;
|
IDXGIFactory5* m_Factory;
|
||||||
ID3D11Device* m_Device;
|
ID3D11Device* m_Device;
|
||||||
@@ -47,11 +47,11 @@ private:
|
|||||||
SDL_mutex* m_ContextLock;
|
SDL_mutex* m_ContextLock;
|
||||||
|
|
||||||
DECODER_PARAMETERS m_DecoderParams;
|
DECODER_PARAMETERS m_DecoderParams;
|
||||||
int m_TextureAlignment;
|
|
||||||
int m_DisplayWidth;
|
int m_DisplayWidth;
|
||||||
int m_DisplayHeight;
|
int m_DisplayHeight;
|
||||||
int m_LastColorSpace;
|
int m_LastColorSpace;
|
||||||
bool m_LastFullRange;
|
bool m_LastFullRange;
|
||||||
|
AVColorTransferCharacteristic m_LastColorTrc;
|
||||||
|
|
||||||
bool m_AllowTearing;
|
bool m_AllowTearing;
|
||||||
|
|
||||||
@@ -70,6 +70,5 @@ private:
|
|||||||
ID3D11PixelShader* m_OverlayPixelShader;
|
ID3D11PixelShader* m_OverlayPixelShader;
|
||||||
|
|
||||||
AVBufferRef* m_HwDeviceContext;
|
AVBufferRef* m_HwDeviceContext;
|
||||||
AVBufferRef* m_HwFramesContext;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "drm.h"
|
#include "drm.h"
|
||||||
|
#include "string.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <libavutil/hwcontext_drm.h>
|
#include <libavutil/hwcontext_drm.h>
|
||||||
@@ -11,7 +12,6 @@ extern "C" {
|
|||||||
|
|
||||||
#include <libdrm/drm_fourcc.h>
|
#include <libdrm/drm_fourcc.h>
|
||||||
#include <linux/dma-buf.h>
|
#include <linux/dma-buf.h>
|
||||||
#include <sys/ioctl.h>
|
|
||||||
|
|
||||||
// Special Rockchip type
|
// Special Rockchip type
|
||||||
#ifndef DRM_FORMAT_NA12
|
#ifndef DRM_FORMAT_NA12
|
||||||
@@ -158,7 +158,8 @@ bool DrmRenderer::prepareDecoderContext(AVCodecContext* context, AVDictionary**
|
|||||||
{
|
{
|
||||||
// The out-of-tree LibreELEC patches use this option to control the type of the V4L2
|
// The out-of-tree LibreELEC patches use this option to control the type of the V4L2
|
||||||
// buffers that we get back. We only support NV12 buffers now.
|
// buffers that we get back. We only support NV12 buffers now.
|
||||||
av_dict_set_int(options, "pixel_format", AV_PIX_FMT_NV12, 0);
|
if(strstr(context->codec->name, "_v4l2") != NULL)
|
||||||
|
av_dict_set_int(options, "pixel_format", AV_PIX_FMT_NV12, 0);
|
||||||
|
|
||||||
// This option controls the pixel format for the h264_omx and hevc_omx decoders
|
// This option controls the pixel format for the h264_omx and hevc_omx decoders
|
||||||
// used by the JH7110 multimedia stack. This decoder gives us software frames,
|
// used by the JH7110 multimedia stack. This decoder gives us software frames,
|
||||||
@@ -844,7 +845,7 @@ bool DrmRenderer::mapSoftwareFrame(AVFrame *frame, AVDRMFrameDescriptor *mappedF
|
|||||||
// Prepare to write to the dumb buffer from the CPU
|
// Prepare to write to the dumb buffer from the CPU
|
||||||
struct dma_buf_sync sync;
|
struct dma_buf_sync sync;
|
||||||
sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE;
|
sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE;
|
||||||
ioctl(drmFrame->primeFd, DMA_BUF_IOCTL_SYNC, &sync);
|
drmIoctl(drmFrame->primeFd, DMA_BUF_IOCTL_SYNC, &sync);
|
||||||
|
|
||||||
int lastPlaneSize = 0;
|
int lastPlaneSize = 0;
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
@@ -898,7 +899,7 @@ bool DrmRenderer::mapSoftwareFrame(AVFrame *frame, AVDRMFrameDescriptor *mappedF
|
|||||||
|
|
||||||
// End the CPU write to the dumb buffer
|
// End the CPU write to the dumb buffer
|
||||||
sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE;
|
sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE;
|
||||||
ioctl(drmFrame->primeFd, DMA_BUF_IOCTL_SYNC, &sync);
|
drmIoctl(drmFrame->primeFd, DMA_BUF_IOCTL_SYNC, &sync);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = true;
|
ret = true;
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0xD2,0x5C,0x15,0xB3,0xD6);
|
DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0xD2,0x5C,0x15,0xB3,0xD6);
|
||||||
DEFINE_GUID(DXVA2_ModeAV1_VLD_Profile0,0xb8be4ccb,0xcf53,0x46ba,0x8d,0x59,0xd6,0xb8,0xa6,0xda,0x5d,0x2a);
|
DEFINE_GUID(DXVA2_ModeAV1_VLD_Profile0,0xb8be4ccb,0xcf53,0x46ba,0x8d,0x59,0xd6,0xb8,0xa6,0xda,0x5d,0x2a);
|
||||||
|
|
||||||
|
// This was incorrectly removed from public headers in FFmpeg 7.0
|
||||||
|
#ifndef FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO
|
||||||
|
#define FF_DXVA2_WORKAROUND_INTEL_CLEARVIDEO 2
|
||||||
|
#endif
|
||||||
|
|
||||||
#define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); }
|
#define SAFE_COM_RELEASE(x) if (x) { (x)->Release(); }
|
||||||
|
|
||||||
typedef struct _VERTEX
|
typedef struct _VERTEX
|
||||||
@@ -447,13 +452,6 @@ bool DXVA2Renderer::initializeQuirksForAdapter(IDirect3D9Ex* d3d9ex, int adapter
|
|||||||
// For other GPUs, we'll avoid populating it as was our previous behavior.
|
// For other GPUs, we'll avoid populating it as was our previous behavior.
|
||||||
m_DeviceQuirks |= DXVA2_QUIRK_SET_DEST_FORMAT;
|
m_DeviceQuirks |= DXVA2_QUIRK_SET_DEST_FORMAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tag this display device if it has a WDDM 2.0+ driver for the decoder selection logic
|
|
||||||
if (HIWORD(id.DriverVersion.HighPart) >= 20) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Detected WDDM 2.0 or later display driver");
|
|
||||||
m_DeviceQuirks |= DXVA2_QUIRK_WDDM_20_PLUS;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -568,49 +566,6 @@ bool DXVA2Renderer::initializeDevice(SDL_Window* window, bool enableVsync)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a WDDM 2.0 or later display driver, prefer the D3D11VA renderer
|
|
||||||
// in all of the following cases:
|
|
||||||
// - Multi-GPU systems
|
|
||||||
// - Windowed and borderless windowed modes
|
|
||||||
// - Full-screen exclusive with V-sync off
|
|
||||||
//
|
|
||||||
// D3D11VA is better in this case because it can enable tearing in non-FSE
|
|
||||||
// modes when the user has V-Sync disabled. In non-FSE V-Sync cases, D3D11VA
|
|
||||||
// provides lower display latency on systems that support Independent Flip
|
|
||||||
// in windowed mode. When using D3D9, DWM will not promote us to IFlip unless
|
|
||||||
// we're full-screen (exclusive or not).
|
|
||||||
//
|
|
||||||
// We prefer D3D11VA in FSE multi-GPU cases due to a plethora of issues with
|
|
||||||
// D3D9Ex PresentEx()/D3DPRESENT_DONOTWAIT on hybrid graphics systems (See
|
|
||||||
// issues #235, #240, #386, and #951 on GitHub). Clearly this codepath is not
|
|
||||||
// well tested by Microsoft or GPU vendors, so stick to the more common
|
|
||||||
// D3D11-based renderer which is much more likely to behave.
|
|
||||||
//
|
|
||||||
// NB: The reason we only do this for WDDM 2.0 and later is because older
|
|
||||||
// AMD drivers (such as those for the HD 5570) render garbage when using
|
|
||||||
// the D3D11VA renderer.
|
|
||||||
if (m_DecoderSelectionPass == 0 &&
|
|
||||||
(m_DeviceQuirks & DXVA2_QUIRK_WDDM_20_PLUS)) {
|
|
||||||
if (!((SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN)) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Defaulting to D3D11VA for non-FSE mode");
|
|
||||||
d3d9ex->Release();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (m_DeviceQuirks & DXVA2_QUIRK_MULTI_GPU) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Defaulting to D3D11VA for multi-GPU FSE mode");
|
|
||||||
d3d9ex->Release();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (!enableVsync) {
|
|
||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Defaulting to D3D11VA for FSE V-Sync Off mode");
|
|
||||||
d3d9ex->Release();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
D3DCAPS9 deviceCaps;
|
D3DCAPS9 deviceCaps;
|
||||||
d3d9ex->GetDeviceCaps(adapterIndex, D3DDEVTYPE_HAL, &deviceCaps);
|
d3d9ex->GetDeviceCaps(adapterIndex, D3DDEVTYPE_HAL, &deviceCaps);
|
||||||
|
|
||||||
@@ -844,7 +799,8 @@ void DXVA2Renderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
|||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type);
|
SDL_Surface* newSurface = Session::get()->getOverlayManager().getUpdatedOverlaySurface(type);
|
||||||
if (newSurface == nullptr && Session::get()->getOverlayManager().isOverlayEnabled(type)) {
|
bool overlayEnabled = Session::get()->getOverlayManager().isOverlayEnabled(type);
|
||||||
|
if (newSurface == nullptr && overlayEnabled) {
|
||||||
// The overlay is enabled and there is no new surface. Leave the old texture alone.
|
// The overlay is enabled and there is no new surface. Leave the old texture alone.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -861,7 +817,7 @@ void DXVA2Renderer::notifyOverlayUpdated(Overlay::OverlayType type)
|
|||||||
SAFE_COM_RELEASE(oldVertexBuffer);
|
SAFE_COM_RELEASE(oldVertexBuffer);
|
||||||
|
|
||||||
// If the overlay is disabled, we're done
|
// If the overlay is disabled, we're done
|
||||||
if (!Session::get()->getOverlayManager().isOverlayEnabled(type)) {
|
if (!overlayEnabled) {
|
||||||
SDL_FreeSurface(newSurface);
|
SDL_FreeSurface(newSurface);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ private:
|
|||||||
|
|
||||||
#define DXVA2_QUIRK_NO_VP 0x01
|
#define DXVA2_QUIRK_NO_VP 0x01
|
||||||
#define DXVA2_QUIRK_SET_DEST_FORMAT 0x02
|
#define DXVA2_QUIRK_SET_DEST_FORMAT 0x02
|
||||||
#define DXVA2_QUIRK_WDDM_20_PLUS 0x04
|
#define DXVA2_QUIRK_WDDM_20_PLUS 0x04 // Unused
|
||||||
#define DXVA2_QUIRK_MULTI_GPU 0x08
|
#define DXVA2_QUIRK_MULTI_GPU 0x08
|
||||||
int m_DeviceQuirks;
|
int m_DeviceQuirks;
|
||||||
};
|
};
|
||||||
Loaded 100 of 136 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user