Compare commits

...
239 Commits
Author SHA1 Message Date
Cameron Gutman 394f28339e Update v4.3.1 changelog 2022-11-02 20:43:25 -05:00
Cameron Gutman 411998d4e3 Implement detection of local IPv6 addresses 2022-11-02 20:35:41 -05:00
Cameron Gutman 371906445e Prepare for v4.3.1 2022-11-02 19:44:44 -05:00
Cameron Gutman 34d91e58c0 Rerun lupdate and lrelease 2022-11-02 19:34:47 -05:00
Cameron Gutman f3f25bec7a Mark unfinished translation in zh_cn 2022-11-02 19:34:14 -05:00
Cameron Gutman 08d74bea75 Merge remote-tracking branch 'origin/weblate' 2022-11-02 19:30:25 -05:00
Dominik Chrástecký 63f917b317 Translated using Weblate (Czech)
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/cs/
2022-11-03 01:28:51 +01:00
Cameron Gutman 448633ec3c Fix some log messages with extra spaces 2022-11-02 19:25:44 -05:00
Cameron Gutman 09ce3c9ce4 Use CRLF line endings for the log file on Windows 2022-11-02 19:19:00 -05:00
Cameron Gutman 8fcc98102b Limit log by size rather than line count 2022-11-02 19:18:22 -05:00
Cameron Gutman e14075b464 Update moonlight-common-c to fix audio FEC OOS bug 2022-11-01 23:47:51 -05:00
Howard Wu ca89d14ec8 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.5% (202 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2022-11-02 03:03:38 +01:00
Cameron Gutman 3f51998065 Start the streaming window maximized if the GUI was maximized
Fixes #889
2022-10-30 11:56:15 -05:00
이정희 6e5b6a1a5c Translated using Weblate (Korean)
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2022-10-30 15:02:46 +01:00
sanhoe 660f6c5bbf Translated using Weblate (Korean)
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2022-10-29 06:02:07 +02:00
Cameron Gutman 5034bd72ed Bump v4.3.0 release date 2022-10-27 19:44:58 -05:00
Cameron Gutman 5b7c144796 Add workaround for DWM dark mode bug on Win10 2022-10-27 19:26:51 -05:00
Cameron Gutman e45b6b9b85 Avoid white flash when starting streaming on Windows 2022-10-27 18:18:47 -05:00
Cameron Gutman 245f242fb8 Add support for dark mode on Windows
Fixes #886
2022-10-27 18:12:26 -05:00
Cameron Gutman a6fccf93d1 Use a separate texture for rendering to avoid blending in the alignment padding
Fixes #885
2022-10-26 21:48:55 -05:00
Cameron Gutman 3dcab90752 Fix appdata for v4.3.0 2022-10-25 23:50:33 -05:00
Cameron Gutman 30a5f405f5 Enable HEVC RFI for the FFmpeg software decoder 2022-10-25 23:19:19 -05:00
Cameron Gutman bad1c9628e Prepare for v4.3.0 2022-10-25 23:11:12 -05:00
Cameron Gutman 1ab7bca43f Update SDL in AppImage 2022-10-25 22:47:29 -05:00
Cameron Gutman 7d0a53773c Rerun lupdate and lrelease 2022-10-25 22:45:03 -05:00
Cameron Gutman 789236ee8f Merge remote-tracking branch 'origin/weblate' 2022-10-25 22:44:21 -05:00
weng weng 21cba0b080 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2022-10-26 05:43:12 +02:00
Cameron Gutman 31eb0891e5 Update moonlight-common-c for improved audio FEC recovery 2022-10-25 22:42:12 -05:00
Cameron Gutman a13f8e7937 Ensure delayed flushes complete during shutdown 2022-10-25 02:43:55 -05:00
Cameron Gutman 5bfe56472f Fix build 2022-10-25 02:08:46 -05:00
Cameron Gutman 16a9ed682e Use a single flush thread for ComputerManager
Some QSettings backends don't like concurrent writes and using a
single thread improves write batching behavior.
2022-10-25 02:07:16 -05:00
Cameron Gutman 59cae610d2 Clarify encoding resolution limit comment 2022-10-25 01:24:57 -05:00
Cameron Gutman 29ac7893d7 Don't enforce the NVENC H.264 resolution limit on Sunshine hosts
Closes #882
2022-10-25 01:18:10 -05:00
Cameron Gutman c8aeca8ace Add an environment variable opt-out for the gamepad quit combo
See #870
2022-10-25 00:27:40 -05:00
Cameron Gutman 3da505c4c6 Minor perf optimization suggested by Clazy 2022-10-25 00:26:03 -05:00
Cameron Gutman 06bc05b708 Rerun lupdate and lrelease 2022-10-25 00:14:22 -05:00
Cameron Gutman a3dc9593c2 Merge remote-tracking branch 'origin/weblate' 2022-10-25 00:10:40 -05:00
Cameron Gutman 18a271a0ec Update moonlight-common-c for improved audio FEC recovery 2022-10-25 00:10:29 -05:00
Cameron Gutman cebd3bf751 Save hosts on a separate thread
QSettings on macOS Ventura performs horribly when saving a lot of values
(like several hosts or a host with a large app list). This change considerably
improves UI performance by moving the long operation off the main thread.
2022-10-25 00:07:07 -05:00
Cameron Gutman cb04925c6e Optimize locking in ComputerManager to reduce reader contention 2022-10-25 00:07:07 -05:00
Cameron Gutman b7a340bd00 Improve locking in NvComputer class 2022-10-24 23:53:02 -05:00
Cameron Gutman 2a0f837120 Stop issuing further HTTP requests when quitting 2022-10-24 23:46:57 -05:00
Cameron Gutman d00bdff495 Update SDL and FFmpeg for AppImage 2022-10-23 16:59:54 -05:00
Cameron Gutman 7e9c977b3d Update moonlight-common-c 2022-10-23 16:55:52 -05:00
reloxx13 1629151528 Translated using Weblate (German)
Currently translated at 100.0% (203 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2022-10-21 20:05:49 +02:00
reloxx13 24295197bd Translated using Weblate (German)
Currently translated at 98.0% (199 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2022-10-20 19:02:35 +02:00
Eric 807dd84daa Translated using Weblate (Chinese (Simplified))
Currently translated at 98.5% (200 of 203 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2022-10-18 13:05:01 +02:00
Cameron Gutman 17848c097c Don't switch between GLX and EGL if EGL worked 2022-10-16 22:57:08 -05:00
Cameron Gutman 563b760d31 Update packages for recent versions of Ubuntu 2022-10-16 22:23:27 -05:00
Cameron Gutman 8465a27a97 Deprioritize VAAPI for Gallium and Nvidia drivers and work around RFI latency bug 2022-10-16 21:51:10 -05:00
Cameron Gutman 255cbd1e7e Fix building with SDL 2.0.20 or earlier 2022-10-15 15:36:09 -05:00
Cameron Gutman 7717749b72 Reduce max queued frames to avoid exhausting the decoder buffer pool 2022-10-15 14:34:42 -05:00
Cameron Gutman 9e4337babf Revert "Increase the number of decoder surfaces for DXVA2/D3D11VA"
This reverts commit 02c781b8f4.
2022-10-15 14:32:12 -05:00
Cameron Gutman 6771877a84 Make scaleSourceToDestinationSurface() idempotent
Deviation between scaling done during window sizing and renderer
viewport configuration can lead to small errors that create visible
artifacts such as a single pixel pillarbox/letterbox on one side.
2022-10-15 13:12:18 -05:00
Cameron Gutman a860bd8dd5 Enable HEVC RFI on Tegra 2022-10-14 23:41:09 -05:00
Cameron Gutman 704ce4ff0c Add environment variable for overriding decoder capabilities 2022-10-14 23:39:49 -05:00
Cameron Gutman 21edcc8113 Add workaround for macOS fullscreen exit bug 2022-10-14 23:01:55 -05:00
Cameron Gutman 125738f13d Cancel overlay update callbacks before destroying VTRenderer 2022-10-14 22:49:33 -05:00
Cameron Gutman 02c781b8f4 Increase the number of decoder surfaces for DXVA2/D3D11VA
We forgot to take into account frames buffered within Moonlight itself
2022-10-14 21:20:35 -05:00
Cameron Gutman 453fe6aea0 Ensure the target window size is always even 2022-10-14 21:09:32 -05:00
Cameron Gutman e0fb7dfd14 Rework window creation, sizing, and fullscreen transition logic
- Fixes window position/size being lost when toggling full-screen
- Fixes initial window size not respecting stream aspect ratio
2022-10-14 20:24:12 -05:00
Cameron Gutman c4012225c2 Rerun lupdate and lrelease 2022-10-14 19:27:04 -05:00
Cameron Gutman 1750243537 Merge remote-tracking branch 'origin/weblate' 2022-10-14 19:26:38 -05:00
Cameron Gutman 84fc5f3008 Use colorspace helper with VAAPI direct rendering 2022-10-14 19:23:51 -05:00
Cameron Gutman 8a224fbae4 Revert "Fix macOS build"
This reverts commit 0928d1fab2.
2022-10-13 01:19:56 -05:00
Cameron Gutman e6c4332445 Revert "Use Rec 2020 colorspace for WCG support even if HDR is off on the host"
Rec 2020 conversion causes colors to be blown out in SDR

This reverts commit 472e8ee92e.
2022-10-13 01:19:49 -05:00
Cameron Gutman 0928d1fab2 Fix macOS build 2022-10-13 01:11:04 -05:00
Cameron Gutman 40dd588bef Use 10-bit format mask instead of hardcoding specific formats 2022-10-13 00:17:05 -05:00
Cameron Gutman 472e8ee92e Use Rec 2020 colorspace for WCG support even if HDR is off on the host 2022-10-13 00:14:10 -05:00
Cameron Gutman c55af7fff3 Add a decoder/renderer callback to specify the desired color range 2022-10-12 23:59:01 -05:00
Cameron Gutman 7a39daf179 Implement Rec 601 Full Range support in SDLRenderer 2022-10-12 23:52:29 -05:00
Cameron Gutman 8d087ba5b1 Allow localization of pairing error strings 2022-10-12 22:20:15 -05:00
Cameron Gutman c7a30d8728 Allow a pairing attempt even if the PC is busy
Pairing while busy doesn't work with GFE but works with Sunshine
2022-10-12 22:15:58 -05:00
LedyBacer 1f678cafba Translated using Weblate (Russian)
Currently translated at 100.0% (200 of 200 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2022-10-12 10:23:12 +02:00
Jen Kung-chih 579e0bc464 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (200 of 200 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2022-10-10 19:59:11 +02:00
Nikita Epifanov 7f36dc5aad Translated using Weblate (Russian)
Currently translated at 97.0% (194 of 200 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2022-10-10 19:59:11 +02:00
Eric a7c2026128 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (200 of 200 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2022-10-10 19:59:11 +02:00
Sargon-Isa 7b0d67ebbe Translated using Weblate (German)
Currently translated at 100.0% (200 of 200 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2022-10-10 19:59:10 +02:00
Cameron Gutman 63f0910eac Update AppImage to libva 2.16.0 2022-10-07 22:22:13 -05:00
Cameron Gutman b16cf9fcc5 Update moonlight-common-c with speculative RFI support 2022-10-07 22:21:16 -05:00
Cameron Gutman 5501d7bc6b Include opt-out variable in VAAPI avoidance log output 2022-10-05 18:50:53 -05:00
Cameron Gutman 413ef42a34 Enable HEVC RFI for CUDA, VAAPI, and VDPAU decoders 2022-10-05 18:47:43 -05:00
Cameron Gutman a1694ab951 Use nvidia-vaapi-driver on Wayland systems if available 2022-10-05 18:44:11 -05:00
Cameron Gutman fbbbd5e0b8 Use new LiRequestIdrFrame() API 2022-10-05 00:53:31 -05:00
Cameron Gutman f8c7e2ee41 Enable HEVC RFI for VideoToolbox 2022-10-05 00:25:59 -05:00
Cameron Gutman 2a4af0bc2c Use colorspace helpers for DRM EGL export 2022-10-05 00:20:33 -05:00
Cameron Gutman d24db13e9d Update SDL_GameControllerDB 2022-10-05 00:00:39 -05:00
Cameron Gutman bc68aad67b Print the frame number if decoding fails 2022-10-04 23:38:54 -05:00
Cameron Gutman 9e8a4cab7b Enable HEVC RFI for DXVA2 and D3D11VA 2022-10-04 22:56:21 -05:00
Cameron Gutman 3679964cf8 Rerun lupdate and lrelease 2022-10-04 22:06:57 -05:00
Cameron Gutman 225ec1a87f Merge remote-tracking branch 'origin/weblate' 2022-10-04 22:05:48 -05:00
Cameron Gutman 071c76c06a Add error text for ML_ERROR_FRAME_CONVERSION 2022-10-04 22:05:18 -05:00
Cameron Gutman d77a255721 Update moonlight-common-c with GFE 3.26 and HEVC RFI fixes 2022-10-04 22:03:27 -05:00
Cameron Gutman 26ee1aee25 Fix protected content error on GFE 3.22+ 2022-10-04 22:02:44 -05:00
Jen Kung-chih b047395938 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2022-09-29 15:16:28 +02:00
Cameron Gutman 483a601867 Display SDR/HDR status in performance overlay 2022-09-28 17:44:22 -05:00
bruh c4bc2f6adf Translated using Weblate (Vietnamese)
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/vi/
2022-09-25 17:18:44 +02:00
Cameron Gutman 439e8fe33f Constify frame colorspace helper functions 2022-09-24 18:37:51 -05:00
Cameron Gutman 99885f5b4b Centralize colorspace and color range handling 2022-09-24 12:28:23 -05:00
Cameron Gutman 78b522ec1d Fix large frame header size for GFE 3.26 2022-09-22 23:48:41 -05:00
Jorys Paulin dbfa5a9358 Translated using Weblate (French)
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2022-09-14 15:20:55 +02:00
Howard Wu 3910b3862a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hans/
2022-09-13 07:18:54 +02:00
Cameron Gutman 3b8e3b7d5c Prepare for v4.2.1 2022-09-12 00:12:35 -05:00
Cameron Gutman 6129000dde Rerun lupdate and lrelease 2022-09-12 00:08:31 -05:00
Cameron Gutman 65b274d695 Merge remote-tracking branch 'origin/weblate' 2022-09-12 00:07:42 -05:00
reloxx13 a13dd0076b Translated using Weblate (German)
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2022-09-12 06:41:15 +02:00
Cameron Gutman cc026c995c Fix mishandling of IDR frames with a SEI or AUD NAL
Fixes #865
2022-09-11 23:36:20 -05:00
Cameron Gutman 81cfacaa3d Add fallback logic when the host doesn't send valid colorspace data 2022-09-11 23:13:10 -05:00
Cameron Gutman fabebc05d2 Prepare for v4.2.0 2022-09-10 12:19:04 -05:00
Cameron Gutman 07a97bab03 Rerun lupdate and lrelease 2022-09-10 12:06:22 -05:00
Cameron Gutman 69ac810c82 Merge remote-tracking branch 'origin/weblate' 2022-09-10 12:05:24 -05:00
Daniel Saburi 3f4609dc6e Translated using Weblate (Portuguese (Brazil))
Currently translated at 16.1% (32 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/pt_BR/
2022-09-09 04:16:43 +02:00
Daniel Saburi 1ddb529d26 Translated using Weblate (Portuguese)
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/pt/
2022-09-09 04:16:42 +02:00
Cameron Gutman ab6b88073c Fix build with _FILE_OFFSET_BITS=64 2022-09-05 22:04:24 -05:00
Cameron Gutman ad3ed97472 Fix handling of 3 byte Annex B start sequences 2022-09-05 17:56:42 -05:00
Cameron Gutman 07e902fe95 Fix build with open() defined to open64() 2022-09-04 23:45:18 -05:00
Cameron Gutman eb781ce689 Use __FUNCTION__ macro instead of hardcoding 2022-09-03 18:09:01 -05:00
Cameron Gutman 555a65c8ea Add open64() hook for _FILE_OFFSET_BITS=64 2022-09-03 18:05:03 -05:00
Cameron Gutman 58429f4172 Fix handling of the optional mode argument in our open() hook 2022-09-03 17:52:54 -05:00
sanhoe e2692a0abd Translated using Weblate (Korean)
Currently translated at 100.0% (198 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2022-09-03 00:21:34 +02:00
Brandon Goldberg d218c43edf Translated using Weblate (Spanish)
Currently translated at 97.4% (193 of 198 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2022-09-03 00:21:34 +02:00
Cameron Gutman fae9813127 Rebuild libraries built with CMake to get working PDBs and better optimizations
Additionally, disable WGI to work around an SDL bug

https://github.com/cgutman/moonlight-deps/commit/1810c6fba8b2377791e68d8829ee4c9a796788af
2022-08-28 20:34:44 -05:00
Cameron Gutman adc56d4ee5 Rerun lupdate and lrelease 2022-08-28 20:22:39 -05:00
Cameron Gutman f982e33fab Merge remote-tracking branch 'origin/weblate' 2022-08-28 20:20:53 -05:00
Cameron Gutman 9ab4479fcf Fix excessive flickering when switching between full-screen and windowed on Windows 2022-08-28 20:10:26 -05:00
Cameron Gutman 3e330b7929 Attach to the parent console to print list output on Win32 2022-08-27 14:54:45 -05:00
Cameron Gutman ecddada150 Improve verbose log suppression 2022-08-25 23:02:32 -05:00
Cameron Gutman 0c828cbb37 Suppress non-critical log output while running the list command 2022-08-25 22:45:42 -05:00
Cameron Gutman 84a32f8c16 Minor cleanup of new list code 2022-08-25 22:39:04 -05:00
Anselm Busse 4ee36fd405 Avoid GUI display for App listing 2022-08-25 22:18:05 -05:00
Anselm Busse 0802609fd8 Correcting for PR comments 2022-08-25 22:18:05 -05:00
Anselm Busse de88176995 Add a 'list' option for the CLI
This commit addresses Issue #448 by adding a command line option that allows the listing of all the Apps reported by the remote host as a CSV.
2022-08-25 22:18:00 -05:00
Cameron Gutman aca82f400a Never use fewer than 480 samples to avoid causing issues on PulseAudio systems
Fixes #830
Fixes #858
2022-08-25 21:27:05 -05:00
Brandon Goldberg 54bb6fe527 Translated using Weblate (Spanish)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/es/
2022-08-25 02:17:55 +02:00
Cameron Gutman c1f663cec1 Add missing files from previous commit 2022-08-24 17:31:22 -05:00
Cameron Gutman aaf59c2d01 Add CLI pairing support 2022-08-24 00:22:02 -05:00
Cameron Gutman 692942e5be Move PIN generation into ComputerManager to be shared with CLI pairing 2022-08-24 00:21:25 -05:00
Cameron Gutman f4acd629a1 Save hosts immediately after pairing concludes to ensure the server cert is persisted 2022-08-24 00:20:22 -05:00
Cameron Gutman 0ff8a44ec3 Allow 10-bit in DXVA2 as a fallback 2022-08-22 00:23:07 -05:00
Cameron Gutman c6d8f6e618 Allow D3D11VA to be disabled via environment variable 2022-08-22 00:19:46 -05:00
Cameron Gutman 1baa23cc43 Update SDL to 84004d1 for AppImage 2022-08-21 20:58:50 -05:00
Cameron Gutman 3be696e6bb Update SDL to 84004d1 2022-08-21 20:55:38 -05:00
Cameron Gutman 3cfcb01603 Add simple script to help updating libs 2022-08-21 20:55:25 -05:00
Cameron Gutman d39a29759c Don't draw with test-only renderers 2022-08-21 18:38:09 -05:00
Cameron Gutman bf4332b9e7 Prefer D3D11VA in non-FSE modes for improved performance 2022-08-21 17:50:56 -05:00
Cameron Gutman feaae7b684 Update moonlight-common-c to improve control stream failure diagnostics 2022-08-21 15:10:49 -05:00
Cameron Gutman be7852dfc0 Remove desktop resolution in favor of native (unscaled) resolution
Seeing 2 "native" resolutions for a single high DPI display is confusing.

If someone wants a lower resolution aspect-ratio match of a display, they
can use a custom resolution.
2022-08-16 01:22:26 -05:00
Cameron Gutman b0804ce048 Fix native resolution detection on high DPI wayland systems 2022-08-16 00:25:57 -05:00
Cameron Gutman aa7d5fa924 Don't use swap interval 1 by default on Wayland systems 2022-08-14 13:09:31 -05:00
Sargon-Isa b4cab12e9d Translated using Weblate (German)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/de/
2022-08-11 19:15:27 +02:00
Cameron Gutman 133dda2de0 Update SDL_GameControllerDB 2022-08-10 19:35:58 -05:00
Cameron Gutman ef5605ae39 Update FFmpeg to 5.1 2022-08-10 00:14:53 -05:00
Cameron Gutman 9b29b2702e Update VCRedist and add a script to update it in the future 2022-08-09 20:09:12 -05:00
Jorys Paulin abafa270e5 Translated using Weblate (French)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2022-08-06 14:19:54 +02:00
Cameron Gutman 15852ccf81 Update dependencies
SDL -> 602b7dd0f4
OpenSSL -> 1.1.1q
SDL_ttf -> 2.20.0
2022-08-02 00:38:33 -05:00
Kirill Korolev a427ecd029 Translated using Weblate (Russian)
Currently translated at 98.9% (190 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ru/
2022-07-22 18:18:21 +02:00
Wen-haur Chiu 77228dcb2c Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2022-07-21 14:19:41 +02:00
Cameron Gutman 3948cfe495 Update libva to 2.15 in AppImage 2022-07-02 13:05:47 -05:00
Cameron Gutman 36218b54f8 Don't hardcode /dev/dri/card0 and /dev/dri/renderD128 2022-06-30 19:25:33 -05:00
Cameron Gutman be2f4433db Avoid hardcoding VIDEO_FORMAT_H265_MAIN10 for HDR/10-bit color 2022-06-26 16:22:05 -05:00
Artur Szulc dd5d36cf07 Translated using Weblate (Polish)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/pl/
2022-06-20 23:14:50 +02:00
Adam Lantos 685136d98f Make drmDropMaster() more reliable by checking for DRM master fds.
This should fix the majority of EACCES permission denied errors
when attempting to drop the DRM master.
2022-06-12 12:29:13 -07:00
Mattia Rombi 852937421e Translated using Weblate (Italian)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/it/
2022-06-07 11:15:33 +02:00
Jorys Paulin a5389b7be5 Translated using Weblate (French)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2022-05-31 10:15:58 +02:00
Cameron Gutman 1b96b18bc0 Fix handling of colorspace changes when using CUDA+SDL 2022-05-28 22:55:55 -05:00
Cameron Gutman e9edad6f8c Fix surface leak if SDLVideo_CreateOverlay() fails 2022-05-28 22:48:49 -05:00
Cameron Gutman f8a6012c3a Improve overlay format handling 2022-05-28 22:48:23 -05:00
Cameron Gutman e103f6c9b6 Avoid holding the overlay lock during vaPutSurface 2022-05-28 22:28:17 -05:00
Cameron Gutman 9daf7b5a36 Fix modesetting for non-EGL HEVC streams 2022-05-28 21:42:27 -05:00
Mattia Rombi d6142e1af0 Translated using Weblate (Italian)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/it/
2022-05-28 12:14:50 +02:00
Jorys Paulin b0491210ff Translated using Weblate (French)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2022-05-28 12:14:50 +02:00
Mattia Rombi 91d8550290 Translated using Weblate (Italian)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/it/
2022-05-26 19:36:07 +02:00
sanhoe dfaaa7d72a Translated using Weblate (Korean)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/ko/
2022-05-26 19:15:29 +02:00
Dominik Chrástecký 5174a4f58a Translated using Weblate (Czech)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/cs/
2022-05-25 14:20:23 +02:00
Cameron Gutman b9c73df488 Draw the black background even for the KMSDRM backend to fix modesetting 2022-05-24 19:41:37 -05:00
Cameron Gutman f99f1f6441 Disable EGL renderer on RPi due to very poor performance 2022-05-23 23:05:07 -05:00
Cameron Gutman 1ec1f5d3be Switch to D3D11VA by default on Windows ARM64
It performs much better at 4K on Surface Pro X
2022-05-22 22:29:18 -05:00
Cameron Gutman 713db06a05 Prepare for v4.1.0 release 2022-05-22 19:56:32 -05:00
Cameron Gutman a0adcbd90b Update SDL_GameControllerDB 2022-05-22 19:08:18 -05:00
Cameron Gutman b94776421c Rerun lupdate and lrelease 2022-05-22 19:03:55 -05:00
Thijs Lumeij 79c1043063 Translated using Weblate (Dutch)
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/nl/
2022-05-23 02:02:14 +02:00
Cameron Gutman cfabae17c0 Rerun lupdate and lrelease 2022-05-22 19:01:18 -05:00
Cameron Gutman 71a1d25e90 Merge remote-tracking branch 'origin/weblate' 2022-05-22 18:59:38 -05:00
Cameron Gutman c2d7b835fb Try moving session.h prior to vaapi.h 2022-05-21 16:58:28 -05:00
Cameron Gutman f72888ce6f Attempt to fix build error with older Qt headers 2022-05-21 16:52:04 -05:00
Wen-haur Chiu 30f474d253 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (192 of 192 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/zh_Hant/
2022-05-21 14:16:30 +02:00
Cameron Gutman 216f274a92 Fix UAF in SLVideo overlay update 2022-05-21 00:30:07 -05:00
Cameron Gutman 5ea79efe4b Work around broken overlay state restoration in Qt GUI 2022-05-21 00:20:36 -05:00
Cameron Gutman 197c1ba21b Add support for the status overlay on Steam Link 2022-05-21 00:10:49 -05:00
Cameron Gutman 95c4a24d99 Destroy subpictures before the associated image 2022-05-20 21:46:40 -05:00
Cameron Gutman f5f23a7c22 Implement overlay support for the VAAPI direct renderer 2022-05-20 21:40:37 -05:00
Cameron Gutman c469576460 Update SDL and FFmpeg for AppImage 2022-05-20 17:45:35 -05:00
Cameron Gutman 0001bb0e4c Default to borderless windowed mode on Wayland systems 2022-05-19 19:22:01 -05:00
Cameron Gutman 56f184393f Allow the cursor lock toggle to work in full-screen mode
Fixes #793
2022-05-19 19:14:55 -05:00
Cameron Gutman 85777e85fa Use newer AppVeyor image for macOS builds 2022-05-18 00:54:21 -05:00
Cameron Gutman c750ec3cc7 Add workaround for macOS scrolling acceleration
See #778
2022-05-18 00:24:58 -05:00
Cameron Gutman c75b781d3f Use SDL_WINDOWEVENT_DISPLAY_CHANGED on SDL 2.0.18+ 2022-05-17 17:13:07 -05:00
Cameron Gutman 8aa42ff1d9 Reset the EGL renderer when specialization fails 2022-05-17 17:10:40 -05:00
Cameron Gutman 63a5bc5e12 Only force pacing in true full-screen mode to be consistent with DXVA2 2022-05-15 13:38:58 -05:00
Cameron Gutman e0b7b6dc3d Fix DXGI_FEATURE_PRESENT_ALLOW_TEARING feature check 2022-05-11 22:48:15 -05:00
Cameron Gutman 3d7ecf98c2 Fix HEVC Main10 support check on macOS GPUFamily1 GPUs 2022-05-11 22:00:34 -05:00
Cameron Gutman ad5581c620 Use DWM MMCSS for D3D11VA like DXVA2 does 2022-05-09 23:52:13 -05:00
Cameron Gutman 69af991772 Improve DXVA2 performance in borderless windowed on AMD hardware 2022-05-09 22:55:58 -05:00
Cameron Gutman 2bb2745f91 Enhance frame pacing logic for HDR renderers 2022-05-07 16:21:17 -05:00
Cameron Gutman 5f682bb45f Revert addition of D3DCREATE_NOWINDOWCHANGES
This flag breaks Alt+Tab in full-screen exclusive mode
2022-05-07 16:21:04 -05:00
Cameron Gutman 48bcb6b9a6 Rerun lupdate and lrelease 2022-05-07 14:32:34 -05:00
Cameron Gutman 092c0a7392 Integrate Czech and Polish translations 2022-05-07 14:32:07 -05:00
Cameron Gutman 4e8618c512 Merge remote-tracking branch 'origin/weblate' 2022-05-07 14:19:06 -05:00
Cameron Gutman c51a69c0dc Update SDL to 8cd908e 2022-05-07 14:18:39 -05:00
Cameron Gutman 2480849f39 Clean up DXGI adapter enumeration code 2022-05-02 19:05:00 -05:00
DankXylese 6bfe40607d Translated using Weblate (Ukrainian)
Currently translated at 27.4% (53 of 193 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/uk/
2022-04-28 22:13:27 +02:00
Cameron Gutman a1c350a537 Fix WARP device leak 2022-04-26 23:29:04 -05:00
Cameron Gutman f732b1978e Require a test frame with D3D11VA 2022-04-26 23:04:31 -05:00
Cameron Gutman d3a542bac4 Try all available GPUs in D3D11VA decoder 2022-04-26 22:49:46 -05:00
Cameron Gutman b12724db02 Reduce log spam when choosing a resolution 2022-04-24 17:11:10 -05:00
Cameron Gutman f8f5e8e869 Don't discard all excess frames before rendering
This prevents the render queue's pacing logic from working and
interferes with renderers that take a full v-sync period between
their waitToRender() and renderFrame() callbacks.
2022-04-24 17:04:32 -05:00
Cameron Gutman 0733f06103 Fix saving keep awake value
Fixes #781
2022-04-24 16:49:36 -05:00
Cameron Gutman 6fdaa6ee8a Avoid modesetting if the display didn't change 2022-04-24 16:48:21 -05:00
Cameron Gutman f10b547a49 Partially revert "Rework window management to fix issues restoring window state between windowed and full-screen"
This exposes all sorts of issues with full-screen transitions on Windows and macOS:
- Loss of proper window decorations when transitioning from full-screen to windowed on Windows
- Improper sizing when transitioning from true full-screen to windowed on macOS

This reverts commit 38396b2691.
2022-04-24 16:40:19 -05:00
Cameron Gutman 38396b2691 Rework window management to fix issues restoring window state between windowed and full-screen 2022-04-24 16:22:17 -05:00
Cameron Gutman 220f50ebe4 Revert "Allow renderers to apply size and display changes seamlessly"
This reverts commit c989133d27.
2022-04-24 16:12:12 -05:00
Cameron Gutman c989133d27 Allow renderers to apply size and display changes seamlessly 2022-04-24 15:42:08 -05:00
Cameron Gutman 6c339551eb Allow resolution changes on all platforms
Take aspect ratio into account when making the choice too.
2022-04-21 23:57:17 -05:00
Cameron Gutman ef23a80b16 Fix EGL build when using EGL 1.4 or earlier headers 2022-04-21 21:13:39 -05:00
Cameron Gutman a521093731 Update Visual C++ Runtime installer 2022-04-17 00:04:03 -05:00
Cameron Gutman 747d3eedf1 Cleanup D3D11VA renderer by removing unnecessary codepaths
Waitable swapchains seem to be worse than our existing DXVsyncSource for synchronizing with VBlank.

Full-screen exclusive mode isn't beneficial vs Independent Flip on modern Win10/Win11 systems.
2022-04-15 23:26:32 -05:00
Cameron Gutman fd563726f0 Fix blocking presents in borderless windowed flip modes 2022-04-15 23:21:52 -05:00
Cameron Gutman 62136a1a96 Increase buffer count to prevent starvation on AMD GPUs 2022-04-14 23:21:32 -05:00
Cameron Gutman cf16c31d38 Improve performance and latency of D3D11VA renderer 2022-04-14 22:46:13 -05:00
Cameron Gutman 0f5d4aa72f Enhance EGLRenderer's waitForRender() implementation 2022-04-10 20:20:44 -05:00
Cameron Gutman d8dbe77427 Tiny EGL code cleanup 2022-04-10 19:35:29 -05:00
Cameron Gutman 0e5670d50d Don't compare StackView items by localized name
Fixes #767
2022-04-10 15:42:50 -05:00
Cameron Gutman 22924a1e92 Reload the FPS and window mode comboboxes after changing language
Fixes #765
2022-04-10 13:17:42 -05:00
Artur Szulc dbbb1bd8a1 Translated using Weblate (Polish)
Currently translated at 100.0% (193 of 193 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/pl/
2022-04-04 23:12:28 +02:00
Dominik Chrástecký 5c838b2fc5 Translated using Weblate (Czech)
Currently translated at 100.0% (193 of 193 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/cs/
2022-04-04 23:12:27 +02:00
Jorys Paulin 3a094d4ba8 Translated using Weblate (French)
Currently translated at 100.0% (193 of 193 strings)

Translation: Moonlight Game Streaming/moonlight-qt
Translate-URL: https://hosted.weblate.org/projects/moonlight/moonlight-qt/fr/
2022-04-04 23:12:26 +02:00
Artur S 5d11c1ea6a Added translation using Weblate (Polish) 2022-04-03 20:52:00 +02:00
Dominik Chrástecký a7e2e15eac Added translation using Weblate (Czech) 2022-04-03 17:11:26 +02:00
Jorys Paulin 585a0ca1f4 Translated using Weblate (French)
Currently translated at 97.4% (188 of 193 strings)

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

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