| 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;
+}
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
diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp
index 18f5cb8e..7058b77b 100644
--- a/src/platform/windows/display_base.cpp
+++ b/src/platform/windows/display_base.cpp
@@ -352,6 +352,52 @@ namespace platf::dxgi {
return true;
}
+ bool
+ 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
+ // the GPU driver control panel options. Since ddprobe.exe can have different
+ // GPU driver overrides than Sunshine.exe, we want to avoid a scenario where
+ // autoselection might work for ddprobe.exe but not for us.
+ 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]
+ HRESULT result;
+ std::vector args = { std::to_string(i), display_name };
+ try {
+ if (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);
+ }
+ catch (bp::process_error &e) {
+ BOOST_LOG(error) << "Failed to start ddprobe.exe: "sv << e.what();
+ return false;
+ }
+
+ 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
+ // would have been raised first if it wasn't.
+ 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)) {
+ return true;
+ }
+ else {
+ return 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
@@ -365,61 +411,23 @@ namespace platf::dxgi {
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;
}
- std::string cmd = "tools\\ddprobe.exe";
-
- // We start at 1 because 0 is automatic selection which can be overridden by
- // the GPU driver control panel options. Since ddprobe.exe can have different
- // GPU driver overrides than Sunshine.exe, we want to avoid a scenario where
- // autoselection might work for ddprobe.exe but not for us.
- 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]
- HRESULT result;
- std::vector args = { std::to_string(i), display_name };
- try {
- 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 " << 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
- // would have been raised first if it wasn't.
- 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;
- }
+ // Try probing with different GPU preferences and verify_frame_capture flag
+ if (validate_and_test_gpu_preference(display_name, true)) {
+ return true;
}
- // 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, 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 49c2caa0..3c2eb057 100644
--- a/tools/ddprobe.cpp
+++ b/tools/ddprobe.cpp
@@ -72,28 +72,28 @@ 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 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);
+ 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) {
@@ -116,18 +116,17 @@ 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) {
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(__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;
@@ -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(), 0);
+ });
+
if (is_valid_frame(mappedResource, frameDesc)) {
std::cout << "Frame " << (i + 1) << " is non-empty (contains visible content)." << std::endl;
return S_OK;
|