From 7c51fbfd18ec59df4453093e8d5d83c3f1b4df50 Mon Sep 17 00:00:00 2001 From: Loki Date: Tue, 31 Aug 2021 20:46:50 +0200 Subject: [PATCH] Correlate KMS output to wayland xdg-output --- CMakeLists.txt | 2 + sunshine/platform/linux/kmsgrab.cpp | 339 +++++++++++++++++++++++----- sunshine/platform/linux/wayland.cpp | 38 ++++ sunshine/platform/linux/wayland.h | 4 +- 4 files changed, 325 insertions(+), 58 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 11f83774..275be431 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,6 +133,8 @@ else() if(WAYLAND_FOUND) add_compile_definitions(SUNSHINE_BUILD_WAYLAND) macro(genWayland FILENAME) + make_directory(${CMAKE_BINARY_DIR}/generated-src) + message("wayland-scanner private-code ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.c") message("wayland-scanner client-header ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.h") execute_process( diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index d97eee13..4984709b 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -15,6 +15,7 @@ // Cursor rendering support through x11 #include "graphics.h" #include "vaapi.h" +#include "wayland.h" #include "x11grab.h" using namespace std::literals; @@ -24,6 +25,8 @@ namespace platf { namespace kms { using plane_res_t = util::safe_ptr; +using encoder_t = util::safe_ptr; +using res_t = util::safe_ptr; using plane_t = util::safe_ptr; using fb_t = util::safe_ptr; using fb2_t = util::safe_ptr; @@ -31,6 +34,8 @@ using crtc_t = util::safe_ptr; using obj_prop_t = util::safe_ptr; using prop_t = util::safe_ptr; +using conn_type_count_t = std::map; + static int env_width; static int env_height; @@ -47,6 +52,58 @@ std::string_view plane_type(std::uint64_t val) { return "UNKNOWN"sv; } +struct connector_t { + // For example: HDMI-A or HDMI + std::uint32_t type; + + // Equals zero if not applicable + std::uint32_t crtc_id; + + // For example HDMI-A-{index} or HDMI-{index} + std::uint32_t index; + + bool connected; +}; + +struct monitor_t { + std::uint32_t type; + + std::uint32_t index; + + platf::touch_port_t viewport; +}; + +struct card_descriptor_t { + std::string path; + + std::map crtc_to_monitor; +}; + +static std::vector card_descriptors; + +static std::uint32_t from_view(const std::string_view &string) { +#define _CONVERT(x, y) \ + if(string == x) return DRM_MODE_CONNECTOR_##y + + _CONVERT("VGA"sv, VGA); + _CONVERT("DVI-I"sv, DVII); + _CONVERT("DVI-D"sv, DVID); + _CONVERT("DVI-A"sv, DVIA); + _CONVERT("S-Video"sv, SVIDEO); + _CONVERT("LVDS"sv, LVDS); + _CONVERT("DIN"sv, 9PinDIN); + _CONVERT("DisplayPort"sv, DisplayPort); + _CONVERT("DP"sv, DisplayPort); + _CONVERT("HDMI-A"sv, HDMIA); + _CONVERT("HDMI"sv, HDMIA); + _CONVERT("HDMI-B"sv, HDMIB); + _CONVERT("eDP"sv, eDP); + _CONVERT("DSI"sv, DSI); + + BOOST_LOG(error) << "Unknown Monitor connector type ["sv << string << "]: Please report this to the Github issue tracker"sv; + return DRM_MODE_CONNECTOR_Unknown; +} + class plane_it_t : public util::it_wrap_t { public: plane_it_t(int fd, std::uint32_t *plane_p, std::uint32_t *end) @@ -96,6 +153,8 @@ public: class card_t { public: + using connector_interal_t = util::safe_ptr; + int init(const char *path) { fd.el = open(path, O_RDWR); @@ -134,6 +193,67 @@ public: return drmModeGetCrtc(fd.el, id); } + encoder_t encoder(std::uint32_t id) { + return drmModeGetEncoder(fd.el, id); + } + + res_t res() { + return drmModeGetResources(fd.el); + } + + bool is_cursor(std::uint32_t plane_id) { + auto props = plane_props(plane_id); + for(auto &[prop, val] : props) { + if(prop->name == "type"sv) { + if(val == DRM_PLANE_TYPE_CURSOR) { + return true; + } + else { + return false; + } + } + } + + return false; + } + + connector_interal_t connector(std::uint32_t id) { + return drmModeGetConnector(fd.el, id); + } + + std::vector monitors(conn_type_count_t &conn_type_count) { + auto resources = res(); + if(!resources) { + BOOST_LOG(error) << "Couldn't get connector resources"sv; + return {}; + } + + std::vector monitors; + std::for_each_n(resources->connectors, resources->count_connectors, [this, &conn_type_count, &monitors](std::uint32_t id) { + auto conn = connector(id); + + std::uint32_t crtc_id = 0; + + if(conn->encoder_id) { + auto enc = encoder(conn->encoder_id); + if(enc) { + crtc_id = enc->crtc_id; + } + } + + auto index = ++conn_type_count[conn->connector_type]; + + monitors.emplace_back(connector_t { + conn->connector_type, + crtc_id, + index, + conn->connection == DRM_MODE_CONNECTED, + }); + }); + + return monitors; + } + file_t handleFD(std::uint32_t handle) { file_t fb_fd; @@ -145,7 +265,6 @@ public: return fb_fd; } - std::vector> props(std::uint32_t id, std::uint32_t type) { obj_prop_t obj_prop = drmModeObjectGetProperties(fd.el, id, type); @@ -192,6 +311,20 @@ public: plane_res_t plane_res; }; +std::map map_crtc_to_monitor(const std::vector &connectors) { + std::map result; + + for(auto &connector : connectors) { + result.emplace(connector.crtc_id, + monitor_t { + connector.type, + connector.index, + }); + } + + return result; +} + struct kms_img_t : public img_t { ~kms_img_t() override { delete[] data; @@ -261,22 +394,7 @@ public: auto end = std::end(card); for(auto plane = std::begin(card); plane != end; ++plane) { - bool cursor = false; - - auto props = card.plane_props(plane->plane_id); - for(auto &[prop, val] : props) { - if(prop->name == "type"sv) { - BOOST_LOG(verbose) << prop->name << "::"sv << kms::plane_type(val); - - if(val == DRM_PLANE_TYPE_CURSOR) { - // Don't count as a monitor when it is a cursor - cursor = true; - break; - } - } - } - - if(cursor) { + if(card.is_cursor(plane->plane_id)) { continue; } @@ -305,20 +423,47 @@ public: BOOST_LOG(info) << "Found monitor for DRM screencasting"sv; + // We need to find the correct /dev/dri/card{nr} to correlate the crtc_id with the monitor descriptor + auto pos = std::find_if(std::begin(card_descriptors), std::end(card_descriptors), [&](card_descriptor_t &cd) { + return cd.path == filestring; + }); + + if(pos == std::end(card_descriptors)) { + // This code path shouldn't happend, but it's there just in case. + // card_descriptors is part of the guesswork after all. + BOOST_LOG(error) << "Couldn't find ["sv << entry.path() << "]: This shouldn't have happened :/"sv; + return -1; + } + auto crct = card.crtc(plane->crtc_id); kms::print(plane.get(), fb.get(), crct.get()); - img_width = fb->width; - img_height = fb->height; - - width = crct->width; - height = crct->height; + img_width = fb->width; + img_height = fb->height; + img_offset_x = crct->x; + img_offset_y = crct->y; this->env_width = ::platf::kms::env_width; this->env_height = ::platf::kms::env_height; - offset_x = crct->x; - offset_y = crct->y; + auto monitor = pos->crtc_to_monitor.find(plane->crtc_id); + if(monitor != std::end(pos->crtc_to_monitor)) { + auto &viewport = monitor->second.viewport; + + width = viewport.width; + height = viewport.height; + offset_x = viewport.offset_x; + offset_y = viewport.offset_y; + } + // This code path shouldn't happend, but it's there just in case. + // crtc_to_monitor is part of the guesswork after all. + else { + BOOST_LOG(warning) << "Couldn't find crtc_id, this shouldn't have happened :\\"sv; + width = crct->width; + height = crct->height; + offset_x = crct->x; + offset_y = crct->y; + } this->card = std::move(card); @@ -379,6 +524,8 @@ public: std::chrono::nanoseconds delay; int img_width, img_height; + int img_offset_x, img_offset_y; + int pitch; int offset; int plane_id; @@ -489,10 +636,10 @@ public: auto &rgb = *rgb_opt; gl::ctx.BindTexture(GL_TEXTURE_2D, rgb->tex[0]); - gl::ctx.GetTextureSubImage(rgb->tex[0], 0, offset_x, offset_y, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->height * img_out_base->row_pitch, img_out_base->data); + gl::ctx.GetTextureSubImage(rgb->tex[0], 0, img_offset_x, img_offset_y, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->height * img_out_base->row_pitch, img_out_base->data); if(cursor_opt && cursor) { - cursor_opt->blend(*img_out_base, offset_x, offset_y); + cursor_opt->blend(*img_out_base, img_offset_x, img_offset_y); } return capture_e::ok; @@ -524,7 +671,7 @@ public: std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override { if(mem_type == mem_type_e::vaapi) { - return va::make_hwdevice(width, height, dup(card.fd.el), offset_x, offset_y, true); + return va::make_hwdevice(width, height, dup(card.fd.el), img_offset_x, img_offset_y, true); } BOOST_LOG(error) << "Unsupported pixel format for egl::display_vram_t: "sv << platf::from_pix_fmt(pix_fmt); @@ -634,6 +781,7 @@ public: std::uint64_t sequence; }; + } // namespace kms std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { @@ -656,11 +804,69 @@ std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::stri return disp; } + +/** + * On Wayland, it's not possible to determine the position of the monitor on the desktop with KMS. + * Wayland does allow applications to query attached monitors on the desktop, + * however, the naming scheme is not standardized across implementations. + * + * As a result, correlating the KMS output to the wayland outputs is guess work at best. + * But, it's necessary for absolute mouse coordinates to work. + * + * This is an ugly hack :( + */ +void correlate_to_wayland(std::vector &cds) { + auto monitors = wl::monitors(); + + for(auto &monitor : monitors) { + std::string_view name = monitor->name; + + BOOST_LOG(info) << name << ": "sv << monitor->description; + + // Try to convert names in the format: + // {type}-{index} + // {index} is n'th occurence of {type} + auto index_begin = name.find_last_of('-'); + + std::uint32_t index; + if(index_begin == std::string_view::npos) { + index = 1; + } + else { + index = std::max(1, util::from_view(name.substr(index_begin + 1))); + } + + auto type = kms::from_view(name.substr(0, index_begin)); + + for(auto &card_descriptor : cds) { + for(auto &[_, monitor_descriptor] : card_descriptor.crtc_to_monitor) { + if(monitor_descriptor.index == index && monitor_descriptor.type == type) { + monitor_descriptor.viewport.offset_x = monitor->viewport.offset_x; + monitor_descriptor.viewport.offset_y = monitor->viewport.offset_y; + + // A sanity check, it's guesswork after all. + if( + monitor_descriptor.viewport.width != monitor->viewport.width || + monitor_descriptor.viewport.height != monitor->viewport.height) { + BOOST_LOG(warning) + << "Mismatch on expected Resolution compared to actual resolution: "sv + << monitor_descriptor.viewport.width << 'x' << monitor_descriptor.viewport.height + << " vs "sv + << monitor->viewport.width << 'x' << monitor->viewport.height; + } + + goto break_for_loop; + } + } + } + break_for_loop: + + BOOST_LOG(verbose) << "Reduced to name: "sv << name << ": "sv << index; + } +} + // A list of names of displays accepted as display_name std::vector kms_display_names() { - kms::env_width = 0; - kms::env_height = 0; - int count = 0; if(!gbm::create_device) { @@ -668,6 +874,9 @@ std::vector kms_display_names() { return {}; } + kms::conn_type_count_t conn_type_count; + + std::vector cds; std::vector display_names; fs::path card_dir { "/dev/dri"sv }; @@ -684,6 +893,8 @@ std::vector kms_display_names() { return {}; } + auto crtc_to_monitor = kms::map_crtc_to_monitor(card.monitors(conn_type_count)); + auto end = std::end(card); for(auto plane = std::begin(card); plane != end; ++plane) { auto fb = card.fb2(plane.get()); @@ -698,30 +909,8 @@ std::vector kms_display_names() { break; } - bool cursor = false; - { - BOOST_LOG(verbose) << "PLANE INFO ["sv << count << ']'; - auto props = card.plane_props(plane->plane_id); - for(auto &[prop, val] : props) { - if(prop->name == "type"sv) { - BOOST_LOG(verbose) << prop->name << "::"sv << kms::plane_type(val); - - if(val == DRM_PLANE_TYPE_CURSOR) { - cursor = true; - } - } - else { - BOOST_LOG(verbose) << prop->name << "::"sv << val; - } - } - } - - { - BOOST_LOG(verbose) << "CRTC INFO"sv; - auto props = card.crtc_props(plane->crtc_id); - for(auto &[prop, val] : props) { - BOOST_LOG(verbose) << prop->name << "::"sv << val; - } + if(card.is_cursor(plane->plane_id)) { + continue; } // This appears to return the offset of the monitor @@ -731,17 +920,53 @@ std::vector kms_display_names() { return {}; } + auto it = crtc_to_monitor.find(plane->crtc_id); + if(it != std::end(crtc_to_monitor)) { + it->second.viewport = platf::touch_port_t { + (int)crtc->x, + (int)crtc->y, + (int)crtc->width, + (int)crtc->height, + }; + } + kms::env_width = std::max(kms::env_width, (int)(crtc->x + crtc->width)); kms::env_height = std::max(kms::env_height, (int)(crtc->y + crtc->height)); kms::print(plane.get(), fb.get(), crtc.get()); - if(!cursor) { - display_names.emplace_back(std::to_string(count++)); - } + display_names.emplace_back(std::to_string(count++)); + } + + cds.emplace_back(kms::card_descriptor_t { + std::move(file), + std::move(crtc_to_monitor), + }); + } + + if(!wl::init()) { + correlate_to_wayland(cds); + } + + // Deduce the full virtual desktop size + kms::env_width = 0; + kms::env_height = 0; + + for(auto &card_descriptor : cds) { + for(auto &[_, monitor_descriptor] : card_descriptor.crtc_to_monitor) { + BOOST_LOG(debug) << "Monitor description"sv; + BOOST_LOG(debug) << "Resolution: "sv << monitor_descriptor.viewport.width << 'x' << monitor_descriptor.viewport.height; + BOOST_LOG(debug) << "Offset: "sv << monitor_descriptor.viewport.offset_x << 'x' << monitor_descriptor.viewport.offset_y; + + kms::env_width = std::max(kms::env_width, (int)(monitor_descriptor.viewport.offset_x + monitor_descriptor.viewport.width)); + kms::env_height = std::max(kms::env_height, (int)(monitor_descriptor.viewport.offset_y + monitor_descriptor.viewport.height)); } } + BOOST_LOG(debug) << "Desktop resolution: "sv << kms::env_width << 'x' << kms::env_height; + + kms::card_descriptors = std::move(cds); + return display_names; } diff --git a/sunshine/platform/linux/wayland.cpp b/sunshine/platform/linux/wayland.cpp index 9ca20f2a..9da8718c 100644 --- a/sunshine/platform/linux/wayland.cpp +++ b/sunshine/platform/linux/wayland.cpp @@ -219,6 +219,44 @@ void frame_t::destroy() { obj_count = 0; } +std::vector> monitors(const char *display_name) { + display_t display; + + if(display.init(display_name)) { + return {}; + } + + interface_t interface; + interface.listen(display.registry()); + + display.roundtrip(); + + if(!interface[interface_t::XDG_OUTPUT]) { + BOOST_LOG(error) << "Missing Wayland wire XDG_OUTPUT"sv; + return {}; + } + + for(auto &monitor : interface.monitors) { + monitor->listen(interface.output_manager); + } + + display.roundtrip(); + + return std::move(interface.monitors); +} + +static bool validate() { + display_t display; + + return display.init() == 0; +} + +int init() { + static bool validated = validate(); + + return !validated; +} + } // namespace wl #pragma GCC diagnostic pop \ No newline at end of file diff --git a/sunshine/platform/linux/wayland.h b/sunshine/platform/linux/wayland.h index ca876922..67bfa884 100644 --- a/sunshine/platform/linux/wayland.h +++ b/sunshine/platform/linux/wayland.h @@ -175,7 +175,9 @@ private: display_internal_t display_internal; }; -void test(); +std::vector> monitors(const char *display_name = nullptr); + +int init(); } // namespace wl #endif \ No newline at end of file