@@ -31,6 +31,12 @@ extern "C" {
3131#include " sync.h"
3232#include " video.h"
3333
34+ #ifdef SUNSHINE_BUILD_VULKAN
35+ #if defined(__linux__) || defined(linux) || defined(__linux) || defined(__FreeBSD__)
36+ #include " platform/linux/vulkan_encode.h"
37+ #endif
38+ #endif
39+
3440#ifdef _WIN32
3541extern " C" {
3642 #include < libavutil/hwcontext_d3d11va.h>
@@ -1181,6 +1187,8 @@ namespace video {
11811187 int active_av1_mode;
11821188 bool last_encoder_probe_supported_ref_frames_invalidation = false ;
11831189 std::array<bool , 3 > last_encoder_probe_supported_yuv444_for_codec = {};
1190+ constexpr auto no_display_available_msg = " Unable to start capture because no display is available" sv;
1191+ constexpr auto no_encoder_selected_msg = " No encoder selected; aborting capture instead of using invalid encoder state" sv;
11841192
11851193 void reset_display (std::shared_ptr<platf::display_t > &disp, const platf::mem_type_e &type, const std::string &display_name, const config_t &config) {
11861194 // We try this twice, in case we still get an error on reinitialization
@@ -1214,16 +1222,20 @@ namespace video {
12141222 }
12151223
12161224 // Refresh the display names
1217- auto old_display_names = std::move ( display_names);
1225+ auto had_display_names = ! display_names. empty ( );
12181226 display_names = platf::display_names (dev_type);
12191227
1220- // If we now have no displays, let's put the old display array back and fail
1221- if (display_names.empty () && !old_display_names.empty ()) {
1222- BOOST_LOG (error) << " No displays were found after reenumeration!" sv;
1223- display_names = std::move (old_display_names);
1224- return ;
1225- } else if (display_names.empty ()) {
1226- display_names.emplace_back (output_name);
1228+ // If we now have no displays, fail instead of reusing stale display names.
1229+ if (display_names.empty ()) {
1230+ if (!had_display_names && !output_name.empty ()) {
1231+ display_names.emplace_back (output_name);
1232+ } else {
1233+ // GCOVR_EXCL_START - requires display enumeration to fail during capture.
1234+ BOOST_LOG (error) << (had_display_names ? " No displays were found after reenumeration!" sv : " No displays were found during enumeration!" sv);
1235+ current_display_index = -1 ;
1236+ return ;
1237+ // GCOVR_EXCL_STOP
1238+ }
12271239 }
12281240
12291241 // We now have a new display name list, so reset the index back to 0
@@ -1284,6 +1296,12 @@ namespace video {
12841296 std::vector<std::string> display_names;
12851297 int display_p = -1 ;
12861298 refresh_displays (encoder.platform_formats ->dev_type , display_names, display_p);
1299+ if (display_p < 0 || display_names.empty ()) {
1300+ // GCOVR_EXCL_START - requires display enumeration to fail during capture.
1301+ BOOST_LOG (error) << no_display_available_msg;
1302+ return ;
1303+ // GCOVR_EXCL_STOP
1304+ }
12871305 auto disp = platf::display (encoder.platform_formats ->dev_type , display_names[display_p], capture_ctxs.front ().config );
12881306 if (!disp) {
12891307 return ;
@@ -1897,27 +1915,34 @@ namespace video {
18971915 // Allow the encoding device a final opportunity to set/unset or override any options
18981916 encode_device->init_codec_options (ctx.get (), &options);
18991917
1900- if (auto status = avcodec_open2 (ctx.get (), codec, &options)) {
1901- char err_str[AV_ERROR_MAX_STRING_SIZE ] {0 };
1918+ auto status = avcodec_open2 (ctx.get (), codec, &options);
1919+ if (!status) {
1920+ // Successfully opened the codec
1921+ break ;
1922+ }
19021923
1903- if (!video_format.fallback_options .empty () && retries == 0 ) {
1904- BOOST_LOG (info)
1905- << " Retrying with fallback configuration options for [" sv << video_format.name << " ] after error: " sv
1906- << av_make_error_string (err_str, AV_ERROR_MAX_STRING_SIZE , status);
1924+ char err_str[AV_ERROR_MAX_STRING_SIZE ] {0 };
1925+ if (!video_format.fallback_options .empty () && retries == 0 ) {
1926+ BOOST_LOG (info)
1927+ << " Retrying with fallback configuration options for [" sv << video_format.name << " ] after error: " sv
1928+ << av_make_error_string (err_str, AV_ERROR_MAX_STRING_SIZE , status);
19071929
1908- continue ;
1909- } else {
1910- BOOST_LOG (error)
1911- << " Could not open codec [" sv
1912- << video_format.name << " ]: " sv
1913- << av_make_error_string (err_str, AV_ERROR_MAX_STRING_SIZE , status);
1930+ continue ;
1931+ }
19141932
1915- return nullptr ;
1916- }
1933+ BOOST_LOG (error)
1934+ << " Could not open codec [" sv
1935+ << video_format.name << " ]: " sv
1936+ << av_make_error_string (err_str, AV_ERROR_MAX_STRING_SIZE , status);
1937+
1938+ if (encoder.name == " vulkan" sv) {
1939+ BOOST_LOG (error)
1940+ << " Vulkan encoder setup failed for [" sv << video_format.name
1941+ << " ]; this GPU or driver does not expose the required Vulkan video encode capability. "
1942+ << " Sunshine will discard this encoder and continue probing fallback encoders." sv;
19171943 }
19181944
1919- // Successfully opened the codec
1920- break ;
1945+ return nullptr ;
19211946 }
19221947
19231948 avcodec_frame_t frame {av_frame_alloc ()};
@@ -2286,6 +2311,12 @@ namespace video {
22862311 while (encode_session_ctx_queue.running ()) {
22872312 // Refresh display names since a display removal might have caused the reinitialization
22882313 refresh_displays (encoder.platform_formats ->dev_type , display_names, display_p);
2314+ if (display_p < 0 || display_names.empty ()) {
2315+ // GCOVR_EXCL_START - requires display enumeration to fail during capture.
2316+ BOOST_LOG (error) << no_display_available_msg;
2317+ return encode_e::error;
2318+ // GCOVR_EXCL_STOP
2319+ }
22892320
22902321 // Process any pending display switch with the new list of displays
22912322 if (switch_display_event->peek ()) {
@@ -2491,6 +2522,13 @@ namespace video {
24912522 display = ref->display_wp ->lock ();
24922523 }
24932524
2525+ if (!chosen_encoder) {
2526+ // GCOVR_EXCL_START - defensive guard for invalid global encoder state.
2527+ BOOST_LOG (error) << no_encoder_selected_msg;
2528+ return ;
2529+ // GCOVR_EXCL_STOP
2530+ }
2531+
24942532 auto &encoder = *chosen_encoder;
24952533
24962534 auto encode_device = make_encode_device (*display, encoder, config);
@@ -2533,6 +2571,13 @@ namespace video {
25332571 ) {
25342572 auto idr_events = mail->event <bool >(mail::idr);
25352573
2574+ if (!chosen_encoder) {
2575+ // GCOVR_EXCL_START - defensive guard for invalid global encoder state.
2576+ BOOST_LOG (error) << no_encoder_selected_msg;
2577+ return ;
2578+ // GCOVR_EXCL_STOP
2579+ }
2580+
25362581 idr_events->raise (true );
25372582 if (chosen_encoder->flags & PARALLEL_ENCODING ) {
25382583 capture_async (std::move (mail), config, channel_data);
@@ -2628,6 +2673,14 @@ namespace video {
26282673 encoder.hevc .capabilities .set ();
26292674 encoder.av1 .capabilities .set ();
26302675
2676+ #ifdef SUNSHINE_BUILD_VULKAN
2677+ if (encoder.name == " vulkan" sv && !vk::validate ()) {
2678+ fg.disable ();
2679+ BOOST_LOG (info) << " Encoder [" sv << encoder.name << " ] is not supported on this GPU" sv;
2680+ return false ;
2681+ }
2682+ #endif
2683+
26312684 // First, test encoder viability
26322685 config_t config_max_ref_frames {1920 , 1080 , 60 , 6000 , 1000 , 1 , 1 , 1 , 0 , 0 , 0 };
26332686 config_t config_autoselect {1920 , 1080 , 60 , 6000 , 1000 , 1 , 0 , 1 , 0 , 0 , 0 };
@@ -3013,6 +3066,7 @@ namespace video {
30133066
30143067 if (encode_device && encode_device->data ) {
30153068 if (((vulkan_init_avcodec_hardware_input_buffer_fn) encode_device->data )(encode_device, &hw_device_buf)) {
3069+ BOOST_LOG (error) << " Failed to create a Vulkan device from the capture backend; aborting Vulkan encoder setup" sv;
30163070 return -1 ;
30173071 }
30183072 return hw_device_buf;
0 commit comments