From e1fd1da96de885da22951f45fce0c722c7053f71 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 5 Feb 2024 20:17:42 -0600 Subject: [PATCH 1/4] Skip encoder reprobing if no GPU changes have occurred --- src/platform/common.h | 7 ++++++ src/platform/linux/misc.cpp | 10 +++++++++ src/platform/macos/display.mm | 10 +++++++++ src/platform/windows/display_base.cpp | 31 +++++++++++++++++++++++++++ src/video.cpp | 10 +++++++-- 5 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/platform/common.h b/src/platform/common.h index 3c21e181e51..5056db8a62a 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -558,6 +558,13 @@ namespace platf { std::vector display_names(mem_type_e hwdevice_type); + /** + * @brief Returns if GPUs/drivers have changed since the last call to this function. + * @return `true` if a change has occurred or if it is unknown whether a change occurred. + */ + bool + needs_encoder_reenumeration(); + boost::process::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const boost::process::environment &env, FILE *file, std::error_code &ec, boost::process::group *group); diff --git a/src/platform/linux/misc.cpp b/src/platform/linux/misc.cpp index 6cfa9e49492..2c6ba16368a 100644 --- a/src/platform/linux/misc.cpp +++ b/src/platform/linux/misc.cpp @@ -773,6 +773,16 @@ namespace platf { return {}; } + /** + * @brief Returns if GPUs/drivers have changed since the last call to this function. + * @return `true` if a change has occurred or if it is unknown whether a change occurred. + */ + bool + needs_encoder_reenumeration() { + // We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on Linux. + return true; + } + std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { #ifdef SUNSHINE_BUILD_CUDA diff --git a/src/platform/macos/display.mm b/src/platform/macos/display.mm index 5c3d37477f8..a9f6bf40dd4 100644 --- a/src/platform/macos/display.mm +++ b/src/platform/macos/display.mm @@ -181,4 +181,14 @@ return display_names; } + + /** + * @brief Returns if GPUs/drivers have changed since the last call to this function. + * @return `true` if a change has occurred or if it is unknown whether a change occurred. + */ + bool + needs_encoder_reenumeration() { + // We don't track GPU state, so we will always reenumerate. Fortunately, it is fast on macOS. + return true; + } } // namespace platf diff --git a/src/platform/windows/display_base.cpp b/src/platform/windows/display_base.cpp index 64954bbf283..95e58b592a6 100644 --- a/src/platform/windows/display_base.cpp +++ b/src/platform/windows/display_base.cpp @@ -1124,4 +1124,35 @@ namespace platf { return display_names; } + /** + * @brief Returns if GPUs/drivers have changed since the last call to this function. + * @return `true` if a change has occurred or if it is unknown whether a change occurred. + */ + bool + needs_encoder_reenumeration() { + // Serialize access to the static DXGI factory + static std::mutex reenumeration_state_lock; + auto lg = std::lock_guard(reenumeration_state_lock); + + // Keep a reference to the DXGI factory, which will keep track of changes internally. + static dxgi::factory1_t factory; + if (!factory || !factory->IsCurrent()) { + factory.reset(); + + auto status = CreateDXGIFactory1(IID_IDXGIFactory1, (void **) &factory); + if (FAILED(status)) { + BOOST_LOG(error) << "Failed to create DXGIFactory1 [0x"sv << util::hex(status).to_string_view() << ']'; + factory.release(); + } + + // Always request reenumeration on the first streaming session just to ensure we + // can deal with any initialization races that may occur when the system is booting. + BOOST_LOG(info) << "Encoder reenumeration is required"sv; + return true; + } + else { + // The DXGI factory from last time is still current, so no encoder changes have occurred. + return false; + } + } } // namespace platf diff --git a/src/video.cpp b/src/video.cpp index d0d2e4b93c0..6706f0e4130 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -276,7 +276,7 @@ namespace video { enum flag_e : uint32_t { DEFAULT = 0, - PARALLEL_ENCODING = 1 << 1, + PARALLEL_ENCODING = 1 << 1, // Capture and encoding can run concurrently on separate threads H264_ONLY = 1 << 2, // When HEVC is too heavy LIMITED_GOP_SIZE = 1 << 3, // Some encoders don't like it when you have an infinite GOP_SIZE. *cough* VAAPI *cough* SINGLE_SLICE_ONLY = 1 << 4, // Never use multiple slices <-- Older intel iGPU's ruin it for everyone else :P @@ -284,6 +284,7 @@ namespace video { RELAXED_COMPLIANCE = 1 << 6, // Use FF_COMPLIANCE_UNOFFICIAL compliance mode NO_RC_BUF_LIMIT = 1 << 7, // Don't set rc_buffer_size REF_FRAMES_INVALIDATION = 1 << 8, // Support reference frames invalidation + ALWAYS_REPROBE = 1 << 9, // This is an encoder of last resort and we want to aggressively probe for a better one }; struct encoder_platform_formats_t { @@ -922,7 +923,7 @@ namespace video { std::make_optional("qp"s, &config::video.qp), "libx264"s, }, - H264_ONLY | PARALLEL_ENCODING + H264_ONLY | PARALLEL_ENCODING | ALWAYS_REPROBE }; #ifdef __linux__ @@ -2579,6 +2580,11 @@ namespace video { probe_encoders() { auto encoder_list = encoders; + // If we already have a good encoder, check to see if another probe is required + if (chosen_encoder && !(chosen_encoder->flags & ALWAYS_REPROBE) && !platf::needs_encoder_reenumeration()) { + return 0; + } + // Restart encoder selection auto previous_encoder = chosen_encoder; chosen_encoder = nullptr; From 102bcd76388c1aaa56f5d09670df81262c03ea67 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 24 Jan 2024 21:46:28 -0600 Subject: [PATCH 2/4] Don't populate QP rate control fallbacks for encoders guaranteed to support CBR/VBR It just needlessly slows down encoder probing when the encoder or codec is not supported. --- src/video.cpp | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index 6706f0e4130..b12e87670bf 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -379,6 +379,10 @@ namespace video { std::vector sdr_options; std::vector hdr_options; std::vector fallback_options; + + // QP option to set in the case that CBR/VBR is not supported + // by the encoder. If CBR/VBR is guaranteed to be supported, + // don't specify this option to avoid wasteful encoder probing. std::optional qp; std::string name; @@ -595,7 +599,7 @@ namespace video { {}, // Fallback options {}, - std::nullopt, // QP + std::nullopt, // QP rate control fallback "av1_nvenc"s, }, { @@ -607,7 +611,7 @@ namespace video { {}, // Fallback options {}, - std::nullopt, // QP + std::nullopt, // QP rate control fallback "hevc_nvenc"s, }, { @@ -619,7 +623,7 @@ namespace video { {}, // Fallback options {}, - std::nullopt, // QP + std::nullopt, // QP rate control fallback "h264_nvenc"s, }, PARALLEL_ENCODING | REF_FRAMES_INVALIDATION // flags @@ -660,7 +664,7 @@ namespace video { {}, // Fallback options {}, - std::nullopt, + std::nullopt, // QP rate control fallback "av1_nvenc"s, }, { @@ -684,7 +688,7 @@ namespace video { { "profile"s, (int) nv::profile_hevc_e::main_10 }, }, {}, // Fallback options - std::nullopt, + std::nullopt, // QP rate control fallback "hevc_nvenc"s, }, { @@ -705,7 +709,7 @@ namespace video { }, {}, // HDR-specific options {}, // Fallback options - std::make_optional({ "qp"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "h264_nvenc"s, }, PARALLEL_ENCODING @@ -735,7 +739,7 @@ namespace video { {}, // Fallback options {}, - std::make_optional({ "qp"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "av1_qsv"s, }, { @@ -759,7 +763,7 @@ namespace video { }, // Fallback options {}, - std::make_optional({ "qp"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "hevc_qsv"s, }, { @@ -786,7 +790,7 @@ namespace video { { { "low_power"s, 0 }, // Some old/low-end Intel GPUs don't support low power encoding }, - std::make_optional({ "qp"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "h264_qsv"s, }, PARALLEL_ENCODING | CBR_WITH_VBR | RELAXED_COMPLIANCE | NO_RC_BUF_LIMIT @@ -812,7 +816,7 @@ namespace video { {}, // SDR-specific options {}, // HDR-specific options {}, // Fallback options - std::make_optional({ "qp_p"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "av1_amf"s, }, { @@ -833,7 +837,7 @@ namespace video { {}, // SDR-specific options {}, // HDR-specific options {}, // Fallback options - std::make_optional({ "qp_p"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "hevc_amf"s, }, { @@ -857,7 +861,7 @@ namespace video { { { "usage"s, 2 /* AMF_VIDEO_ENCODER_USAGE_LOW_LATENCY */ }, // Workaround for https://github.com/GPUOpen-LibrariesAndSDKs/AMF/issues/410 }, - std::make_optional({ "qp_p"s, &config::video.qp }), + std::nullopt, // QP rate control fallback "h264_amf"s, }, PARALLEL_ENCODING @@ -883,7 +887,9 @@ namespace video { {}, // SDR-specific options {}, // HDR-specific options {}, // Fallback options - std::make_optional("qp"s, &config::video.qp), + + // QP rate control fallback + std::nullopt, #ifdef ENABLE_BROKEN_AV1_ENCODER // Due to bugs preventing on-demand IDR frames from working and very poor @@ -908,7 +914,7 @@ namespace video { {}, // SDR-specific options {}, // HDR-specific options {}, // Fallback options - std::make_optional("qp"s, &config::video.qp), + std::nullopt, // QP rate control fallback "libx265"s, }, { @@ -920,7 +926,7 @@ namespace video { {}, // SDR-specific options {}, // HDR-specific options {}, // Fallback options - std::make_optional("qp"s, &config::video.qp), + std::nullopt, // QP rate control fallback "libx264"s, }, H264_ONLY | PARALLEL_ENCODING | ALWAYS_REPROBE From 14752ad6b2598fdb55b254b413ee4766505ff449 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 24 Jan 2024 21:49:25 -0600 Subject: [PATCH 3/4] Don't fall back to undefined ref frame count on HEVC/AV1 if H.264 succeded with ref frame count specified I don't think there are any encoders out there that support this for some codecs and not others. --- src/video.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index b12e87670bf..53ef6ecb255 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -2479,7 +2479,13 @@ namespace video { if (disp->is_codec_supported(encoder.hevc.name, config_autoselect)) { retry_hevc: auto max_ref_frames_hevc = validate_config(disp, encoder, config_max_ref_frames); - auto autoselect_hevc = max_ref_frames_hevc >= 0 ? max_ref_frames_hevc : validate_config(disp, encoder, config_autoselect); + + // If H.264 succeeded with max ref frames specified, assume that we can count on + // HEVC to also succeed with max ref frames specified if HEVC is supported. + auto autoselect_hevc = (max_ref_frames_hevc >= 0 || max_ref_frames_h264 >= 0) ? + max_ref_frames_hevc : + validate_config(disp, encoder, config_autoselect); + if (autoselect_hevc < 0 && encoder.hevc.qp && encoder.hevc[encoder_t::CBR]) { // It's possible the encoder isn't accepting Constant Bit Rate. Turn off CBR and make another attempt encoder.hevc.capabilities.set(); @@ -2511,7 +2517,13 @@ namespace video { if (disp->is_codec_supported(encoder.av1.name, config_autoselect)) { retry_av1: auto max_ref_frames_av1 = validate_config(disp, encoder, config_max_ref_frames); - auto autoselect_av1 = max_ref_frames_av1 >= 0 ? max_ref_frames_av1 : validate_config(disp, encoder, config_autoselect); + + // If H.264 succeeded with max ref frames specified, assume that we can count on + // AV1 to also succeed with max ref frames specified if AV1 is supported. + auto autoselect_av1 = (max_ref_frames_av1 >= 0 || max_ref_frames_h264 >= 0) ? + max_ref_frames_av1 : + validate_config(disp, encoder, config_autoselect); + if (autoselect_av1 < 0 && encoder.av1.qp && encoder.av1[encoder_t::CBR]) { // It's possible the encoder isn't accepting Constant Bit Rate. Turn off CBR and make another attempt encoder.av1.capabilities.set(); From 87d2e090df81ae705453e9bead82088dffc45099 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 6 Feb 2024 19:30:22 -0600 Subject: [PATCH 4/4] Avoid recreating the display when no display parameters have changed --- src/video.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/video.cpp b/src/video.cpp index 53ef6ecb255..1914faaa181 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -2353,12 +2353,7 @@ namespace video { }; int - validate_config(std::shared_ptr &disp, const encoder_t &encoder, const config_t &config) { - reset_display(disp, encoder.platform_formats->dev_type, config::video.output_name, config); - if (!disp) { - return -1; - } - + validate_config(std::shared_ptr disp, const encoder_t &encoder, const config_t &config) { auto encode_device = make_encode_device(*disp, encoder, config); if (!encode_device) { return -1; @@ -2561,6 +2556,12 @@ namespace video { hevc.videoFormat = 1; av1.videoFormat = 2; + // Reset the display since we're switching from SDR to HDR + reset_display(disp, encoder.platform_formats->dev_type, config::video.output_name, config); + if (!disp) { + return false; + } + // HDR is not supported with H.264. Don't bother even trying it. encoder.h264[flag] = flag != encoder_t::DYNAMIC_RANGE && validate_config(disp, encoder, h264) >= 0;