diff --git a/docs/source/about/advanced_usage.rst b/docs/source/about/advanced_usage.rst index c9d9ebae3b4..a0f60ebbf2c 100644 --- a/docs/source/about/advanced_usage.rst +++ b/docs/source/about/advanced_usage.rst @@ -1045,6 +1045,59 @@ nvenc_twopass nvenc_twopass = quarter_res +nvenc_spatial_aq +^^^^^^^^^^^^^^^^ + +**Description** + 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`_. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + disabled Don't enable Spatial AQ (faster) + enabled Enable Spatial AQ (slower) + ========== =========== + +**Default** + ``disabled`` + +**Example** + .. code-block:: text + + nvenc_spatial_aq = disabled + +nvenc_vbv_increase +^^^^^^^^^^^^^^^^^^ + +**Description** + Single-frame VBV/HRD percentage increase. + By default sunshine uses single-frame VBV/HRD, which means any encoded video frame size is not expected to exceed requested bitrate divided by requested frame rate. + Relaxing this restriction can be beneficial and 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`_. + + .. Warning:: Can lead to network packet loss. + +**Default** + ``0`` + +**Range** + ``0-400`` + +**Example** + .. code-block:: text + + nvenc_vbv_increase = 0 + nvenc_realtime_hags ^^^^^^^^^^^^^^^^^^^ @@ -1077,6 +1130,96 @@ nvenc_realtime_hags nvenc_realtime_hags = enabled +nvenc_h264_hevc_ref_frames +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + Default value for H.264 and HEVC reference frames in DPB (decoder picture buffer). + Override default sunshine value in situations when moonlight doesn't request one explicitly. + Higher values can provide larger pool for reference frame invalidation on unstable networks, but specific decoders may not handle it well depending on video resolution. + + .. Note:: This option only applies when using NVENC `encoder`_. + + .. Note:: Doesn't apply to AV1 since it uses predetermined value as part of specification. + + .. Warning:: Can lead to (or remedy) decoding artifacts. + + .. Caution:: Applies to Windows only. + +**Default** + Variable is unset and sunshine will pick whatever it thinks is the best. + +**Range** + ``1-15`` + +**Example** + .. code-block:: text + + nvenc_h264_hevc_ref_frames = 5 + +nvenc_latency_over_power +^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + 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`_. + + .. Warning:: Disabling it is not recommended since this can lead to significantly increased encoding latency. + + .. Caution:: Applies to Windows only. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + disabled Sunshine doesn't change GPU power preferences (not recommended) + enabled Sunshine requests high power mode explicitly + ========== =========== + +**Default** + ``enabled`` + +**Example** + .. code-block:: text + + nvenc_latency_over_power = enabled + +nvenc_opengl_vulkan_on_dxgi +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Description** + Sunshine can't capture fullscreen OpenGL and Vulkan programs at full frame rate unless they present on top of DXGI. + This is system-wide setting that is reverted on sunshine program exit. + + .. Note:: This option only applies when using NVENC `encoder`_. + + .. Caution:: Applies to Windows only. + +**Choices** + +.. table:: + :widths: auto + + ========== =========== + Value Description + ========== =========== + disabled Sunshine leaves global Vulkan/OpenGL present method unchanged + enabled Sunshine changes global Vulkan/OpenGL present method to "Prefer layered on DXGI Swapchain" + ========== =========== + +**Default** + ``enabled`` + +**Example** + .. code-block:: text + + nvenc_opengl_vulkan_on_dxgi = enabled + nvenc_h264_cavlc ^^^^^^^^^^^^^^^^ diff --git a/src/config.cpp b/src/config.cpp index d5932d62cfe..f0d7eac64fa 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -329,6 +329,8 @@ namespace config { {}, // nv true, // nv_realtime_hags + true, // nv_opengl_vulkan_on_dxgi + true, // nv_sunshine_high_power_mode {}, // nv_legacy { @@ -925,9 +927,14 @@ namespace config { string_f(vars, "sw_tune", video.sw.sw_tune); int_between_f(vars, "nvenc_preset", video.nv.quality_preset, { 1, 7 }); + int_between_f(vars, "nvenc_vbv_increase", video.nv.vbv_percentage_increase, { 0, 400 }); + int_between_f(vars, "nvenc_h264_hevc_ref_frames", video.nv.h264_hevc_default_ref_frames_in_dpb, { 0, 15 }); + bool_f(vars, "nvenc_spatial_aq", video.nv.adaptive_quantization); generic_f(vars, "nvenc_twopass", video.nv.two_pass, nv::twopass_from_view); bool_f(vars, "nvenc_h264_cavlc", video.nv.h264_cavlc); bool_f(vars, "nvenc_realtime_hags", video.nv_realtime_hags); + bool_f(vars, "nvenc_opengl_vulkan_on_dxgi", video.nv_opengl_vulkan_on_dxgi); + bool_f(vars, "nvenc_latency_over_power", video.nv_sunshine_high_power_mode); #ifndef __APPLE__ video.nv_legacy.preset = video.nv.quality_preset + 11; @@ -935,6 +942,8 @@ namespace config { video.nv.two_pass == nvenc::nvenc_two_pass::full_resolution ? NV_ENC_TWO_PASS_FULL_RESOLUTION : NV_ENC_MULTI_PASS_DISABLED; video.nv_legacy.h264_coder = video.nv.h264_cavlc ? NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC : NV_ENC_H264_ENTROPY_CODING_MODE_CABAC; + video.nv_legacy.aq = video.nv.adaptive_quantization; + video.nv_legacy.vbv_percentage_increase = video.nv.vbv_percentage_increase; #endif int_f(vars, "qsv_preset", video.qsv.qsv_preset, qsv::preset_from_view); diff --git a/src/config.h b/src/config.h index 749a8556d11..c39153c6014 100644 --- a/src/config.h +++ b/src/config.h @@ -30,11 +30,15 @@ namespace config { nvenc::nvenc_config nv; bool nv_realtime_hags; + bool nv_opengl_vulkan_on_dxgi; + bool nv_sunshine_high_power_mode; struct { int preset; int multipass; int h264_coder; + int aq; + int vbv_percentage_increase; } nv_legacy; struct { diff --git a/src/nvenc/nvenc_base.cpp b/src/nvenc/nvenc_base.cpp index f305f7682ad..149951b81c0 100644 --- a/src/nvenc/nvenc_base.cpp +++ b/src/nvenc/nvenc_base.cpp @@ -222,6 +222,9 @@ namespace nvenc { if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) { enc_config.rcParams.vbvBufferSize = client_config.bitrate * 1000 / client_config.framerate; + if (config.vbv_percentage_increase > 0) { + enc_config.rcParams.vbvBufferSize += enc_config.rcParams.vbvBufferSize * config.vbv_percentage_increase / 100; + } } auto set_h264_hevc_common_format_config = [&](auto &format_config) { @@ -284,7 +287,8 @@ namespace nvenc { else { format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CABAC; } - set_ref_frames(format_config.maxNumRefFrames, format_config.numRefL0, 5); + auto ref_frames_in_dpb = config.h264_hevc_default_ref_frames_in_dpb > 0 ? config.h264_hevc_default_ref_frames_in_dpb : 5; + set_ref_frames(format_config.maxNumRefFrames, format_config.numRefL0, ref_frames_in_dpb); set_minqp_if_enabled(config.min_qp_h264); fill_h264_hevc_vui(format_config.h264VUIParameters); break; @@ -297,7 +301,8 @@ namespace nvenc { if (buffer_is_10bit()) { format_config.pixelBitDepthMinus8 = 2; } - set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numRefL0, 5); + auto ref_frames_in_dpb = config.h264_hevc_default_ref_frames_in_dpb > 0 ? config.h264_hevc_default_ref_frames_in_dpb : 5; + set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numRefL0, ref_frames_in_dpb); set_minqp_if_enabled(config.min_qp_hevc); fill_h264_hevc_vui(format_config.hevcVUIParameters); break; @@ -369,9 +374,10 @@ namespace nvenc { if (init_params.enableEncodeAsync) extra += " async"; if (buffer_is_10bit()) extra += " 10-bit"; if (enc_config.rcParams.multiPass != NV_ENC_MULTI_PASS_DISABLED) extra += " two-pass"; + if (config.vbv_percentage_increase > 0 && get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) extra += " vbv+" + std::to_string(config.vbv_percentage_increase); if (encoder_params.rfi) extra += " rfi"; if (init_params.enableWeightedPrediction) extra += " weighted-prediction"; - if (enc_config.rcParams.enableAQ) extra += " adaptive-quantization"; + if (enc_config.rcParams.enableAQ) extra += " spatial-aq"; if (enc_config.rcParams.enableMinQP) extra += " qpmin=" + std::to_string(enc_config.rcParams.minQP.qpInterP); if (config.insert_filler_data) extra += " filler-data"; BOOST_LOG(info) << "NvEnc: created encoder " << quality_preset_string_from_guid(init_params.presetGUID) << extra; diff --git a/src/nvenc/nvenc_config.h b/src/nvenc/nvenc_config.h index 632146b7db0..a9053405171 100644 --- a/src/nvenc/nvenc_config.h +++ b/src/nvenc/nvenc_config.h @@ -20,6 +20,9 @@ namespace nvenc { // Use optional preliminary pass for better motion vectors, bitrate distribution and stricter VBV(HRD), uses CUDA cores nvenc_two_pass two_pass = nvenc_two_pass::quarter_resolution; + // Percentage increase of VBV/HRD from the default single frame, allows low-latency variable bitrate + int vbv_percentage_increase = 0; + // Improves fades compression, uses CUDA cores bool weighted_prediction = false; @@ -41,6 +44,10 @@ namespace nvenc { // Use CAVLC entropy coding in H.264 instead of CABAC, not relevant and here for historical reasons bool h264_cavlc = false; + // Override "ref frames in dpb" value for H.264 and HEVC when client doesn't request one explicitly + // Valid values are from 0 to 15, where 0 disables the override + int h264_hevc_default_ref_frames_in_dpb = 0; + // Add filler data to encoded frames to stay at target bitrate, mainly for testing bool insert_filler_data = false; }; diff --git a/src/platform/windows/nvprefs/driver_settings.cpp b/src/platform/windows/nvprefs/driver_settings.cpp index 54529fd5ddd..eebb31f4153 100644 --- a/src/platform/windows/nvprefs/driver_settings.cpp +++ b/src/platform/windows/nvprefs/driver_settings.cpp @@ -159,6 +159,11 @@ namespace nvprefs { undo_data.reset(); NvAPI_Status status; + if (!get_nvprefs_options().opengl_vulkan_on_dxgi) { + // User requested to leave OpenGL/Vulkan DXGI swapchain setting alone + return true; + } + NvDRSProfileHandle profile_handle = 0; status = NvAPI_DRS_GetBaseProfile(session_handle, &profile_handle); if (status != NVAPI_OK) { @@ -261,9 +266,26 @@ namespace nvprefs { setting.version = NVDRS_SETTING_VER1; status = NvAPI_DRS_GetSetting(session_handle, profile_handle, PREFERRED_PSTATE_ID, &setting); - if (status != NVAPI_OK || - setting.settingLocation != NVDRS_CURRENT_PROFILE_LOCATION || - setting.u32CurrentValue != PREFERRED_PSTATE_PREFER_MAX) { + if (!get_nvprefs_options().sunshine_high_power_mode) { + if (status == NVAPI_OK && + setting.settingLocation == NVDRS_CURRENT_PROFILE_LOCATION) { + // User requested to not use high power mode for sunshine.exe, + // remove the setting from application profile if it's been set previously + + status = NvAPI_DRS_DeleteProfileSetting(session_handle, profile_handle, PREFERRED_PSTATE_ID); + if (status != NVAPI_OK && status != NVAPI_SETTING_NOT_FOUND) { + nvapi_error_message(status); + error_message("NvAPI_DRS_DeleteProfileSetting() PREFERRED_PSTATE failed"); + return false; + } + modified = true; + + info_message(std::wstring(L"Removed PREFERRED_PSTATE for ") + sunshine_application_path); + } + } + else if (status != NVAPI_OK || + setting.settingLocation != NVDRS_CURRENT_PROFILE_LOCATION || + setting.u32CurrentValue != PREFERRED_PSTATE_PREFER_MAX) { // Set power setting if needed setting = {}; setting.version = NVDRS_SETTING_VER1; diff --git a/src/platform/windows/nvprefs/nvprefs_common.cpp b/src/platform/windows/nvprefs/nvprefs_common.cpp index ba15dfe3084..5e9a994c1a6 100644 --- a/src/platform/windows/nvprefs/nvprefs_common.cpp +++ b/src/platform/windows/nvprefs/nvprefs_common.cpp @@ -1,5 +1,8 @@ #include "nvprefs_common.h" +// read user override preferences from global sunshine config +#include "src/config.h" + namespace nvprefs { void @@ -22,4 +25,12 @@ namespace nvprefs { BOOST_LOG(error) << "nvprefs: " << message; } + nvprefs_options + get_nvprefs_options() { + nvprefs_options options; + options.opengl_vulkan_on_dxgi = config::video.nv_opengl_vulkan_on_dxgi; + options.sunshine_high_power_mode = config::video.nv_sunshine_high_power_mode; + return options; + } + } // namespace nvprefs diff --git a/src/platform/windows/nvprefs/nvprefs_common.h b/src/platform/windows/nvprefs/nvprefs_common.h index 7d4a661924e..f3e5c948f97 100644 --- a/src/platform/windows/nvprefs/nvprefs_common.h +++ b/src/platform/windows/nvprefs/nvprefs_common.h @@ -37,7 +37,8 @@ namespace nvprefs { struct safe_handle: public util::safe_ptr_v2 { using util::safe_ptr_v2::safe_ptr_v2; - explicit operator bool() const { + explicit + operator bool() const { auto handle = get(); return handle != NULL && handle != INVALID_HANDLE_VALUE; } @@ -67,4 +68,12 @@ namespace nvprefs { void error_message(const std::string &message); + struct nvprefs_options { + bool opengl_vulkan_on_dxgi = true; + bool sunshine_high_power_mode = true; + }; + + nvprefs_options + get_nvprefs_options(); + } // namespace nvprefs diff --git a/src/system_tray.cpp b/src/system_tray.cpp index 8a06a0a79c1..96b07e1091d 100644 --- a/src/system_tray.cpp +++ b/src/system_tray.cpp @@ -293,7 +293,7 @@ namespace system_tray { tray.icon = TRAY_ICON_PLAYING; tray.notification_title = "Stream Started"; char msg[256]; - sprintf(msg, "Streaming started for %s", app_name.c_str()); + snprintf(msg, std::size(msg), "Streaming started for %s", app_name.c_str()); tray.notification_text = msg; tray.tooltip = msg; tray.notification_icon = TRAY_ICON_PLAYING; @@ -313,7 +313,7 @@ namespace system_tray { tray.icon = TRAY_ICON_PAUSING; tray_update(&tray); char msg[256]; - sprintf(msg, "Streaming paused for %s", app_name.c_str()); + snprintf(msg, std::size(msg), "Streaming paused for %s", app_name.c_str()); tray.icon = TRAY_ICON_PAUSING; tray.notification_title = "Stream Paused"; tray.notification_text = msg; @@ -335,7 +335,7 @@ namespace system_tray { tray.icon = TRAY_ICON; tray_update(&tray); char msg[256]; - sprintf(msg, "Application %s successfully stopped", app_name.c_str()); + snprintf(msg, std::size(msg), "Application %s successfully stopped", app_name.c_str()); tray.icon = TRAY_ICON; tray.notification_icon = TRAY_ICON; tray.notification_title = "Application Stopped"; diff --git a/src/video.cpp b/src/video.cpp index 1191a8684bf..fdcc077243c 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -631,6 +631,7 @@ namespace video { { "tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY }, { "rc"s, NV_ENC_PARAMS_RC_CBR }, { "multipass"s, &config::video.nv_legacy.multipass }, + { "aq"s, &config::video.nv_legacy.aq }, }, // SDR-specific options {}, @@ -649,6 +650,7 @@ namespace video { { "tune"s, NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY }, { "rc"s, NV_ENC_PARAMS_RC_CBR }, { "multipass"s, &config::video.nv_legacy.multipass }, + { "aq"s, &config::video.nv_legacy.aq }, }, // SDR-specific options { @@ -671,6 +673,7 @@ namespace video { { "rc"s, NV_ENC_PARAMS_RC_CBR }, { "coder"s, &config::video.nv_legacy.h264_coder }, { "multipass"s, &config::video.nv_legacy.multipass }, + { "aq"s, &config::video.nv_legacy.aq }, }, // SDR-specific options { @@ -1578,6 +1581,12 @@ namespace video { } else { ctx->rc_buffer_size = bitrate / config.framerate; + +#ifndef __APPLE__ + if (encoder.name == "nvenc" && config::video.nv_legacy.vbv_percentage_increase > 0) { + ctx->rc_buffer_size += ctx->rc_buffer_size * config::video.nv_legacy.vbv_percentage_increase / 100; + } +#endif } } } diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html index 6e2b61b760c..a15f6187622 100644 --- a/src_assets/common/assets/web/config.html +++ b/src_assets/common/assets/web/config.html @@ -976,6 +976,31 @@

Disabling it is not recommended since this can lead to occasional bitrate overshoot and subsequent packet loss. +
+ + +
+ Assign higher QP values to flat regions of the video.
+ Recommended to enable when streaming at lower bitrates. +
+
+
+ + +
+ By default sunshine uses single-frame + VBV/HRD, + which means any encoded video frame size is not expected to exceed requested bitrate divided by requested frame + rate.
+ Relaxing this restriction can be beneficial and 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. +
+

@@ -1000,6 +1025,45 @@

performance when the GPU is heavily loaded.

+
+ + +
+ Override default sunshine value in situations when moonlight doesn't request one explicitly.
+ Higher values can provide larger pool for reference frame invalidation on unstable networks, but specific + decoders may not handle it well depending on video resolution.
+ Maximum value is 15.
+ Doesn't apply to AV1 since it uses predetermined value as part of specification. +
+
+
+ + +
+ Adaptive P-State algorithm which NVIDIA drivers employ doesn't work well with low latency streaming, so + sunshine requests high power mode explicitly.
+ Disabling it is not recommended since this can lead to + significantly increased encoding latency. +
+
+
+ + +
+ Sunshine can't capture fullscreen OpenGL and Vulkan programs at full frame rate unless they present on top + of DXGI.
+ This is system-wide setting that is reverted on sunshine program exit. +
+