From 8098df6c381fcb8d9e847144069796114b2dd62c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 08:33:25 -0400 Subject: [PATCH 01/22] build(deps): bump packaging/linux/flatpak/deps/shared-modules from `d88a915` to `a4a03d7` (#3167) build(deps): bump packaging/linux/flatpak/deps/shared-modules Bumps [packaging/linux/flatpak/deps/shared-modules](https://github.com/flathub/shared-modules) from `d88a915` to `a4a03d7`. - [Commits](https://github.com/flathub/shared-modules/compare/d88a9156b91eef64ecf1a313c868f1401c4bb39b...a4a03d755258d0d242bfec259b559c673abbafb3) --- updated-dependencies: - dependency-name: packaging/linux/flatpak/deps/shared-modules dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packaging/linux/flatpak/deps/shared-modules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/linux/flatpak/deps/shared-modules b/packaging/linux/flatpak/deps/shared-modules index d88a9156..a4a03d75 160000 --- a/packaging/linux/flatpak/deps/shared-modules +++ b/packaging/linux/flatpak/deps/shared-modules @@ -1 +1 @@ -Subproject commit d88a9156b91eef64ecf1a313c868f1401c4bb39b +Subproject commit a4a03d755258d0d242bfec259b559c673abbafb3 From c8920264548fa12bf1425c50e8d0df5986871cad Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Wed, 11 Sep 2024 17:56:54 -0400 Subject: [PATCH 02/22] docs(configure): auto generate open urls (#3163) --- docs/Doxyfile | 6 ++ docs/configuration.js | 37 +++++++ docs/configuration.md | 219 ++++++++++++++++++++++-------------------- docs/doc-styles.css | 6 ++ 4 files changed, 163 insertions(+), 105 deletions(-) create mode 100644 docs/configuration.js create mode 100644 docs/doc-styles.css diff --git a/docs/Doxyfile b/docs/Doxyfile index 1333aa51..6526b8bf 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -57,3 +57,9 @@ INPUT = ../README.md \ contributing.md \ ../third-party/doxyconfig/docs/source_code.md \ ../src + +# extra css +HTML_EXTRA_STYLESHEET += doc-styles.css + +# extra js +HTML_EXTRA_FILES += configuration.js diff --git a/docs/configuration.js b/docs/configuration.js new file mode 100644 index 00000000..6927ec7a --- /dev/null +++ b/docs/configuration.js @@ -0,0 +1,37 @@ +/** + * @brief Add a button to open the configuration option for each table + */ +document.addEventListener("DOMContentLoaded", function() { + const tables = document.querySelectorAll("table"); + tables.forEach(table => { + if (table.className !== "doxtable") { + return; + } + + let previousElement = table.previousElementSibling; + while (previousElement && previousElement.tagName !== "H2") { + previousElement = previousElement.previousElementSibling; + } + if (previousElement && previousElement.textContent) { + const sectionId = previousElement.textContent.trim().toLowerCase(); + const newRow = document.createElement("tr"); + + const newCell = document.createElement("td"); + newCell.setAttribute("colspan", "3"); + + const newCode = document.createElement("code"); + newCode.className = "open-button"; + newCode.setAttribute("onclick", `window.open('https://${document.getElementById('host-authority').value}/config/#${sectionId}', '_blank')`); + newCode.textContent = "Open"; + + newCell.appendChild(newCode); + newRow.appendChild(newCell); + + // get the table body + const tbody = table.querySelector("tbody"); + + // Insert at the beginning of the table + tbody.insertBefore(newRow, tbody.firstChild); + } + }); +}); diff --git a/docs/configuration.md b/docs/configuration.md index fe1d3b7e..f853e418 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,4 +1,13 @@ # Configuration + +@admonition{ Host authority | @htmlonly +By providing the host authority (URI + port), you can easily open each configuration option in the config UI. +
+ +Host authority: +@endhtmlonly +} + Sunshine will work with the default settings for most users. In some cases you may want to configure Sunshine further. The default location for the configuration file is listed below. You can use another location if you @@ -24,9 +33,9 @@ location by modifying the configuration file. Although it is recommended to use the configuration UI, it is possible manually configure Sunshine by editing the `conf` file in a text editor. Use the examples as reference. -## [General](https://localhost:47990/config/#general) +## General -### [locale](https://localhost:47990/config/#locale) +### locale @@ -102,7 +111,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [sunshine_name](https://localhost:47990/config/#sunshine_name) +### sunshine_name @@ -123,7 +132,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [min_log_level](https://localhost:47990/config/#min_log_level) +### min_log_level @@ -177,7 +186,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [channels](https://localhost:47990/config/#channels) +### channels @@ -203,7 +212,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [global_prep_cmd](https://localhost:47990/config/#global_prep_cmd) +### global_prep_cmd @@ -227,7 +236,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [notify_pre_releases](https://localhost:47990/config/#notify_pre_releases) +### notify_pre_releases @@ -250,9 +259,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
-## [Input](https://localhost:47990/config/#input) +## Input -### [controller](https://localhost:47990/config/#controller) +### controller @@ -275,7 +284,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [gamepad](https://localhost:47990/config/#gamepad) +### gamepad @@ -324,7 +333,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [ds4_back_as_touchpad_click](https://localhost:47990/config/#ds4_back_as_touchpad_click) +### ds4_back_as_touchpad_click @@ -349,7 +358,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [motion_as_ds4](https://localhost:47990/config/#motion_as_ds4) +### motion_as_ds4 @@ -377,7 +386,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [touchpad_as_ds4](https://localhost:47990/config/#touchpad_as_ds4) +### touchpad_as_ds4 @@ -405,7 +414,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [back_button_timeout](https://localhost:47990/config/#back_button_timeout) +### back_button_timeout @@ -430,7 +439,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [keyboard](https://localhost:47990/config/#keyboard) +### keyboard @@ -453,7 +462,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [key_repeat_delay](https://localhost:47990/config/#key_repeat_delay) +### key_repeat_delay @@ -477,7 +486,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [key_repeat_frequency](https://localhost:47990/config/#key_repeat_frequency) +### key_repeat_frequency @@ -501,7 +510,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [always_send_scancodes](https://localhost:47990/config/#always_send_scancodes) +### always_send_scancodes @@ -532,7 +541,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [key_rightalt_to_key_win](https://localhost:47990/config/#key_rightalt_to_key_win) +### key_rightalt_to_key_win @@ -555,7 +564,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [mouse](https://localhost:47990/config/#mouse) +### mouse @@ -578,7 +587,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [high_resolution_scrolling](https://localhost:47990/config/#high_resolution_scrolling) +### high_resolution_scrolling @@ -604,7 +613,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [native_pen_touch](https://localhost:47990/config/#native_pen_touch) +### native_pen_touch @@ -629,7 +638,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [native_pen_touch](https://localhost:47990/config/#native_pen_touch) +### keybindings @@ -665,9 +674,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
-## [Audio/Video](https://localhost:47990/config/#audio-video) +## Audio/Video -### [audio_sink](https://localhost:47990/config/#audio_sink) +### audio_sink @@ -710,7 +719,7 @@ editing the `conf` file in a text editor. Use the examples as reference. If you have multiple audio devices with identical names, use the Device ID instead. } @attention{If you want to mute the host speakers, use - [virtual_sink](#virtual_sinkhttpslocalhost47990configvirtual_sink) instead.} + [virtual_sink](#virtual_sink) instead.} @@ -737,7 +746,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [virtual_sink](https://localhost:47990/config/#virtual_sink) +### virtual_sink @@ -745,11 +754,11 @@ editing the `conf` file in a text editor. Use the examples as reference.
The audio device that's virtual, like Steam Streaming Speakers. This allows Sunshine to stream audio, while muting the speakers. - @tip{See [audio_sink](#audio_sinkhttpslocalhost47990configaudio_sink)!} + @tip{See [audio_sink](#audio_sink)!} @tip{These are some options for virtual sound devices. * Stream Streaming Speakers (Linux, macOS, Windows) * Steam must be installed. - * Enable [install_steam_audio_drivers](#install_steam_audio_drivershttpslocalhost47990configinstall_steam_audio_drivers) + * Enable [install_steam_audio_drivers](#install_steam_audio_drivers) or use Steam Remote Play at least once to install the drivers. * [Virtual Audio Cable](https://vb-audio.com/Cable) (macOS, Windows) } @@ -767,7 +776,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [install_steam_audio_drivers](https://localhost:47990/config/#install_steam_audio_drivers) +### install_steam_audio_drivers @@ -792,7 +801,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [adapter_name](https://localhost:47990/config/#adapter_name) +### adapter_name @@ -845,7 +854,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [output_name](https://localhost:47990/config/#output_name) +### output_name @@ -913,7 +922,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [min_fps_factor](https://localhost:47990/config/#min_fps_factor) +### min_fps_factor @@ -942,9 +951,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
-## [Network](https://localhost:47990/config/#network) +## Network -### [upnp](https://localhost:47990/config/#upnp) +### upnp @@ -967,7 +976,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [address_family](https://localhost:47990/config/#address_family) +### address_family @@ -999,7 +1008,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [port](https://localhost:47990/config/#port) +### port @@ -1027,7 +1036,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [origin_web_ui_allowed](https://localhost:47990/config/#origin_web_ui_allowed) +### origin_web_ui_allowed @@ -1063,7 +1072,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [external_ip](https://localhost:47990/config/#external_ip) +### external_ip @@ -1084,7 +1093,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [lan_encryption_mode](https://localhost:47990/config/#lan_encryption_mode) +### lan_encryption_mode @@ -1121,7 +1130,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [wan_encryption_mode](https://localhost:47990/config/#wan_encryption_mode) +### wan_encryption_mode @@ -1158,7 +1167,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [ping_timeout](https://localhost:47990/config/#ping_timeout) +### ping_timeout @@ -1181,9 +1190,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
-## [Config Files](https://localhost:47990/config/#files) +## Config Files -### [file_apps](https://localhost:47990/config/#file_apps) +### file_apps @@ -1207,7 +1216,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [credentials_file](https://localhost:47990/config/#credentials_file) +### credentials_file @@ -1230,7 +1239,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [log_path](https://localhost:47990/config/#log_path) +### log_path @@ -1253,7 +1262,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [pkey](https://localhost:47990/config/#pkey) +### pkey @@ -1278,7 +1287,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [cert](https://localhost:47990/config/#cert) +### cert @@ -1303,7 +1312,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [file_state](https://localhost:47990/config/#file_state) +### file_state @@ -1326,9 +1335,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
-## [Advanced](https://localhost:47990/config/#advanced) +## Advanced -### [fec_percentage](https://localhost:47990/config/#fec_percentage) +### fec_percentage @@ -1357,7 +1366,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [qp](https://localhost:47990/config/#qp) +### qp @@ -1381,7 +1390,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [min_threads](https://localhost:47990/config/#min_threads) +### min_threads @@ -1407,7 +1416,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [hevc_mode](https://localhost:47990/config/#hevc_mode) +### hevc_mode @@ -1449,7 +1458,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [av1_mode](https://localhost:47990/config/#av1_mode) +### av1_mode @@ -1491,7 +1500,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [capture](https://localhost:47990/config/#capture) +### capture @@ -1546,7 +1555,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [encoder](https://localhost:47990/config/#encoder) +### encoder @@ -1588,9 +1597,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
-## [NVIDIA NVENC Encoder](https://localhost:47990/config/#nvidia-nvenc-encoder) +## NVIDIA NVENC Encoder -### [nvenc_preset](https://localhost:47990/config/#nvenc_preset) +### nvenc_preset @@ -1600,7 +1609,7 @@ editing the `conf` file in a text editor. Use the examples as reference. Higher numbers improve compression (quality at given bitrate) at the cost of increased encoding latency. Recommended to change only when limited by network or decoder, otherwise similar effect can be accomplished by increasing bitrate. - @note{This option only applies when using NVENC [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using NVENC [encoder](#encoder).} @@ -1646,7 +1655,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [nvenc_twopass](https://localhost:47990/config/#nvenc_twopass) +### nvenc_twopass @@ -1656,7 +1665,7 @@ editing the `conf` file in a text editor. Use the examples as reference. This allows to detect more motion vectors, better distribute bitrate across the frame and more strictly adhere to bitrate limits. Disabling it is not recommended since this can lead to occasional bitrate overshoot and subsequent packet loss. - @note{This option only applies when using NVENC [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using NVENC [encoder](#encoder).} @@ -1686,7 +1695,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [nvenc_spatial_aq](https://localhost:47990/config/#nvenc_spatial_aq) +### nvenc_spatial_aq @@ -1694,7 +1703,7 @@ editing the `conf` file in a text editor. Use the examples as reference. @@ -1712,7 +1721,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
Assign higher QP values to flat regions of the video. Recommended to enable when streaming at lower bitrates. - @note{This option only applies when using NVENC [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using NVENC [encoder](#encoder).} @warning{Enabling this option may reduce performance.}
-### [nvenc_vbv_increase](https://localhost:47990/config/#nvenc_vbv_increase) +### nvenc_vbv_increase @@ -1724,7 +1733,7 @@ editing the `conf` file in a text editor. Use the examples as reference. act as low-latency variable bitrate, but may also lead to packet loss if the network doesn't have buffer headroom to handle bitrate spikes. Maximum accepted value is 400, which corresponds to 5x increased encoded video frame upper size limit. - @note{This option only applies when using NVENC [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using NVENC [encoder](#encoder).} @warning{Can lead to network packet loss.} @@ -1746,7 +1755,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [nvenc_realtime_hags](https://localhost:47990/config/#nvenc_realtime_hags) +### nvenc_realtime_hags @@ -1756,7 +1765,7 @@ editing the `conf` file in a text editor. Use the examples as reference. in Windows. Currently, NVIDIA drivers may freeze in encoder when HAGS is enabled, realtime priority is used and VRAM utilization is close to maximum. Disabling this option lowers the priority to high, sidestepping the freeze at the cost of reduced capture performance when the GPU is heavily loaded. - @note{This option only applies when using NVENC [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using NVENC [encoder](#encoder).} @note{Applies to Windows only.} @@ -1774,7 +1783,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [nvenc_latency_over_power](https://localhost:47990/config/#nvenc_latency_over_power) +### nvenc_latency_over_power @@ -1782,7 +1791,7 @@ editing the `conf` file in a text editor. Use the examples as reference. @@ -1801,7 +1810,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
Adaptive P-State algorithm which NVIDIA drivers employ doesn't work well with low latency streaming, so Sunshine requests high power mode explicitly. - @note{This option only applies when using NVENC [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using NVENC [encoder](#encoder).} @warning{Disabling this is not recommended since this can lead to significantly increased encoding latency.} @note{Applies to Windows only.}
-### [nvenc_opengl_vulkan_on_dxgi](https://localhost:47990/config/#nvenc_opengl_vulkan_on_dxgi) +### nvenc_opengl_vulkan_on_dxgi @@ -1810,7 +1819,7 @@ editing the `conf` file in a text editor. Use the examples as reference. Sunshine can't capture fullscreen OpenGL and Vulkan programs at full frame rate unless they present on top of DXGI. With this option enabled Sunshine changes global Vulkan/OpenGL present method to "Prefer layered on DXGI Swapchain". This is system-wide setting that is reverted on Sunshine program exit. - @note{This option only applies when using NVENC [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using NVENC [encoder](#encoder).} @note{Applies to Windows only.} @@ -1828,7 +1837,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [nvenc_h264_cavlc](https://localhost:47990/config/#nvenc_h264_cavlc) +### nvenc_h264_cavlc @@ -1838,7 +1847,7 @@ editing the `conf` file in a text editor. Use the examples as reference. CAVLC is outdated and needs around 10% more bitrate for same quality, but provides slightly faster decoding when using software decoder. @note{This option only applies when using H.264 format with the - NVENC [encoder](#encoderhttpslocalhost47990configencoder).} + NVENC [encoder](#encoder).} @@ -1855,16 +1864,16 @@ editing the `conf` file in a text editor. Use the examples as reference.
-## [Intel QuickSync Encoder](https://localhost:47990/config/#intel-quicksync-encoder) +## Intel QuickSync Encoder -### [qsv_preset](https://localhost:47990/config/#qsv_preset) +### qsv_preset @@ -1910,7 +1919,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
Description The encoder preset to use. - @note{This option only applies when using quicksync [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using quicksync [encoder](#encoder).}
-### [qsv_coder](https://localhost:47990/config/#qsv_coder) +### qsv_coder @@ -1918,7 +1927,7 @@ editing the `conf` file in a text editor. Use the examples as reference. @@ -1948,14 +1957,14 @@ editing the `conf` file in a text editor. Use the examples as reference.
The entropy encoding to use. @note{This option only applies when using H.264 with the quicksync - [encoder](#encoderhttpslocalhost47990configencoder).} + [encoder](#encoder).}
-### [qsv_slow_hevc](https://localhost:47990/config/#qsv_slow_hevc) +### qsv_slow_hevc @@ -1973,16 +1982,16 @@ editing the `conf` file in a text editor. Use the examples as reference.
Description This options enables use of HEVC on older Intel GPUs that only support low power encoding for H.264. - @note{This option only applies when using quicksync [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using quicksync [encoder](#encoder).} @caution{Streaming performance may be significantly reduced when this option is enabled.}
-## [AMD AMF Encoder](https://localhost:47990/config/#amd-amf-encoder) +## AMD AMF Encoder -### [amd_usage](https://localhost:47990/config/#amd_usage) +### amd_usage @@ -2022,14 +2031,14 @@ editing the `conf` file in a text editor. Use the examples as reference.
Description The encoder usage profile is used to set the base set of encoding parameters. - @note{This option only applies when using amdvce [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using amdvce [encoder](#encoder).} @note{The other AMF options that follow will override a subset of the settings applied by your usage profile, but there are hidden parameters set in usage profiles that cannot be overridden elsewhere.}
-### [amd_rc](https://localhost:47990/config/#amd_rc) +### amd_rc
Description The encoder rate control. - @note{This option only applies when using amdvce [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using amdvce [encoder](#encoder).} @warning{The `vbr_latency` option generally works best, but some bitrate overshoots may still occur. Enabling HRD allows all bitrate based rate controls to better constrain peak bitrate, but may result in encoding artifacts depending on your card.} @@ -2066,14 +2075,14 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [amd_enforce_hrd](https://localhost:47990/config/#amd_enforce_hrd) +### amd_enforce_hrd @@ -2091,14 +2100,14 @@ editing the `conf` file in a text editor. Use the examples as reference.
Description Enable Hypothetical Reference Decoder (HRD) enforcement to help constrain the target bitrate. - @note{This option only applies when using amdvce [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using amdvce [encoder](#encoder).} @warning{HRD is known to cause encoding artifacts or negatively affect encoding quality on certain cards.}
-### [amd_quality](https://localhost:47990/config/#amd_quality) +### amd_quality @@ -2128,14 +2137,14 @@ editing the `conf` file in a text editor. Use the examples as reference.
Description The quality profile controls the tradeoff between speed and quality of encoding. - @note{This option only applies when using amdvce [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using amdvce [encoder](#encoder).}
-### [amd_preanalysis](https://localhost:47990/config/#amd_preanalysis) +### amd_preanalysis @@ -2152,7 +2161,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
Description Preanalysis can increase encoding quality at the cost of latency. - @note{This option only applies when using amdvce [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using amdvce [encoder](#encoder).}
-### [amd_vbaq](https://localhost:47990/config/#amd_vbaq) +### amd_vbaq @@ -2160,7 +2169,7 @@ editing the `conf` file in a text editor. Use the examples as reference. @@ -2177,7 +2186,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
Variance Based Adaptive Quantization (VBAQ) can increase subjective visual quality by prioritizing allocation of more bits to smooth areas compared to more textured areas. - @note{This option only applies when using amdvce [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using amdvce [encoder](#encoder).}
-### [amd_coder](https://localhost:47990/config/#amd_coder) +### amd_coder @@ -2185,7 +2194,7 @@ editing the `conf` file in a text editor. Use the examples as reference. @@ -2215,9 +2224,9 @@ editing the `conf` file in a text editor. Use the examples as reference.
The entropy encoding to use. @note{This option only applies when using H.264 with the amdvce - [encoder](#encoderhttpslocalhost47990configencoder).} + [encoder](#encoder).}
-## [VideoToolbox Encoder](https://localhost:47990/config/#videotoolbox-encoder) +## VideoToolbox Encoder -### [vt_coder](https://localhost:47990/config/#vt_coder) +### vt_coder @@ -2254,7 +2263,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [vt_software](https://localhost:47990/config/#vt_software) +### vt_software @@ -2295,7 +2304,7 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [vt_realtime](https://localhost:47990/config/#vt_realtime) +### vt_realtime @@ -2320,16 +2329,16 @@ editing the `conf` file in a text editor. Use the examples as reference.
-## [Software Encoder](https://localhost:47990/config/#software-encoder) +## Software Encoder -### [sw_preset](https://localhost:47990/config/#sw_preset) +### sw_preset
Description The encoder preset to use. - @note{This option only applies when using software [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using software [encoder](#encoder).} @note{From [FFmpeg](https://trac.ffmpeg.org/wiki/Encode/H.264#preset).

@@ -2393,14 +2402,14 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### [sw_tune](https://localhost:47990/config/#sw_tune) +### sw_tune
Description The tuning preset to use. - @note{This option only applies when using software [encoder](#encoderhttpslocalhost47990configencoder).} + @note{This option only applies when using software [encoder](#encoder).} @note{From [FFmpeg](https://trac.ffmpeg.org/wiki/Encode/H.264#preset).

diff --git a/docs/doc-styles.css b/docs/doc-styles.css new file mode 100644 index 00000000..1505e851 --- /dev/null +++ b/docs/doc-styles.css @@ -0,0 +1,6 @@ +/* A fake button as doxygen doesn't allow button elements */ +.open-button { + background: var(--primary-color); + color: white; + cursor: pointer; +} From 6875fee479302d24f02d0ae81925af733dfa4c17 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 14 Sep 2024 16:14:04 -0400 Subject: [PATCH 03/22] chore(l10n): update translations (#3160) --- src_assets/common/assets/web/public/assets/locale/zh.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src_assets/common/assets/web/public/assets/locale/zh.json b/src_assets/common/assets/web/public/assets/locale/zh.json index e8753316..e5d79c53 100644 --- a/src_assets/common/assets/web/public/assets/locale/zh.json +++ b/src_assets/common/assets/web/public/assets/locale/zh.json @@ -49,7 +49,7 @@ "env_app_name": "应用名称", "env_client_audio_config": "客户端请求的音频配置 (2.0/5.1/7.1)", "env_client_enable_sops": "客户端请求自动更改游戏设置以实现最佳串流效果 (true/false)", - "env_client_fps": "客户端请求的帧率 (int)", + "env_client_fps": "客户请求的 FPS (int)", "env_client_gcmap": "客户端请求的游戏手柄掩码,采用 bitset/bitfield 格式 (int)", "env_client_hdr": "HDR 已被客户端启用 (true/false)", "env_client_height": "客户端请求的高度 (int)", @@ -270,7 +270,7 @@ "port_tcp": "TCP", "port_udp": "UDP", "port_warning": "暴露 Web UI 到公网存在安全风险!请自行承担风险!", - "port_web_ui": "Web UI", + "port_web_ui": "Clients", "qp": "量化参数 (QP)", "qp_desc": "某些设备可能不支持恒定码率。对于这些设备,则使用 QP 代替。数值越大,压缩率越高,但质量越差。", "qsv_coder": "QSV 编码器 (H264)", @@ -298,7 +298,7 @@ "sw_preset_ultrafast": "ultrafast - 极快", "sw_preset_veryfast": "veryfast - 非常快", "sw_preset_veryslow": "veryslow - 非常慢", - "sw_tune": "软件编码调校", + "sw_tune": "西色调", "sw_tune_animation": "animation -- 适合动画片;使用更高的去块和更多的参考帧", "sw_tune_desc": "调校选项,在预设后应用。默认值为 zerolatency。", "sw_tune_fastdecode": "fastdecode -- 通过禁用某些过滤器来加快解码速度", @@ -382,7 +382,7 @@ "logs_desc": "查看 Sunshine 上传的日志", "logs_find": "查找...", "restart_sunshine": "重启 Sunhine", - "restart_sunshine_desc": "如果 Sunshine 无法正常工作,可以尝试重新启动。这将终止任何正在运行的会话。", + "restart_sunshine_desc": "如果阳光工作不正常,您可以尝试重新启动它。这将终止任何已运行的会话。", "restart_sunshine_success": "Sunhine 正在重启", "troubleshooting": "故障排除", "unpair_all": "全部取消配对", From c63678ddcdba7a30a546adcf89f7d303788f1973 Mon Sep 17 00:00:00 2001 From: Joe Mou <1091031+jmou@users.noreply.github.com> Date: Sat, 14 Sep 2024 18:19:09 -0400 Subject: [PATCH 04/22] fix(macos/packaging): MacPorts startupitem.executable path (#3183) --- packaging/macos/Portfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/macos/Portfile b/packaging/macos/Portfile index 8c48d31e..678f742a 100644 --- a/packaging/macos/Portfile +++ b/packaging/macos/Portfile @@ -53,7 +53,7 @@ configure.env-append BUILD_VERSION=@BUILD_VERSION@ configure.env-append COMMIT=@GITHUB_COMMIT@ startupitem.create yes -startupitem.executable "${prefix}/bin/{$name}" +startupitem.executable "${prefix}/bin/sunshine" startupitem.location LaunchDaemons startupitem.name ${name} startupitem.netchange yes From 2185c4f2ceb366b392833dbe31da11aa91d567b4 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Sat, 10 Aug 2024 14:00:47 -0500 Subject: [PATCH 05/22] Fix frame capture and output duplication for dual GPU setups and virtual displays - Added `test_frame_capture` function to verify if frames are successfully captured and not empty. - Fixes issues with virtual displays such as IDDSampleDriver when using more than one GPU. --- tools/ddprobe.cpp | 152 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 146 insertions(+), 6 deletions(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index a30b2f41..e7040c5f 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -69,9 +69,132 @@ syncThreadDesktop() { CloseDesktop(hDesk); } + +/** + * @brief Determines whether a given frame is entirely black by sampling pixels. + * + * This function checks if the provided frame is predominantly black by sampling every 10th pixel in both the x and y dimensions. It inspects the RGB channels of each sampled pixel and compares them against a specified black threshold. If any sampled pixel's RGB values exceed this threshold, the frame is considered not black, and the function returns `false`. Otherwise, if all sampled pixels are below the threshold, the function returns `true`. + * + * @param mappedResource A reference to a `D3D11_MAPPED_SUBRESOURCE` structure that contains the mapped subresource data of the frame to be analyzed. + * @param frameDesc A reference to a `D3D11_TEXTURE2D_DESC` structure that describes the texture properties, including width and height. + * @param blackThreshold A floating-point value representing the threshold above which a pixel's RGB channels are considered non-black. The value ranges from 0.0f to 1.0f, with a default value of 0.1f. + * @return bool Returns `true` if the frame is determined to be black, otherwise returns `false`. + */ +bool +isFrameBlack(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTURE2D_DESC &frameDesc, float blackThreshold = 0.1f) { + const uint8_t *pixels = static_cast(mappedResource.pData); + const int bytesPerPixel = 4; // Assuming RGBA format + const int stride = mappedResource.RowPitch; + const int width = frameDesc.Width; + const int height = frameDesc.Height; + + // Sample every 10th pixel in both dimensions + const int sampleStep = 10; + const int threshold = static_cast(blackThreshold * 255); + + for (int y = 0; y < height; y += sampleStep) { + for (int x = 0; x < width; x += sampleStep) { + const uint8_t *pixel = pixels + y * stride + x * bytesPerPixel; + // Check if any channel (R, G, B) is significantly above black + if (pixel[0] > threshold || pixel[1] > threshold || pixel[2] > threshold) { + return false; + } + } + } + return true; +} + +/** + * @brief Attempts to capture and verify the contents of up to 10 consecutive frames from a DXGI output duplication. + * + * This function tries to acquire the next frame from a provided DXGI output duplication object (`dup`) and inspects its content to determine if the frame is not completely black. If a non-black frame is found within the 10 attempts, the function returns `S_OK`. If all 10 frames are black, it returns `S_FALSE`, indicating that the capture might not be functioning properly. In case of any failure during the process, the appropriate `HRESULT` error code is returned. + * + * @param dup A reference to the DXGI output duplication object (`dxgi::dup_t&`) used to acquire frames. + * @param device A pointer to the ID3D11Device interface that represents the device associated with the Direct3D context. + * @return HRESULT Returns `S_OK` if a non-black frame is captured successfully, `S_FALSE` if all frames are black, or an error code if a failure occurs during the process. + * + * Possible return values: + * - `S_OK`: A non-black frame was captured, indicating capture was successful. + * - `S_FALSE`: All 10 frames were black, indicating potential capture issues. + * - `E_FAIL`, `DXGI_ERROR_*`, or other DirectX HRESULT error codes in case of specific failures during frame capture, texture creation, or resource mapping. + */ +HRESULT +test_frame_capture(dxgi::dup_t &dup, ID3D11Device *device) { + for (int i = 0; i < 10; ++i) { + std::cout << "Attempting to acquire the next frame (" << i + 1 << "/10)..." << std::endl; + IDXGIResource *frameResource = nullptr; + DXGI_OUTDUPL_FRAME_INFO frameInfo; + HRESULT status = dup->AcquireNextFrame(500, &frameInfo, &frameResource); + if (FAILED(status)) { + std::cout << "Failed to acquire next frame [0x" << std::hex << status << "]" << std::endl; + return status; + } + + std::cout << "Frame acquired successfully." << std::endl; + + ID3D11Texture2D *frameTexture = nullptr; + status = frameResource->QueryInterface(__uuidof(ID3D11Texture2D), (void **) &frameTexture); + frameResource->Release(); + if (FAILED(status)) { + std::cout << "Failed to query texture interface from frame resource [0x" << std::hex << status << "]" << std::endl; + return status; + } + + D3D11_TEXTURE2D_DESC frameDesc; + frameTexture->GetDesc(&frameDesc); + frameDesc.Usage = D3D11_USAGE_STAGING; + frameDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + frameDesc.BindFlags = 0; + frameDesc.MiscFlags = 0; + + ID3D11Texture2D *stagingTexture = nullptr; + status = device->CreateTexture2D(&frameDesc, nullptr, &stagingTexture); + if (FAILED(status)) { + std::cout << "Failed to create staging texture [0x" << std::hex << status << "]" << std::endl; + frameTexture->Release(); + return status; + } + + ID3D11DeviceContext *context = nullptr; + device->GetImmediateContext(&context); + context->CopyResource(stagingTexture, frameTexture); + frameTexture->Release(); + + D3D11_MAPPED_SUBRESOURCE mappedResource; + status = context->Map(stagingTexture, 0, D3D11_MAP_READ, 0, &mappedResource); + if (SUCCEEDED(status)) { + std::cout << "Verifying if frame is not empty" << std::endl; + + if (!isFrameBlack(mappedResource, frameDesc)) { + std::cout << "Frame " << i + 1 << " is not empty!" << std::endl; + context->Unmap(stagingTexture, 0); + stagingTexture->Release(); + context->Release(); + dup->ReleaseFrame(); + return S_OK; + } + context->Unmap(stagingTexture, 0); + } + else { + std::cout << "Failed to map the staging texture for inspection [0x" << std::hex << status << "]" << std::endl; + stagingTexture->Release(); + context->Release(); + return status; + } + + stagingTexture->Release(); + context->Release(); + std::cout << "Releasing the frame..." << std::endl; + dup->ReleaseFrame(); + } + + // If all frames are black, then we can assume the capture isn't working properly. + return S_FALSE; +} + HRESULT test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { - D3D_FEATURE_LEVEL featureLevels[] { + D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, @@ -82,7 +205,7 @@ test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { }; dxgi::device_t device; - auto status = D3D11CreateDevice( + HRESULT status = D3D11CreateDevice( adapter.get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, @@ -92,24 +215,41 @@ test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { &device, nullptr, nullptr); + if (FAILED(status)) { - std::cout << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; + std::cout << "Failed to create D3D11 device for DD test [0x" << std::hex << status << "]" << std::endl; return status; } dxgi::output1_t output1; status = output->QueryInterface(IID_IDXGIOutput1, (void **) &output1); if (FAILED(status)) { - std::cout << "Failed to query IDXGIOutput1 from the output"sv << std::endl; + std::cout << "Failed to query IDXGIOutput1 from the output [0x" << std::hex << status << "]" << std::endl; return status; } // Ensure we can duplicate the current display syncThreadDesktop(); - // Return the result of DuplicateOutput() to Sunshine + // Attempt to duplicate the output dxgi::dup_t dup; - return output1->DuplicateOutput((IUnknown *) device.get(), &dup); + HRESULT result = output1->DuplicateOutput(static_cast(device.get()), &dup); + + if (SUCCEEDED(result)) { + // If duplication is successful, test frame capture + HRESULT captureResult = test_frame_capture(dup, device.get()); + if (SUCCEEDED(captureResult)) { + return S_OK; + } + else { + std::cout << "Frame capture test failed [0x" << std::hex << captureResult << "]" << std::endl; + return captureResult; + } + } + else { + std::cout << "Failed to duplicate output [0x" << std::hex << result << "]" << std::endl; + return result; + } } int From d470cc7d4ef8510f60c862ae3d61687f95635062 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Sat, 10 Aug 2024 14:08:59 -0500 Subject: [PATCH 06/22] Check all pixels instead It occoured to me there might be some desktop images that are predominately black, which could cause a false positive. --- tools/ddprobe.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index e7040c5f..f49be924 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -71,9 +71,9 @@ syncThreadDesktop() { /** - * @brief Determines whether a given frame is entirely black by sampling pixels. + * @brief Determines whether a given frame is entirely black by checking every pixel. * - * This function checks if the provided frame is predominantly black by sampling every 10th pixel in both the x and y dimensions. It inspects the RGB channels of each sampled pixel and compares them against a specified black threshold. If any sampled pixel's RGB values exceed this threshold, the frame is considered not black, and the function returns `false`. Otherwise, if all sampled pixels are below the threshold, the function returns `true`. + * This function checks if the provided frame is entirely black by inspecting each pixel in both the x and y dimensions. It inspects the RGB channels of each pixel and compares them against a specified black threshold. If any pixel's RGB values exceed this threshold, the frame is considered not black, and the function returns `false`. Otherwise, if all pixels are below the threshold, the function returns `true`. * * @param mappedResource A reference to a `D3D11_MAPPED_SUBRESOURCE` structure that contains the mapped subresource data of the frame to be analyzed. * @param frameDesc A reference to a `D3D11_TEXTURE2D_DESC` structure that describes the texture properties, including width and height. @@ -88,12 +88,12 @@ isFrameBlack(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTURE const int width = frameDesc.Width; const int height = frameDesc.Height; - // Sample every 10th pixel in both dimensions - const int sampleStep = 10; + // Convert the threshold to an integer value for comparison const int threshold = static_cast(blackThreshold * 255); - for (int y = 0; y < height; y += sampleStep) { - for (int x = 0; x < width; x += sampleStep) { + // Loop through every pixel + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { const uint8_t *pixel = pixels + y * stride + x * bytesPerPixel; // Check if any channel (R, G, B) is significantly above black if (pixel[0] > threshold || pixel[1] > threshold || pixel[2] > threshold) { @@ -104,6 +104,7 @@ isFrameBlack(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTURE return true; } + /** * @brief Attempts to capture and verify the contents of up to 10 consecutive frames from a DXGI output duplication. * From d9bd8e2d7710ee21eb669759c1695f4289a9fc27 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Sat, 10 Aug 2024 14:27:54 -0500 Subject: [PATCH 07/22] clang fixes --- tools/ddprobe.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index f49be924..bfabb503 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -69,12 +69,11 @@ syncThreadDesktop() { CloseDesktop(hDesk); } - /** * @brief Determines whether a given frame is entirely black by checking every pixel. - * + * * This function checks if the provided frame is entirely black by inspecting each pixel in both the x and y dimensions. It inspects the RGB channels of each pixel and compares them against a specified black threshold. If any pixel's RGB values exceed this threshold, the frame is considered not black, and the function returns `false`. Otherwise, if all pixels are below the threshold, the function returns `true`. - * + * * @param mappedResource A reference to a `D3D11_MAPPED_SUBRESOURCE` structure that contains the mapped subresource data of the frame to be analyzed. * @param frameDesc A reference to a `D3D11_TEXTURE2D_DESC` structure that describes the texture properties, including width and height. * @param blackThreshold A floating-point value representing the threshold above which a pixel's RGB channels are considered non-black. The value ranges from 0.0f to 1.0f, with a default value of 0.1f. @@ -104,7 +103,6 @@ isFrameBlack(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTURE return true; } - /** * @brief Attempts to capture and verify the contents of up to 10 consecutive frames from a DXGI output duplication. * From 56dedbde7f627669ca9bd7c086586ebab783d799 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Sun, 11 Aug 2024 23:06:11 -0500 Subject: [PATCH 08/22] code review suggestions --- tools/ddprobe.cpp | 124 +++++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 63 deletions(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index bfabb503..f89c085a 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -9,10 +9,13 @@ #include #include #include +#include #include "src/utility.h" using namespace std::literals; +using Microsoft::WRL::ComPtr; + namespace dxgi { template void @@ -70,72 +73,80 @@ syncThreadDesktop() { } /** - * @brief Determines whether a given frame is entirely black by checking every pixel. + * @brief Checks whether a given frame is entirely dark by evaluating the RGB values of each pixel. * - * This function checks if the provided frame is entirely black by inspecting each pixel in both the x and y dimensions. It inspects the RGB channels of each pixel and compares them against a specified black threshold. If any pixel's RGB values exceed this threshold, the frame is considered not black, and the function returns `false`. Otherwise, if all pixels are below the threshold, the function returns `true`. + * This function determines if the provided frame is completely dark by analyzing the RGB values of every pixel. + * It iterates over all pixels in the frame and compares each pixel's RGB channels to a defined darkness threshold. + * If any pixel's RGB values exceed this threshold, the function concludes that the frame is not entirely dark and returns `false`. + * If all pixels are below the threshold, indicating a completely dark frame, the function returns `true`. * - * @param mappedResource A reference to a `D3D11_MAPPED_SUBRESOURCE` structure that contains the mapped subresource data of the frame to be analyzed. - * @param frameDesc A reference to a `D3D11_TEXTURE2D_DESC` structure that describes the texture properties, including width and height. - * @param blackThreshold A floating-point value representing the threshold above which a pixel's RGB channels are considered non-black. The value ranges from 0.0f to 1.0f, with a default value of 0.1f. - * @return bool Returns `true` if the frame is determined to be black, otherwise returns `false`. + * @param mappedResource A reference to a `D3D11_MAPPED_SUBRESOURCE` structure containing the mapped subresource data of the frame to be analyzed. + * @param frameDesc A reference to a `D3D11_TEXTURE2D_DESC` structure describing the texture properties, including width and height. + * @param darknessThreshold A floating-point value representing the threshold above which a pixel's RGB values are considered non-dark. The value ranges from 0.0f to 1.0f, with a default value of 0.1f. + * @return bool Returns `true` if the frame is determined to be entirely dark, otherwise returns `false`. */ bool -isFrameBlack(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTURE2D_DESC &frameDesc, float blackThreshold = 0.1f) { +is_valid_frame(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTURE2D_DESC &frameDesc, float darknessThreshold = 0.1f) { const uint8_t *pixels = static_cast(mappedResource.pData); - const int bytesPerPixel = 4; // Assuming RGBA format + const int bytesPerPixel = 4; // (8 bits per channel, excluding alpha). Factoring HDR is not needed because it doesn't cause black levels to raise enough to be a concern. const int stride = mappedResource.RowPitch; const int width = frameDesc.Width; const int height = frameDesc.Height; - // Convert the threshold to an integer value for comparison - const int threshold = static_cast(blackThreshold * 255); + // Convert the darkness threshold to an integer value for comparison + const int threshold = static_cast(darknessThreshold * 255); - // Loop through every pixel + // Iterate over each pixel in the frame for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { const uint8_t *pixel = pixels + y * stride + x * bytesPerPixel; - // Check if any channel (R, G, B) is significantly above black + // Check if any RGB channel exceeds the darkness threshold if (pixel[0] > threshold || pixel[1] > threshold || pixel[2] > threshold) { - return false; + // Frame is not dark + return true; } } } - return true; + // Frame is entirely dark + return false; } /** - * @brief Attempts to capture and verify the contents of up to 10 consecutive frames from a DXGI output duplication. + * @brief Captures and verifies the contents of up to 10 consecutive frames from a DXGI output duplication. * - * This function tries to acquire the next frame from a provided DXGI output duplication object (`dup`) and inspects its content to determine if the frame is not completely black. If a non-black frame is found within the 10 attempts, the function returns `S_OK`. If all 10 frames are black, it returns `S_FALSE`, indicating that the capture might not be functioning properly. In case of any failure during the process, the appropriate `HRESULT` error code is returned. + * This function attempts to acquire and analyze up to 10 frames from a DXGI output duplication object (`dup`). + * It checks if each frame is non-empty (not entirely dark) by using the `is_valid_frame` function. + * If any non-empty frame is found, the function returns `S_OK`. + * If all 10 frames are empty, it returns `S_FALSE`, suggesting potential issues with the capture process. + * If any error occurs during the frame acquisition or analysis process, the corresponding `HRESULT` error code is returned. * * @param dup A reference to the DXGI output duplication object (`dxgi::dup_t&`) used to acquire frames. - * @param device A pointer to the ID3D11Device interface that represents the device associated with the Direct3D context. - * @return HRESULT Returns `S_OK` if a non-black frame is captured successfully, `S_FALSE` if all frames are black, or an error code if a failure occurs during the process. - * - * Possible return values: - * - `S_OK`: A non-black frame was captured, indicating capture was successful. - * - `S_FALSE`: All 10 frames were black, indicating potential capture issues. - * - `E_FAIL`, `DXGI_ERROR_*`, or other DirectX HRESULT error codes in case of specific failures during frame capture, texture creation, or resource mapping. + * @param device A ComPtr to the ID3D11Device interface representing the device associated with the Direct3D context. + * @return HRESULT Returns `S_OK` if a non-empty frame is captured successfully, `S_FALSE` if all frames are empty, or an error code if any failure occurs during the process. */ HRESULT -test_frame_capture(dxgi::dup_t &dup, ID3D11Device *device) { +test_frame_capture(dxgi::dup_t &dup, ComPtr device) { for (int i = 0; i < 10; ++i) { - std::cout << "Attempting to acquire the next frame (" << i + 1 << "/10)..." << std::endl; - IDXGIResource *frameResource = nullptr; + std::cout << "Attempting to acquire frame " << (i + 1) << " of 10..." << std::endl; + + ComPtr frameResource; DXGI_OUTDUPL_FRAME_INFO frameInfo; HRESULT status = dup->AcquireNextFrame(500, &frameInfo, &frameResource); + auto release_frame = util::fail_guard([&dup]() { + dup->ReleaseFrame(); + }); + if (FAILED(status)) { - std::cout << "Failed to acquire next frame [0x" << std::hex << status << "]" << std::endl; + std::cout << "Error: Failed to acquire next frame [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; } std::cout << "Frame acquired successfully." << std::endl; - ID3D11Texture2D *frameTexture = nullptr; - status = frameResource->QueryInterface(__uuidof(ID3D11Texture2D), (void **) &frameTexture); - frameResource->Release(); + ComPtr frameTexture; + status = frameResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(frameTexture.GetAddressOf())); if (FAILED(status)) { - std::cout << "Failed to query texture interface from frame resource [0x" << std::hex << status << "]" << std::endl; + std::cout << "Error: Failed to query texture interface from frame resource [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; } @@ -146,51 +157,37 @@ test_frame_capture(dxgi::dup_t &dup, ID3D11Device *device) { frameDesc.BindFlags = 0; frameDesc.MiscFlags = 0; - ID3D11Texture2D *stagingTexture = nullptr; + ComPtr stagingTexture; status = device->CreateTexture2D(&frameDesc, nullptr, &stagingTexture); if (FAILED(status)) { - std::cout << "Failed to create staging texture [0x" << std::hex << status << "]" << std::endl; - frameTexture->Release(); + std::cout << "Error: Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; } - ID3D11DeviceContext *context = nullptr; + ComPtr context; device->GetImmediateContext(&context); - context->CopyResource(stagingTexture, frameTexture); - frameTexture->Release(); + context->CopyResource(stagingTexture.Get(), frameTexture.Get()); D3D11_MAPPED_SUBRESOURCE mappedResource; - status = context->Map(stagingTexture, 0, D3D11_MAP_READ, 0, &mappedResource); - if (SUCCEEDED(status)) { - std::cout << "Verifying if frame is not empty" << std::endl; - - if (!isFrameBlack(mappedResource, frameDesc)) { - std::cout << "Frame " << i + 1 << " is not empty!" << std::endl; - context->Unmap(stagingTexture, 0); - stagingTexture->Release(); - context->Release(); - dup->ReleaseFrame(); - return S_OK; - } - context->Unmap(stagingTexture, 0); - } - else { - std::cout << "Failed to map the staging texture for inspection [0x" << std::hex << status << "]" << std::endl; - stagingTexture->Release(); - context->Release(); + status = context->Map(stagingTexture.Get(), 0, D3D11_MAP_READ, 0, &mappedResource); + if (FAILED(status)) { + std::cout << "Error: Failed to map the staging texture for inspection [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; } - stagingTexture->Release(); - context->Release(); - std::cout << "Releasing the frame..." << std::endl; - dup->ReleaseFrame(); + if (is_valid_frame(mappedResource, frameDesc)) { + std::cout << "Frame " << (i + 1) << " is non-empty (contains visible content)." << std::endl; + context->Unmap(stagingTexture.Get(), 0); + return S_OK; + } + + std::cout << "Frame " << (i + 1) << " is empty (no visible content)." << std::endl; + context->Unmap(stagingTexture.Get(), 0); } - // If all frames are black, then we can assume the capture isn't working properly. + // All frames were empty, indicating potential capture issues. return S_FALSE; } - HRESULT test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { D3D_FEATURE_LEVEL featureLevels[] = { @@ -232,11 +229,12 @@ test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { // Attempt to duplicate the output dxgi::dup_t dup; - HRESULT result = output1->DuplicateOutput(static_cast(device.get()), &dup); + ComPtr device_ptr(device.get()); + HRESULT result = output1->DuplicateOutput(device_ptr.Get(), &dup); if (SUCCEEDED(result)) { // If duplication is successful, test frame capture - HRESULT captureResult = test_frame_capture(dup, device.get()); + HRESULT captureResult = test_frame_capture(dup, device_ptr.Get()); if (SUCCEEDED(captureResult)) { return S_OK; } From 9a1bfebd03fd8a3a1c35c39a1ad309cd69220b8a Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Sun, 11 Aug 2024 23:09:54 -0500 Subject: [PATCH 09/22] attempt to match previous coding styles --- tools/ddprobe.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index f89c085a..ad8c5e78 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -213,14 +213,14 @@ test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { nullptr); if (FAILED(status)) { - std::cout << "Failed to create D3D11 device for DD test [0x" << std::hex << status << "]" << std::endl; + std::cout << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; } dxgi::output1_t output1; status = output->QueryInterface(IID_IDXGIOutput1, (void **) &output1); if (FAILED(status)) { - std::cout << "Failed to query IDXGIOutput1 from the output [0x" << std::hex << status << "]" << std::endl; + std::cout << "Failed to query IDXGIOutput1 from the output [0x"sv << util::hex(status).to_string_view() << "]" << std::endl; return status; } @@ -239,12 +239,12 @@ test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { return S_OK; } else { - std::cout << "Frame capture test failed [0x" << std::hex << captureResult << "]" << std::endl; + std::cout << "Frame capture test failed [0x"sv << util::hex(captureResult).to_string_view() << "]" << std::endl; return captureResult; } } else { - std::cout << "Failed to duplicate output [0x" << std::hex << result << "]" << std::endl; + std::cout << "Failed to duplicate output [0x"sv << util::hex(result).to_string_view() << "]" << std::endl; return result; } } From b9069cd9b508070fdd6e3d7fc62978fee45508c2 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Sun, 11 Aug 2024 23:12:16 -0500 Subject: [PATCH 10/22] remove else statements --- tools/ddprobe.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index ad8c5e78..62c6cc7f 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -188,6 +188,7 @@ test_frame_capture(dxgi::dup_t &dup, ComPtr device) { // All frames were empty, indicating potential capture issues. return S_FALSE; } + HRESULT test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { D3D_FEATURE_LEVEL featureLevels[] = { @@ -232,21 +233,19 @@ test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { ComPtr device_ptr(device.get()); HRESULT result = output1->DuplicateOutput(device_ptr.Get(), &dup); - if (SUCCEEDED(result)) { - // If duplication is successful, test frame capture - HRESULT captureResult = test_frame_capture(dup, device_ptr.Get()); - if (SUCCEEDED(captureResult)) { - return S_OK; - } - else { - std::cout << "Frame capture test failed [0x"sv << util::hex(captureResult).to_string_view() << "]" << std::endl; - return captureResult; - } - } - else { + if (FAILED(result)) { std::cout << "Failed to duplicate output [0x"sv << util::hex(result).to_string_view() << "]" << std::endl; return result; } + + // If duplication is successful, test frame capture + HRESULT captureResult = test_frame_capture(dup, device_ptr.Get()); + if (FAILED(captureResult)) { + std::cout << "Frame capture test failed [0x"sv << util::hex(captureResult).to_string_view() << "]" << std::endl; + return captureResult; + } + + return S_OK; } int From f8d14ac05b1c0da2148fa79b6cb8b895314e8b85 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Sun, 11 Aug 2024 23:15:28 -0500 Subject: [PATCH 11/22] remove code changes that were not needed --- tools/ddprobe.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index 62c6cc7f..8aa75b51 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -13,9 +13,8 @@ #include "src/utility.h" -using namespace std::literals; using Microsoft::WRL::ComPtr; - +using namespace std::literals; namespace dxgi { template void @@ -191,7 +190,7 @@ test_frame_capture(dxgi::dup_t &dup, ComPtr device) { HRESULT test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { - D3D_FEATURE_LEVEL featureLevels[] = { + D3D_FEATURE_LEVEL featureLevels[] { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, @@ -202,7 +201,7 @@ test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { }; dxgi::device_t device; - HRESULT status = D3D11CreateDevice( + auto status = D3D11CreateDevice( adapter.get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, @@ -212,7 +211,6 @@ test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { &device, nullptr, nullptr); - if (FAILED(status)) { std::cout << "Failed to create D3D11 device for DD test [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; @@ -221,7 +219,7 @@ test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { dxgi::output1_t output1; status = output->QueryInterface(IID_IDXGIOutput1, (void **) &output1); if (FAILED(status)) { - std::cout << "Failed to query IDXGIOutput1 from the output [0x"sv << util::hex(status).to_string_view() << "]" << std::endl; + std::cout << "Failed to query IDXGIOutput1 from the output"sv << std::endl; return status; } From 026c2aebf3435aef6700a8c4ed61f9b785d6a638 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Mon, 12 Aug 2024 03:03:12 -0500 Subject: [PATCH 12/22] move unmap to fail guard --- tools/ddprobe.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index 8aa75b51..24ffb3d1 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -130,8 +130,16 @@ test_frame_capture(dxgi::dup_t &dup, ComPtr device) { ComPtr frameResource; DXGI_OUTDUPL_FRAME_INFO frameInfo; + ComPtr context; + ComPtr stagingTexture; + HRESULT status = dup->AcquireNextFrame(500, &frameInfo, &frameResource); - auto release_frame = util::fail_guard([&dup]() { + device->GetImmediateContext(&context); + + auto cleanup = util::fail_guard([&dup, &context, &stagingTexture]() { + if (stagingTexture) { + context->Unmap(stagingTexture.Get(), 0); + } dup->ReleaseFrame(); }); @@ -156,15 +164,12 @@ test_frame_capture(dxgi::dup_t &dup, ComPtr device) { frameDesc.BindFlags = 0; frameDesc.MiscFlags = 0; - ComPtr stagingTexture; status = device->CreateTexture2D(&frameDesc, nullptr, &stagingTexture); if (FAILED(status)) { std::cout << "Error: Failed to create staging texture [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; } - ComPtr context; - device->GetImmediateContext(&context); context->CopyResource(stagingTexture.Get(), frameTexture.Get()); D3D11_MAPPED_SUBRESOURCE mappedResource; @@ -176,12 +181,10 @@ test_frame_capture(dxgi::dup_t &dup, ComPtr device) { if (is_valid_frame(mappedResource, frameDesc)) { std::cout << "Frame " << (i + 1) << " is non-empty (contains visible content)." << std::endl; - context->Unmap(stagingTexture.Get(), 0); return S_OK; } std::cout << "Frame " << (i + 1) << " is empty (no visible content)." << std::endl; - context->Unmap(stagingTexture.Get(), 0); } // All frames were empty, indicating potential capture issues. From 7dc2d41b77f4ecda8f87afaf65abd81b39fd7529 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Sat, 17 Aug 2024 01:36:44 -0500 Subject: [PATCH 13/22] implement cgutmans ideas --- src/platform/windows/display_base.cpp | 18 +++++++--- tools/ddprobe.cpp | 49 ++++++++++++++++++--------- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 3d178daf..18f5cb8e 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -6,6 +6,7 @@ #include #include +#include #include // We have to include boost/process.hpp before display.h due to WinSock.h, @@ -363,8 +364,10 @@ namespace platf::dxgi { // we spawn a helper tool to probe for us before we set our own GPU preference. bool probe_for_gpu_preference(const std::string &display_name) { - // If we've already been through here, there's nothing to do this time. static bool set_gpu_preference = false; + static bool verify_frame_capture = true; + + // If we've already been through here, there's nothing to do this time. if (set_gpu_preference) { return true; } @@ -378,17 +381,22 @@ namespace platf::dxgi { for (int i = 1; i < 5; i++) { // Run the probe tool. It returns the status of DuplicateOutput(). // - // Arg format: [GPU preference] [Display name] + // Arg format: [GPU preference] [Display name] [--verify--frame-capture] HRESULT result; + std::vector args = { std::to_string(i), display_name }; try { - result = bp::system(cmd, std::to_string(i), display_name, bp::std_out > bp::null, bp::std_err > bp::null); + if (verify_frame_capture) { + args.push_back("--verify-frame-capture"); + } + result = bp::system(cmd, bp::args(args), bp::std_out > bp::null, bp::std_err > bp::null); } catch (bp::process_error &e) { BOOST_LOG(error) << "Failed to start ddprobe.exe: "sv << e.what(); return false; } - BOOST_LOG(info) << "ddprobe.exe ["sv << i << "] ["sv << display_name << "] returned: 0x"sv << util::hex(result).to_string_view(); + BOOST_LOG(info) << "ddprobe.exe " << boost::algorithm::join(args, " ") << "returned 0x" + << util::hex(result).to_string_view(); // E_ACCESSDENIED can happen at the login screen. If we get this error, // we know capture would have been supported, because DXGI_ERROR_UNSUPPORTED @@ -410,6 +418,8 @@ namespace platf::dxgi { } // If none of the manual options worked, leave the GPU preference alone + // And set the verify frame capture option to false, just in case there is a chance for a false negative. + verify_frame_capture = false; return false; } diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index 24ffb3d1..79c1c994 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -121,7 +121,7 @@ is_valid_frame(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTU * * @param dup A reference to the DXGI output duplication object (`dxgi::dup_t&`) used to acquire frames. * @param device A ComPtr to the ID3D11Device interface representing the device associated with the Direct3D context. - * @return HRESULT Returns `S_OK` if a non-empty frame is captured successfully, `S_FALSE` if all frames are empty, or an error code if any failure occurs during the process. + * @return HRESULT Returns `S_OK` if a non-empty frame is captured successfully, `E_FAIL` if all frames are empty, or an error code if any failure occurs during the process. */ HRESULT test_frame_capture(dxgi::dup_t &dup, ComPtr device) { @@ -188,11 +188,11 @@ test_frame_capture(dxgi::dup_t &dup, ComPtr device) { } // All frames were empty, indicating potential capture issues. - return S_FALSE; + return E_FAIL; } HRESULT -test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { +test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output, bool verify_frame_capture) { D3D_FEATURE_LEVEL featureLevels[] { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, @@ -239,13 +239,16 @@ test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output) { return result; } - // If duplication is successful, test frame capture - HRESULT captureResult = test_frame_capture(dup, device_ptr.Get()); - if (FAILED(captureResult)) { - std::cout << "Frame capture test failed [0x"sv << util::hex(captureResult).to_string_view() << "]" << std::endl; - return captureResult; + // To prevent false negatives, we'll make it optional to test for frame capture. + if (verify_frame_capture) { + HRESULT captureResult = test_frame_capture(dup, device_ptr.Get()); + if (FAILED(captureResult)) { + std::cout << "Frame capture test failed [0x"sv << util::hex(captureResult).to_string_view() << "]" << std::endl; + return captureResult; + } } + return S_OK; } @@ -253,20 +256,34 @@ int main(int argc, char *argv[]) { HRESULT status; - // Display name may be omitted - if (argc != 2 && argc != 3) { - std::cout << "ddprobe.exe [GPU preference value] [display name]"sv << std::endl; + // Usage message + if (argc < 2 || argc > 4) { + std::cout << "Usage: ddprobe.exe [GPU preference value] [display name] [--verify-frame-capture]"sv << std::endl; return -1; } std::wstring display_name; - if (argc == 3) { - std::wstring_convert, wchar_t> converter; - display_name = converter.from_bytes(argv[2]); + bool verify_frame_capture = false; + + // Parse GPU preference value (required) + int gpu_preference = atoi(argv[1]); + + // Parse optional arguments + for (int i = 2; i < argc; ++i) { + std::string arg = argv[i]; + + if (arg == "--verify-frame-capture") { + verify_frame_capture = true; + } + else { + // Assume any other argument is the display name + std::wstring_convert, wchar_t> converter; + display_name = converter.from_bytes(arg); + } } // We must set the GPU preference before making any DXGI/D3D calls - status = set_gpu_preference(atoi(argv[1])); + status = set_gpu_preference(gpu_preference); if (status != ERROR_SUCCESS) { return status; } @@ -310,7 +327,7 @@ main(int argc, char *argv[]) { } // We found the matching output. Test it and return the result. - return test_dxgi_duplication(adapter, output); + return test_dxgi_duplication(adapter, output, verify_frame_capture); } } From 6d8f5b22c324d7ddcc21b4075c7f7d9e66845fed Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Sat, 17 Aug 2024 01:40:57 -0500 Subject: [PATCH 14/22] clang fixes --- tools/ddprobe.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index 79c1c994..49c2caa0 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -248,7 +248,6 @@ test_dxgi_duplication(dxgi::adapter_t &adapter, dxgi::output_t &output, bool ver } } - return S_OK; } From 431a7f6bf064916009e6fd28436a666668f72abe Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Fri, 23 Aug 2024 02:42:03 -0500 Subject: [PATCH 15/22] fix documentation --- tools/ddprobe.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index 49c2caa0..b05f599a 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -72,18 +72,18 @@ syncThreadDesktop() { } /** - * @brief Checks whether a given frame is entirely dark by evaluating the RGB values of each pixel. - * - * This function determines if the provided frame is completely dark by analyzing the RGB values of every pixel. - * It iterates over all pixels in the frame and compares each pixel's RGB channels to a defined darkness threshold. - * If any pixel's RGB values exceed this threshold, the function concludes that the frame is not entirely dark and returns `false`. - * If all pixels are below the threshold, indicating a completely dark frame, the function returns `true`. - * - * @param mappedResource A reference to a `D3D11_MAPPED_SUBRESOURCE` structure containing the mapped subresource data of the frame to be analyzed. - * @param frameDesc A reference to a `D3D11_TEXTURE2D_DESC` structure describing the texture properties, including width and height. - * @param darknessThreshold A floating-point value representing the threshold above which a pixel's RGB values are considered non-dark. The value ranges from 0.0f to 1.0f, with a default value of 0.1f. - * @return bool Returns `true` if the frame is determined to be entirely dark, otherwise returns `false`. - */ + * @brief Determines if a given frame is valid by checking if it contains any non-dark pixels. + * + * This function analyzes the provided frame to determine if it contains any pixels that exceed a specified darkness threshold. + * It iterates over all pixels in the frame, comparing each pixel's RGB values to the defined darkness threshold. + * If any pixel's RGB values exceed this threshold, the function concludes that the frame is valid (i.e., not entirely dark) and returns `true`. + * If all pixels are below or equal to the threshold, indicating a completely dark frame, the function returns `false`. + + * @param mappedResource A reference to a `D3D11_MAPPED_SUBRESOURCE` structure containing the mapped subresource data of the frame to be analyzed. + * @param frameDesc A reference to a `D3D11_TEXTURE2D_DESC` structure describing the texture properties, including width and height. + * @param darknessThreshold A floating-point value representing the threshold above which a pixel's RGB values are considered dark. The value ranges from 0.0f to 1.0f, with a default value of 0.1f. + * @return bool Returns `true` if the frame contains any non-dark pixels, indicating it is valid; otherwise, returns `false`. + */ bool is_valid_frame(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTURE2D_DESC &frameDesc, float darknessThreshold = 0.1f) { const uint8_t *pixels = static_cast(mappedResource.pData); From bae2d30816fedbb03acc914da157246fc6100563 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Fri, 23 Aug 2024 09:11:18 -0500 Subject: [PATCH 16/22] Update tools/ddprobe.cpp Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> --- tools/ddprobe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index b05f599a..5626e401 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -82,7 +82,7 @@ syncThreadDesktop() { * @param mappedResource A reference to a `D3D11_MAPPED_SUBRESOURCE` structure containing the mapped subresource data of the frame to be analyzed. * @param frameDesc A reference to a `D3D11_TEXTURE2D_DESC` structure describing the texture properties, including width and height. * @param darknessThreshold A floating-point value representing the threshold above which a pixel's RGB values are considered dark. The value ranges from 0.0f to 1.0f, with a default value of 0.1f. - * @return bool Returns `true` if the frame contains any non-dark pixels, indicating it is valid; otherwise, returns `false`. + * @return Returns `true` if the frame contains any non-dark pixels, indicating it is valid; otherwise, returns `false`. */ bool is_valid_frame(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTURE2D_DESC &frameDesc, float darknessThreshold = 0.1f) { From 6f71f42d50542bcff0b32fc8ba81d983401882ce Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Fri, 23 Aug 2024 09:29:12 -0500 Subject: [PATCH 17/22] more doc fixes --- tools/ddprobe.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index 5626e401..58c9b169 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -116,12 +116,12 @@ is_valid_frame(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTU * This function attempts to acquire and analyze up to 10 frames from a DXGI output duplication object (`dup`). * It checks if each frame is non-empty (not entirely dark) by using the `is_valid_frame` function. * If any non-empty frame is found, the function returns `S_OK`. - * If all 10 frames are empty, it returns `S_FALSE`, suggesting potential issues with the capture process. + * If all 10 frames are empty, it returns `E_FAIL`, suggesting potential issues with the capture process. * If any error occurs during the frame acquisition or analysis process, the corresponding `HRESULT` error code is returned. * * @param dup A reference to the DXGI output duplication object (`dxgi::dup_t&`) used to acquire frames. * @param device A ComPtr to the ID3D11Device interface representing the device associated with the Direct3D context. - * @return HRESULT Returns `S_OK` if a non-empty frame is captured successfully, `E_FAIL` if all frames are empty, or an error code if any failure occurs during the process. + * @return Returns `S_OK` if a non-empty frame is captured successfully, `E_FAIL` if all frames are empty, or an error code if any failure occurs during the process. */ HRESULT test_frame_capture(dxgi::dup_t &dup, ComPtr device) { From 7257b188c3688a2b8a31ca144aedb6e7557ac5a0 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Thu, 5 Sep 2024 16:20:35 -0500 Subject: [PATCH 18/22] Update tools/ddprobe.cpp Co-authored-by: Cameron Gutman --- tools/ddprobe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index 58c9b169..12ff014c 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -151,7 +151,7 @@ test_frame_capture(dxgi::dup_t &dup, ComPtr device) { std::cout << "Frame acquired successfully." << std::endl; ComPtr frameTexture; - status = frameResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast(frameTexture.GetAddressOf())); + status = frameResource->QueryInterface(IID_PPV_ARGS(&frameTexture)); if (FAILED(status)) { std::cout << "Error: Failed to query texture interface from frame resource [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; From b733d2b9e92a2fd635852058d97b8b6739c41431 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Thu, 5 Sep 2024 16:56:14 -0500 Subject: [PATCH 19/22] implement suggestions from cgutman --- src/platform/windows/display_base.cpp | 64 +++++++++++++++------------ tools/ddprobe.cpp | 18 ++++---- 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 18f5cb8e..ffbfd6f8 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -352,26 +352,8 @@ namespace platf::dxgi { return true; } - // On hybrid graphics systems, Windows will change the order of GPUs reported by - // DXGI in accordance with the user's GPU preference. If the selected GPU is a - // render-only device with no displays, DXGI will add virtual outputs to the - // that device to avoid confusing applications. While this works properly for most - // applications, it breaks the Desktop Duplication API because DXGI doesn't proxy - // the virtual DXGIOutput to the real GPU it is attached to. When trying to call - // DuplicateOutput() on one of these virtual outputs, it fails with DXGI_ERROR_UNSUPPORTED - // (even if you try sneaky stuff like passing the ID3D11Device for the iGPU and the - // virtual DXGIOutput from the dGPU). Because the GPU preference is once-per-process, - // we spawn a helper tool to probe for us before we set our own GPU preference. bool - probe_for_gpu_preference(const std::string &display_name) { - static bool set_gpu_preference = false; - static bool verify_frame_capture = true; - - // If we've already been through here, there's nothing to do this time. - if (set_gpu_preference) { - return true; - } - + validate_and_test_gpu_preference(const std::string &display_name, bool verify_frame_capture) { std::string cmd = "tools\\ddprobe.exe"; // We start at 1 because 0 is automatic selection which can be overridden by @@ -381,7 +363,7 @@ namespace platf::dxgi { for (int i = 1; i < 5; i++) { // Run the probe tool. It returns the status of DuplicateOutput(). // - // Arg format: [GPU preference] [Display name] [--verify--frame-capture] + // Arg format: [GPU preference] [Display name] [--verify-frame-capture] HRESULT result; std::vector args = { std::to_string(i), display_name }; try { @@ -404,22 +386,48 @@ namespace platf::dxgi { if (result == S_OK || result == E_ACCESSDENIED) { // We found a working GPU preference, so set ourselves to use that. if (set_gpu_preference_on_self(i)) { - set_gpu_preference = true; return true; } else { return false; } } - else { - // This configuration didn't work, so continue testing others - continue; - } } - // If none of the manual options worked, leave the GPU preference alone - // And set the verify frame capture option to false, just in case there is a chance for a false negative. - verify_frame_capture = false; + // If no valid configuration was found, return false + return false; + } + + // On hybrid graphics systems, Windows will change the order of GPUs reported by + // DXGI in accordance with the user's GPU preference. If the selected GPU is a + // render-only device with no displays, DXGI will add virtual outputs to the + // that device to avoid confusing applications. While this works properly for most + // applications, it breaks the Desktop Duplication API because DXGI doesn't proxy + // the virtual DXGIOutput to the real GPU it is attached to. When trying to call + // DuplicateOutput() on one of these virtual outputs, it fails with DXGI_ERROR_UNSUPPORTED + // (even if you try sneaky stuff like passing the ID3D11Device for the iGPU and the + // virtual DXGIOutput from the dGPU). Because the GPU preference is once-per-process, + // we spawn a helper tool to probe for us before we set our own GPU preference. + bool + probe_for_gpu_preference(const std::string &display_name) { + static bool set_gpu_preference = false; + + // If we've already been through here, there's nothing to do this time. + if (set_gpu_preference) { + return true; + } + + // Try probing with different GPU preferences and verify_frame_capture flag + if (validate_and_test_gpu_preference(display_name, true)) { + return true; + } + + // If no valid configuration was found, try again with verify_frame_capture == false + if (validate_and_test_gpu_preference(display_name, false)) { + return true; + } + + // If neither worked, return false return false; } diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index 12ff014c..22a2a210 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -127,7 +127,6 @@ HRESULT test_frame_capture(dxgi::dup_t &dup, ComPtr device) { for (int i = 0; i < 10; ++i) { std::cout << "Attempting to acquire frame " << (i + 1) << " of 10..." << std::endl; - ComPtr frameResource; DXGI_OUTDUPL_FRAME_INFO frameInfo; ComPtr context; @@ -136,22 +135,19 @@ test_frame_capture(dxgi::dup_t &dup, ComPtr device) { HRESULT status = dup->AcquireNextFrame(500, &frameInfo, &frameResource); device->GetImmediateContext(&context); - auto cleanup = util::fail_guard([&dup, &context, &stagingTexture]() { - if (stagingTexture) { - context->Unmap(stagingTexture.Get(), 0); - } - dup->ReleaseFrame(); - }); - if (FAILED(status)) { std::cout << "Error: Failed to acquire next frame [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; } + auto cleanup = util::fail_guard([&dup]() { + dup->ReleaseFrame(); + }); + std::cout << "Frame acquired successfully." << std::endl; ComPtr frameTexture; - status = frameResource->QueryInterface(IID_PPV_ARGS(&frameTexture)); + HRESULT status = frameResource->QueryInterface(IID_PPV_ARGS(&frameTexture)); if (FAILED(status)) { std::cout << "Error: Failed to query texture interface from frame resource [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; @@ -179,6 +175,10 @@ test_frame_capture(dxgi::dup_t &dup, ComPtr device) { return status; } + auto contextCleanup = util::fail_guard([&context, &stagingTexture]() { + context->Unmap(stagingTexture.Get()); + }); + if (is_valid_frame(mappedResource, frameDesc)) { std::cout << "Frame " << (i + 1) << " is non-empty (contains visible content)." << std::endl; return S_OK; From c0fdc320c2cbcbeaeecdbc8332da84b5e4e29b20 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Thu, 5 Sep 2024 16:57:32 -0500 Subject: [PATCH 20/22] add an additional space --- src/platform/windows/display_base.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index ffbfd6f8..85d321cf 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -377,7 +377,7 @@ namespace platf::dxgi { return false; } - BOOST_LOG(info) << "ddprobe.exe " << boost::algorithm::join(args, " ") << "returned 0x" + BOOST_LOG(info) << "ddprobe.exe " << boost::algorithm::join(args, " ") << " returned 0x" << util::hex(result).to_string_view(); // E_ACCESSDENIED can happen at the login screen. If we get this error, From 68235953528ccc34f3881ffd2e1a65f90f21866c Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Sat, 14 Sep 2024 11:53:18 -0500 Subject: [PATCH 21/22] fix compliler errors --- tools/ddprobe.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index 22a2a210..f33f7517 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -147,7 +147,7 @@ test_frame_capture(dxgi::dup_t &dup, ComPtr device) { std::cout << "Frame acquired successfully." << std::endl; ComPtr frameTexture; - HRESULT status = frameResource->QueryInterface(IID_PPV_ARGS(&frameTexture)); + status = frameResource->QueryInterface(IID_PPV_ARGS(&frameTexture)); if (FAILED(status)) { std::cout << "Error: Failed to query texture interface from frame resource [0x"sv << util::hex(status).to_string_view() << ']' << std::endl; return status; @@ -176,7 +176,7 @@ test_frame_capture(dxgi::dup_t &dup, ComPtr device) { } auto contextCleanup = util::fail_guard([&context, &stagingTexture]() { - context->Unmap(stagingTexture.Get()); + context->Unmap(stagingTexture.Get(), 0); }); if (is_valid_frame(mappedResource, frameDesc)) { From 4bdae9cf86957926cee26cc034ba3411135f1b10 Mon Sep 17 00:00:00 2001 From: Chase Payne Date: Sat, 14 Sep 2024 15:55:41 -0500 Subject: [PATCH 22/22] make sonarcloud happy again --- src/platform/windows/display_base.cpp | 2 +- tools/ddprobe.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 85d321cf..7058b77b 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -368,7 +368,7 @@ namespace platf::dxgi { std::vector args = { std::to_string(i), display_name }; try { if (verify_frame_capture) { - args.push_back("--verify-frame-capture"); + args.emplace_back("--verify-frame-capture"); } result = bp::system(cmd, bp::args(args), bp::std_out > bp::null, bp::std_err > bp::null); } diff --git a/tools/ddprobe.cpp b/tools/ddprobe.cpp index f33f7517..3c2eb057 100644 --- a/tools/ddprobe.cpp +++ b/tools/ddprobe.cpp @@ -86,14 +86,14 @@ syncThreadDesktop() { */ bool is_valid_frame(const D3D11_MAPPED_SUBRESOURCE &mappedResource, const D3D11_TEXTURE2D_DESC &frameDesc, float darknessThreshold = 0.1f) { - const uint8_t *pixels = static_cast(mappedResource.pData); + const auto *pixels = static_cast(mappedResource.pData); const int bytesPerPixel = 4; // (8 bits per channel, excluding alpha). Factoring HDR is not needed because it doesn't cause black levels to raise enough to be a concern. const int stride = mappedResource.RowPitch; const int width = frameDesc.Width; const int height = frameDesc.Height; // Convert the darkness threshold to an integer value for comparison - const int threshold = static_cast(darknessThreshold * 255); + const auto threshold = static_cast(darknessThreshold * 255); // Iterate over each pixel in the frame for (int y = 0; y < height; ++y) {