Skip to content

Commit 0d0857d

Browse files
committed
harden vulkan encoder fallback and KMS monitor failure handling
1 parent 3c7952b commit 0d0857d

3 files changed

Lines changed: 165 additions & 31 deletions

File tree

src/platform/linux/kmsgrab.cpp

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,13 @@ namespace platf {
611611
int init(const std::string &display_name, const ::video::config_t &config) {
612612
delay = std::chrono::nanoseconds {1s} / config.framerate;
613613

614+
if (kms::card_descriptors.empty()) {
615+
// GCOVR_EXCL_START - requires KMS monitor enumeration to fail on the host.
616+
BOOST_LOG(error) << "No KMS monitor descriptors are available; aborting monitor lookup for ["sv << display_name << ']';
617+
return -1;
618+
// GCOVR_EXCL_STOP
619+
}
620+
614621
int monitor_index = util::from_view(display_name);
615622
int monitor = 0;
616623

@@ -777,7 +784,7 @@ namespace platf {
777784
}
778785
}
779786

780-
BOOST_LOG(error) << "Couldn't find monitor ["sv << monitor_index << ']';
787+
BOOST_LOG(error) << "Couldn't find monitor ["sv << monitor_index << "] in "sv << monitor << " enumerated KMS monitor(s)"sv;
781788
return -1;
782789

783790
// Neatly break from nested for loop
@@ -1616,6 +1623,12 @@ namespace platf {
16161623
std::vector<std::string> kms_display_names(mem_type_e hwdevice_type) {
16171624
int count = 0;
16181625

1626+
kms::env_width = 0;
1627+
kms::env_height = 0;
1628+
kms::env_logical_width = 0;
1629+
kms::env_logical_height = 0;
1630+
kms::card_descriptors.clear();
1631+
16191632
if (!fs::exists("/dev/dri")) {
16201633
BOOST_LOG(warning) << "Couldn't find /dev/dri, kmsgrab won't be enabled"sv;
16211634
return {};
@@ -1728,13 +1741,16 @@ namespace platf {
17281741
correlate_to_wayland(cds);
17291742
}
17301743

1731-
// Deduce the full virtual desktop size
1732-
kms::env_width = 0;
1733-
kms::env_height = 0;
1744+
BOOST_LOG(debug) << "Enumerated "sv << display_names.size() << " KMS monitor(s)"sv;
17341745

1735-
kms::env_logical_width = 0;
1736-
kms::env_logical_height = 0;
1746+
if (display_names.empty()) {
1747+
// GCOVR_EXCL_START - requires KMS monitor enumeration to fail on the host.
1748+
BOOST_LOG(error) << "No KMS monitors were found during enumeration"sv;
1749+
return {};
1750+
// GCOVR_EXCL_STOP
1751+
}
17371752

1753+
// Deduce the full virtual desktop size
17381754
for (auto &card_descriptor : cds) {
17391755
for (auto &[_, monitor_descriptor] : card_descriptor.crtc_to_monitor) {
17401756
BOOST_LOG(debug) << "Monitor description"sv;

src/platform/linux/vulkan_encode.cpp

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,62 @@ namespace vk {
106106
return -1;
107107
}
108108

109+
// GCOVR_EXCL_START - depends on the host Vulkan driver and physical GPU capabilities.
110+
static bool physical_device_supports_h264_encode(VkPhysicalDevice physical_device) {
111+
uint32_t count = 0;
112+
if (vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &count, nullptr) != VK_SUCCESS) {
113+
return false;
114+
}
115+
116+
std::vector<VkExtensionProperties> extensions(count);
117+
if (vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &count, extensions.data()) != VK_SUCCESS) {
118+
return false;
119+
}
120+
121+
bool video_queue = false;
122+
bool encode_queue = false;
123+
bool encode_h264 = false;
124+
125+
for (const auto &extension : extensions) {
126+
const std::string_view name {extension.extensionName};
127+
video_queue = video_queue || name == "VK_KHR_video_queue"sv;
128+
encode_queue = encode_queue || name == "VK_KHR_video_encode_queue"sv;
129+
encode_h264 = encode_h264 || name == "VK_KHR_video_encode_h264"sv;
130+
}
131+
132+
return video_queue && encode_queue && encode_h264;
133+
}
134+
135+
static bool has_h264_encode_physical_device() {
136+
VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO};
137+
app.apiVersion = VK_API_VERSION_1_1;
138+
139+
VkInstanceCreateInfo ci = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
140+
ci.pApplicationInfo = &app;
141+
142+
VkInstance instance = VK_NULL_HANDLE;
143+
if (vkCreateInstance(&ci, nullptr, &instance) != VK_SUCCESS) {
144+
return false;
145+
}
146+
147+
uint32_t count = 0;
148+
if (vkEnumeratePhysicalDevices(instance, &count, nullptr) != VK_SUCCESS || count == 0) {
149+
vkDestroyInstance(instance, nullptr);
150+
return false;
151+
}
152+
153+
std::vector<VkPhysicalDevice> physical_devices(count);
154+
if (vkEnumeratePhysicalDevices(instance, &count, physical_devices.data()) != VK_SUCCESS) {
155+
vkDestroyInstance(instance, nullptr);
156+
return false;
157+
}
158+
159+
const bool supported = std::any_of(std::begin(physical_devices), std::end(physical_devices), physical_device_supports_h264_encode);
160+
vkDestroyInstance(instance, nullptr);
161+
return supported;
162+
}
163+
// GCOVR_EXCL_STOP
164+
109165
struct PushConstants {
110166
std::array<float, 4> color_vec_y;
111167
std::array<float, 4> color_vec_u;
@@ -1025,11 +1081,19 @@ namespace vk {
10251081
}
10261082

10271083
bool validate() {
1028-
if (!avcodec_find_encoder_by_name("h264_vulkan") && !avcodec_find_encoder_by_name("hevc_vulkan")) {
1084+
if (!avcodec_find_encoder_by_name("h264_vulkan")) {
1085+
BOOST_LOG(info) << "FFmpeg h264_vulkan encoder is not available"sv;
10291086
return false;
10301087
}
1088+
1089+
if (!has_h264_encode_physical_device()) {
1090+
BOOST_LOG(info) << "Vulkan H.264 video encode is not supported by this device"sv;
1091+
return false;
1092+
}
1093+
10311094
AVBufferRef *dev = nullptr;
10321095
if (create_vulkan_hwdevice(&dev) < 0) {
1096+
BOOST_LOG(info) << "Failed to create Vulkan hardware device for encoder validation"sv;
10331097
return false;
10341098
}
10351099
av_buffer_unref(&dev);

src/video.cpp

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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
3541
extern "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

Comments
 (0)