Skip to content

Commit b6c8fec

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

3 files changed

Lines changed: 153 additions & 31 deletions

File tree

src/platform/linux/kmsgrab.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,11 @@ 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+
BOOST_LOG(error) << "No KMS monitor descriptors are available; aborting monitor lookup for ["sv << display_name << ']';
616+
return -1;
617+
}
618+
614619
int monitor_index = util::from_view(display_name);
615620
int monitor = 0;
616621

@@ -777,7 +782,7 @@ namespace platf {
777782
}
778783
}
779784

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

783788
// Neatly break from nested for loop
@@ -1616,6 +1621,12 @@ namespace platf {
16161621
std::vector<std::string> kms_display_names(mem_type_e hwdevice_type) {
16171622
int count = 0;
16181623

1624+
kms::env_width = 0;
1625+
kms::env_height = 0;
1626+
kms::env_logical_width = 0;
1627+
kms::env_logical_height = 0;
1628+
kms::card_descriptors.clear();
1629+
16191630
if (!fs::exists("/dev/dri")) {
16201631
BOOST_LOG(warning) << "Couldn't find /dev/dri, kmsgrab won't be enabled"sv;
16211632
return {};
@@ -1728,13 +1739,14 @@ namespace platf {
17281739
correlate_to_wayland(cds);
17291740
}
17301741

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

1735-
kms::env_logical_width = 0;
1736-
kms::env_logical_height = 0;
1744+
if (display_names.empty()) {
1745+
BOOST_LOG(error) << "No KMS monitors were found during enumeration"sv;
1746+
return {};
1747+
}
17371748

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

src/platform/linux/vulkan_encode.cpp

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

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

10271085
bool validate() {
1028-
if (!avcodec_find_encoder_by_name("h264_vulkan") && !avcodec_find_encoder_by_name("hevc_vulkan")) {
1086+
if (!avcodec_find_encoder_by_name("h264_vulkan")) {
1087+
BOOST_LOG(info) << "FFmpeg h264_vulkan encoder is not available"sv;
10291088
return false;
10301089
}
1090+
1091+
if (!has_h264_encode_physical_device()) {
1092+
BOOST_LOG(info) << "Vulkan H.264 video encode is not supported by this device"sv;
1093+
return false;
1094+
}
1095+
10311096
AVBufferRef *dev = nullptr;
10321097
if (create_vulkan_hwdevice(&dev) < 0) {
1098+
BOOST_LOG(info) << "Failed to create Vulkan hardware device for encoder validation"sv;
10331099
return false;
10341100
}
10351101
av_buffer_unref(&dev);

src/video.cpp

Lines changed: 68 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,18 @@ 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+
BOOST_LOG(error) << (had_display_names ? "No displays were found after reenumeration!"sv : "No displays were found during enumeration!"sv);
1234+
current_display_index = -1;
1235+
return;
1236+
}
12271237
}
12281238

12291239
// We now have a new display name list, so reset the index back to 0
@@ -1284,6 +1294,10 @@ namespace video {
12841294
std::vector<std::string> display_names;
12851295
int display_p = -1;
12861296
refresh_displays(encoder.platform_formats->dev_type, display_names, display_p);
1297+
if (display_p < 0 || display_names.empty()) {
1298+
BOOST_LOG(error) << no_display_available_msg;
1299+
return;
1300+
}
12871301
auto disp = platf::display(encoder.platform_formats->dev_type, display_names[display_p], capture_ctxs.front().config);
12881302
if (!disp) {
12891303
return;
@@ -1897,27 +1911,34 @@ namespace video {
18971911
// Allow the encoding device a final opportunity to set/unset or override any options
18981912
encode_device->init_codec_options(ctx.get(), &options);
18991913

1900-
if (auto status = avcodec_open2(ctx.get(), codec, &options)) {
1901-
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
1914+
auto status = avcodec_open2(ctx.get(), codec, &options);
1915+
if (!status) {
1916+
// Successfully opened the codec
1917+
break;
1918+
}
19021919

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);
1920+
char err_str[AV_ERROR_MAX_STRING_SIZE] {0};
1921+
if (!video_format.fallback_options.empty() && retries == 0) {
1922+
BOOST_LOG(info)
1923+
<< "Retrying with fallback configuration options for ["sv << video_format.name << "] after error: "sv
1924+
<< av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, status);
19071925

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);
1926+
continue;
1927+
}
19141928

1915-
return nullptr;
1916-
}
1929+
BOOST_LOG(error)
1930+
<< "Could not open codec ["sv
1931+
<< video_format.name << "]: "sv
1932+
<< av_make_error_string(err_str, AV_ERROR_MAX_STRING_SIZE, status);
1933+
1934+
if (encoder.name == "vulkan"sv) {
1935+
BOOST_LOG(error)
1936+
<< "Vulkan encoder setup failed for ["sv << video_format.name
1937+
<< "]; this GPU or driver does not expose the required Vulkan video encode capability. "
1938+
<< "Sunshine will discard this encoder and continue probing fallback encoders."sv;
19171939
}
19181940

1919-
// Successfully opened the codec
1920-
break;
1941+
return nullptr;
19211942
}
19221943

19231944
avcodec_frame_t frame {av_frame_alloc()};
@@ -2286,6 +2307,10 @@ namespace video {
22862307
while (encode_session_ctx_queue.running()) {
22872308
// Refresh display names since a display removal might have caused the reinitialization
22882309
refresh_displays(encoder.platform_formats->dev_type, display_names, display_p);
2310+
if (display_p < 0 || display_names.empty()) {
2311+
BOOST_LOG(error) << no_display_available_msg;
2312+
return encode_e::error;
2313+
}
22892314

22902315
// Process any pending display switch with the new list of displays
22912316
if (switch_display_event->peek()) {
@@ -2491,6 +2516,11 @@ namespace video {
24912516
display = ref->display_wp->lock();
24922517
}
24932518

2519+
if (!chosen_encoder) {
2520+
BOOST_LOG(error) << no_encoder_selected_msg;
2521+
return;
2522+
}
2523+
24942524
auto &encoder = *chosen_encoder;
24952525

24962526
auto encode_device = make_encode_device(*display, encoder, config);
@@ -2533,6 +2563,11 @@ namespace video {
25332563
) {
25342564
auto idr_events = mail->event<bool>(mail::idr);
25352565

2566+
if (!chosen_encoder) {
2567+
BOOST_LOG(error) << no_encoder_selected_msg;
2568+
return;
2569+
}
2570+
25362571
idr_events->raise(true);
25372572
if (chosen_encoder->flags & PARALLEL_ENCODING) {
25382573
capture_async(std::move(mail), config, channel_data);
@@ -2628,6 +2663,14 @@ namespace video {
26282663
encoder.hevc.capabilities.set();
26292664
encoder.av1.capabilities.set();
26302665

2666+
#ifdef SUNSHINE_BUILD_VULKAN
2667+
if (encoder.name == "vulkan"sv && !vk::validate()) {
2668+
fg.disable();
2669+
BOOST_LOG(info) << "Encoder ["sv << encoder.name << "] is not supported on this GPU"sv;
2670+
return false;
2671+
}
2672+
#endif
2673+
26312674
// First, test encoder viability
26322675
config_t config_max_ref_frames {1920, 1080, 60, 6000, 1000, 1, 1, 1, 0, 0, 0};
26332676
config_t config_autoselect {1920, 1080, 60, 6000, 1000, 1, 0, 1, 0, 0, 0};
@@ -3013,6 +3056,7 @@ namespace video {
30133056

30143057
if (encode_device && encode_device->data) {
30153058
if (((vulkan_init_avcodec_hardware_input_buffer_fn) encode_device->data)(encode_device, &hw_device_buf)) {
3059+
BOOST_LOG(error) << "Failed to create a Vulkan device from the capture backend; aborting Vulkan encoder setup"sv;
30163060
return -1;
30173061
}
30183062
return hw_device_buf;

0 commit comments

Comments
 (0)