From 065e9e718a448b9784a1dac28e1f06b3db4cd1aa Mon Sep 17 00:00:00 2001 From: loki Date: Fri, 6 Aug 2021 15:55:38 +0200 Subject: [PATCH] Choose between x11grab and kmsgrab at runtime --- CMakeLists.txt | 16 +- sunshine/platform/linux/kmsgrab.cpp | 239 +++++++++++++++++++++------- sunshine/platform/linux/misc.cpp | 150 +++++++++++++---- sunshine/platform/linux/misc.h | 1 - sunshine/platform/linux/vaapi.cpp | 21 +-- sunshine/platform/linux/vaapi.h | 2 + sunshine/platform/linux/x11grab.cpp | 22 +-- sunshine/utility.h | 8 + 8 files changed, 341 insertions(+), 118 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fc28a460..b0e36791 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,20 +106,28 @@ else() add_compile_definitions(SUNSHINE_PLATFORM="linux") list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") - find_package(X11) - find_package(LIBDRM) + if(NOT DEFINED SUNSHINE_DISABLE_X11) + find_package(X11) + endif() + if(NOT DEFINED SUNSHINE_DISABLE_DRM) + find_package(LIBDRM) + endif() find_package(FFMPEG REQUIRED) if(X11_FOUND) + add_compile_definitions(SUNSHINE_BUILD_X11) include_directories(${X11_INCLUDE_DIR}) list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/x11grab.cpp) - elseif(LIBDRM_FOUND) + endif() + if(LIBDRM_FOUND) + add_compile_definitions(SUNSHINE_BUILD_DRM) include_directories(${LIBDRM_INCLUDE_DIRS}) list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES}) list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/kmsgrab.cpp) list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1) - else() + endif() + if(NOT X11_FOUND AND NOT LIBDRM_FOUND) message(FATAL "Couldn't find either x11 or libdrm") endif() diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index 3cd51e5c..b04f83f4 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -7,6 +7,7 @@ #include "sunshine/main.h" #include "sunshine/platform/common.h" +#include "sunshine/round_robin.h" #include "sunshine/utility.h" #include "graphics.h" @@ -21,6 +22,102 @@ using plane_t = util::safe_ptr; using fb_t = util::safe_ptr; using fb2_t = util::safe_ptr; +class plane_it_t : public util::it_wrap_t { +public: + plane_it_t(int fd, std::uint32_t *plane_p, std::uint32_t *end) + : fd { fd }, plane_p { plane_p }, end { end } { + inc(); + } + + plane_it_t(int fd, std::uint32_t *end) + : fd { fd }, plane_p { end }, end { end } {} + + void inc() { + this->plane.reset(); + + for(; plane_p != end; ++plane_p) { + plane_t plane = drmModeGetPlane(fd, *plane_p); + + if(!plane) { + BOOST_LOG(error) << "Couldn't get drm plane ["sv << (end - plane_p) << "]: "sv << strerror(errno); + continue; + } + + // If this plane is unused + if(plane->fb_id) { + this->plane = util::make_shared(plane.release()); + + // One last increment + ++plane_p; + break; + } + } + } + + bool eq(const plane_it_t &other) const { + return plane_p == other.plane_p; + } + + plane_t::pointer get() { + return plane.get(); + } + + int fd; + std::uint32_t *plane_p; + std::uint32_t *end; + + util::shared_t plane; +}; + +class card_t { +public: + int init(const char *path) { + fd.el = open(path, O_RDWR); + + if(fd.el < 0) { + BOOST_LOG(error) << "Couldn't open: "sv << path << ": "sv << strerror(errno); + return -1; + } + + if(drmSetClientCap(fd.el, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { + BOOST_LOG(error) << "Couldn't expose some/all drm planes"sv; + return -1; + } + + plane_res.reset(drmModeGetPlaneResources(fd.el)); + if(!plane_res) { + BOOST_LOG(error) << "Couldn't get drm plane resources"sv; + return -1; + } + + return 0; + } + + fb_t fb(plane_t::pointer plane) { + return drmModeGetFB(fd.el, plane->fb_id); + } + + plane_t operator[](std::uint32_t index) { + return drmModeGetPlane(fd.el, plane_res->planes[index]); + } + + std::uint32_t count() { + return plane_res->count_planes; + } + + plane_it_t begin() const { + return plane_it_t { fd.el, plane_res->planes, plane_res->planes + plane_res->count_planes }; + } + + plane_it_t end() const { + return plane_it_t { fd.el, plane_res->planes + plane_res->count_planes }; + } + + + egl::file_t fd; + plane_res_t plane_res; +}; + struct kms_img_t : public img_t { ~kms_img_t() override { delete[] data; @@ -28,6 +125,33 @@ struct kms_img_t : public img_t { } }; +void print(plane_t::pointer plane, fb_t::pointer fb) { + BOOST_LOG(debug) + << "x("sv << plane->x + << ") y("sv << plane->y + << ") crtc_x("sv << plane->crtc_x + << ") crtc_y("sv << plane->crtc_y + << ") crtc_id("sv << plane->crtc_id + << ')'; + + BOOST_LOG(debug) + << "Resolution: "sv << fb->width << 'x' << fb->height + << ": Pitch: "sv << fb->pitch + << ": bpp: "sv << fb->bpp + << ": depth: "sv << fb->depth; + + std::stringstream ss; + + ss << "Format ["sv; + std::for_each_n(plane->formats, plane->count_formats - 1, [&ss](auto format) { + ss << util::view(format) << ", "sv; + }); + + ss << util::view(plane->formats[plane->count_formats - 1]) << ']'; + + BOOST_LOG(debug) << ss.str(); +} + class display_t : public platf::display_t { public: display_t() : platf::display_t() {} @@ -41,88 +165,59 @@ public: delay = std::chrono::nanoseconds { 1s } / framerate; constexpr auto path = "/dev/dri/card1"; - - fd.el = open(path, O_RDWR); - - if(fd.el < 0) { - BOOST_LOG(error) << "Couldn't open: "sv << path << ": "sv << strerror(errno); + if(card.init(path)) { return -1; } - if(drmSetClientCap(fd.el, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { - BOOST_LOG(error) << "Couldn't expose some/all drm planes"sv; - return -1; - } - - plane_res_t planes = drmModeGetPlaneResources(fd.el); - if(!planes) { - BOOST_LOG(error) << "Couldn't get drm plane resources"sv; - return -1; - } - - int monitor_index = 0; + int monitor_index = util::from_view(display_name); int monitor = 0; - BOOST_LOG(info) << "Found "sv << planes->count_planes << " planes"sv; - int pitch; - for(std::uint32_t x = 0; x < planes->count_planes; ++x) { - plane_t plane = drmModeGetPlane(fd.el, planes->planes[x]); - if(!plane) { - BOOST_LOG(error) << "Couldn't get drm plane ["sv << x << "]: "sv << strerror(errno); + auto end = std::end(card); + for(auto plane = std::begin(card); plane != end; ++plane) { + if(monitor != monitor_index) { + ++monitor; continue; } - if(!plane->fb_id) { - continue; - } - - fb_t fb = drmModeGetFB(fd.el, plane->fb_id); + auto fb = card.fb(plane.get()); if(!fb) { BOOST_LOG(error) << "Couldn't get drm fb for plane ["sv << plane->fb_id << "]: "sv << strerror(errno); - continue; - } - - if(monitor++ != monitor_index) { - continue; + return -1; } if(!fb->handle) { BOOST_LOG(error) - << "Couldn't get handle for Framebuffer ["sv << plane->fb_id << "]: Possibly not permitted: do [sudo setcap cap_sys_admin+ep sunshine]"sv; - continue; + << "Couldn't get handle for DRM Framebuffer ["sv << plane->fb_id << "]: Possibly not permitted: do [sudo setcap cap_sys_admin+ep sunshine]"sv; + return -1; } - BOOST_LOG(info) << "Opened Framebuffer for plane ["sv << plane->fb_id << ']'; - - auto status = drmPrimeHandleToFD(fd.el, fb->handle, 0 /* flags */, &fb_fd.el); + auto status = drmPrimeHandleToFD(card.fd.el, fb->handle, 0 /* flags */, &fb_fd.el); if(status || fb_fd.el < 0) { BOOST_LOG(error) << "Couldn't get primary file descriptor for Framebuffer ["sv << fb->fb_id << "]: "sv << strerror(errno); continue; } - BOOST_LOG(info) - << "x("sv << plane->x << ") y("sv << plane->y << ") crtc_x("sv << plane->crtc_x << ") crtc_y("sv << plane->crtc_y << ')'; - - BOOST_LOG(info) - << "Resolution: "sv << fb->width << 'x' << fb->height - << ": Pitch: "sv << fb->pitch - << ": bpp: "sv << fb->bpp - << ": depth: "sv << fb->depth; - - std::for_each_n(plane->formats, plane->count_formats, [](auto format) { - BOOST_LOG(info) << "Format "sv << util::view(format); - }); + BOOST_LOG(info) << "Found monitor for DRM screencasting"sv; + kms::print(plane.get(), fb.get()); width = fb->width; height = fb->height; pitch = fb->pitch; env_width = width; env_height = height; + + break; } - gbm.reset(gbm::create_device(fd.el)); + if(monitor != monitor_index) { + BOOST_LOG(error) << "Couldn't find monitor ["sv << monitor_index << ']'; + + return -1; + } + + gbm.reset(gbm::create_device(card.fd.el)); if(!gbm) { BOOST_LOG(error) << "Couldn't create GBM device: ["sv << util::hex(eglGetError()).to_string_view() << ']'; return -1; @@ -218,7 +313,7 @@ public: std::chrono::nanoseconds delay; - egl::file_t fd; + card_t card; egl::file_t fb_fd; gbm::gbm_t gbm; @@ -229,20 +324,52 @@ public: }; } // namespace kms -std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { +std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { auto disp = std::make_shared(); if(disp->init(display_name, framerate)) { return nullptr; } - BOOST_LOG(info) << "Opened DRM Display"sv; return disp; } // A list of names of displays accepted as display_name -std::vector display_names() { - return {}; +std::vector kms_display_names() { + if(!gbm::create_device) { + BOOST_LOG(warning) << "libgbm not initialized"sv; + return {}; + } + + std::vector display_names; + + kms::card_t card; + if(card.init("/dev/dri/card1")) { + return {}; + } + + int count = 0; + + auto end = std::end(card); + for(auto plane = std::begin(card); plane != end; ++plane) { + auto fb = card.fb(plane.get()); + if(!fb) { + BOOST_LOG(error) << "Couldn't get drm fb for plane ["sv << plane->fb_id << "]: "sv << strerror(errno); + continue; + } + + if(!fb->handle) { + BOOST_LOG(error) + << "Couldn't get handle for DRM Framebuffer ["sv << plane->fb_id << "]: Possibly not permitted: do [sudo setcap cap_sys_admin+ep sunshine]"sv; + break; + } + + kms::print(plane.get(), fb.get()); + + display_names.emplace_back(std::to_string(count++)); + } + + return display_names; } } // namespace platf \ No newline at end of file diff --git a/sunshine/platform/linux/misc.cpp b/sunshine/platform/linux/misc.cpp index c4c63e84..0e80c379 100644 --- a/sunshine/platform/linux/misc.cpp +++ b/sunshine/platform/linux/misc.cpp @@ -7,7 +7,10 @@ #include +#include "graphics.h" #include "misc.h" +#include "vaapi.h" + #include "sunshine/main.h" #include "sunshine/platform/common.h" @@ -20,6 +23,47 @@ using namespace std::literals; namespace fs = std::filesystem; +namespace dyn { +void *handle(const std::vector &libs) { + void *handle; + + for(auto lib : libs) { + handle = dlopen(lib, RTLD_LAZY | RTLD_LOCAL); + if(handle) { + return handle; + } + } + + std::stringstream ss; + ss << "Couldn't find any of the following libraries: ["sv << libs.front(); + std::for_each(std::begin(libs) + 1, std::end(libs), [&](auto lib) { + ss << ", "sv << lib; + }); + + ss << ']'; + + BOOST_LOG(error) << ss.str(); + + return nullptr; +} + +int load(void *handle, const std::vector> &funcs, bool strict) { + int err = 0; + for(auto &func : funcs) { + TUPLE_2D_REF(fn, name, func); + + *fn = SUNSHINE_GNUC_EXTENSION(apiproc) dlsym(handle, name); + + if(!*fn && strict) { + BOOST_LOG(error) << "Couldn't find function: "sv << name; + + err = -1; + } + } + + return err; +} +} // namespace dyn namespace platf { using ifaddr_t = util::safe_ptr; @@ -93,46 +137,94 @@ std::string get_mac_address(const std::string_view &address) { BOOST_LOG(warning) << "Unable to find MAC address for "sv << address; return "00:00:00:00:00:00"s; } -} // namespace platf -namespace dyn { -void *handle(const std::vector &libs) { - void *handle; +enum class source_e { +#ifdef SUNSHINE_BUILD_DRM + KMS, +#endif +#ifdef SUNSHINE_BUILD_X11 + X11, +#endif +}; +static source_e source; - for(auto lib : libs) { - handle = dlopen(lib, RTLD_LAZY | RTLD_LOCAL); - if(handle) { - return handle; - } +#ifdef SUNSHINE_BUILD_DRM +std::vector kms_display_names(); +std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate); + +bool verify_kms() { + return !kms_display_names().empty(); +} +#endif + +#ifdef SUNSHINE_BUILD_X11 +std::vector x11_display_names(); +std::shared_ptr x11_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate); + +bool verify_x11() { + return !x11_display_names().empty(); +} +#endif + +std::vector display_names() { + switch(source) { +#ifdef SUNSHINE_BUILD_DRM + case source_e::KMS: + return kms_display_names(); +#endif +#ifdef SUNSHINE_BUILD_X11 + case source_e::X11: + return x11_display_names(); +#endif } - std::stringstream ss; - ss << "Couldn't find any of the following libraries: ["sv << libs.front(); - std::for_each(std::begin(libs) + 1, std::end(libs), [&](auto lib) { - ss << ", "sv << lib; - }); + return {}; +} - ss << ']'; - - BOOST_LOG(error) << ss.str(); +std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { + switch(source) { +#ifdef SUNSHINE_BUILD_DRM + case source_e::KMS: + return kms_display(hwdevice_type, display_name, framerate); +#endif +#ifdef SUNSHINE_BUILD_X11 + case source_e::X11: + return x11_display(hwdevice_type, display_name, framerate); +#endif + } return nullptr; } -int load(void *handle, const std::vector> &funcs, bool strict) { - int err = 0; - for(auto &func : funcs) { - TUPLE_2D_REF(fn, name, func); +std::unique_ptr init() { + // These are allowed to fail. + gbm::init(); + va::init(); - *fn = SUNSHINE_GNUC_EXTENSION(apiproc) dlsym(handle, name); +#ifdef SUNSHINE_BUILD_DRM + if(verify_kms()) { + BOOST_LOG(info) << "Using KMS for screencasting"sv; + source = source_e::KMS; + goto found_source; + } +#endif +#ifdef SUNSHINE_BUILD_X11 + if(verify_x11()) { + BOOST_LOG(info) << "Using X11 for screencasting"sv; + source = source_e::X11; + goto found_source; + } +#endif + // Did not find a source + return nullptr; - if(!*fn && strict) { - BOOST_LOG(error) << "Couldn't find function: "sv << name; - - err = -1; - } +// Normally, I would simply use if-else statements to achieve this result, +// but due to the macro's, (*spits on ground*), it would be too messy +found_source: + if(!gladLoaderLoadEGL(EGL_NO_DISPLAY) || !eglGetPlatformDisplay) { + BOOST_LOG(warning) << "Couldn't load EGL library"sv; } - return err; + return std::make_unique(); } -} // namespace dyn \ No newline at end of file +} // namespace platf \ No newline at end of file diff --git a/sunshine/platform/linux/misc.h b/sunshine/platform/linux/misc.h index 70c9d880..d25fc031 100644 --- a/sunshine/platform/linux/misc.h +++ b/sunshine/platform/linux/misc.h @@ -2,7 +2,6 @@ #define SUNSHINE_PLATFORM_MISC_H #include - namespace dyn { typedef void (*apiproc)(void); diff --git a/sunshine/platform/linux/vaapi.cpp b/sunshine/platform/linux/vaapi.cpp index 905c0bbe..6b1a123a 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/sunshine/platform/linux/vaapi.cpp @@ -98,7 +98,7 @@ exportSurfaceHandle_fn exportSurfaceHandle; using display_t = util::dyn_safe_ptr_v2; -int init() { +int init_main_va() { static void *handle { nullptr }; static bool funcs_loaded = false; @@ -129,8 +129,8 @@ int init() { return 0; } -int init_drm() { - if(init()) { +int init() { + if(init_main_va()) { return -1; } @@ -346,17 +346,4 @@ std::shared_ptr make_hwdevice(int width, int height) { return egl; } -} // namespace va - -namespace platf { -std::unique_ptr init() { - gbm::init(); - va::init_drm(); - - if(!gladLoaderLoadEGL(EGL_NO_DISPLAY) || !eglGetPlatformDisplay) { - BOOST_LOG(warning) << "Couldn't load EGL library"sv; - } - - return std::make_unique(); -} -} // namespace platf \ No newline at end of file +} // namespace va \ No newline at end of file diff --git a/sunshine/platform/linux/vaapi.h b/sunshine/platform/linux/vaapi.h index f6f18389..1299fdc9 100644 --- a/sunshine/platform/linux/vaapi.h +++ b/sunshine/platform/linux/vaapi.h @@ -4,5 +4,7 @@ #include "sunshine/platform/common.h" namespace va { std::shared_ptr make_hwdevice(int width, int height); + +int init(); } // namespace va #endif \ No newline at end of file diff --git a/sunshine/platform/linux/x11grab.cpp b/sunshine/platform/linux/x11grab.cpp index a1b1a7ca..a8f9eb34 100644 --- a/sunshine/platform/linux/x11grab.cpp +++ b/sunshine/platform/linux/x11grab.cpp @@ -398,7 +398,7 @@ struct x11_attr_t : public display_t { x11::InitThreads(); } - int init(int framerate) { + int init(const std::string &display_name, int framerate) { if(!xdisplay) { BOOST_LOG(error) << "Could not open X11 display"sv; return -1; @@ -411,8 +411,8 @@ struct x11_attr_t : public display_t { refresh(); int streamedMonitor = -1; - if(!config::video.output_name.empty()) { - streamedMonitor = (int)util::from_view(config::video.output_name); + if(!display_name.empty()) { + streamedMonitor = (int)util::from_view(display_name); } if(streamedMonitor != -1) { @@ -529,7 +529,7 @@ struct x11_attr_t : public display_t { std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override { if(mem_type == mem_type_e::vaapi) { - return egl::make_hwdevice(width, height); + return va::make_hwdevice(width, height); } return std::make_shared(); @@ -642,8 +642,8 @@ struct shm_attr_t : public x11_attr_t { return 0; } - int init(int framerate) { - if(x11_attr_t::init(framerate)) { + int init(const std::string &display_name, int framerate) { + if(x11_attr_t::init(display_name, framerate)) { return 1; } @@ -686,7 +686,7 @@ struct shm_attr_t : public x11_attr_t { } }; -std::shared_ptr display(platf::mem_type_e hwdevice_type, int framerate) { +std::shared_ptr x11_display(platf::mem_type_e hwdevice_type, const std::string &display_name, int framerate) { if(hwdevice_type != platf::mem_type_e::system && hwdevice_type != platf::mem_type_e::vaapi && hwdevice_type != platf::mem_type_e::cuda) { BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv; return nullptr; @@ -701,7 +701,7 @@ std::shared_ptr display(platf::mem_type_e hwdevice_type, int framerat // Attempt to use shared memory X11 to avoid copying the frame auto shm_disp = std::make_shared(hwdevice_type); - auto status = shm_disp->init(framerate); + auto status = shm_disp->init(display_name, framerate); if(status > 0) { // x11_attr_t::init() failed, don't bother trying again. return nullptr; @@ -713,18 +713,18 @@ std::shared_ptr display(platf::mem_type_e hwdevice_type, int framerat // Fallback auto x11_disp = std::make_shared(hwdevice_type); - if(x11_disp->init(framerate)) { + if(x11_disp->init(display_name, framerate)) { return nullptr; } return x11_disp; } -std::vector display_names() { +std::vector x11_display_names() { if(xcb::init_shm() || xcb::init() || x11::init() || x11::rr::init() || x11::fix::init()) { BOOST_LOG(error) << "Couldn't init x11 libraries"sv; - return nullptr; + return {}; } BOOST_LOG(info) << "Detecting connected monitors"sv; diff --git a/sunshine/utility.h b/sunshine/utility.h index 4740ae6a..be63f475 100644 --- a/sunshine/utility.h +++ b/sunshine/utility.h @@ -592,6 +592,14 @@ bool operator!=(std::nullptr_t, const uniq_ptr &y) { return (bool)y; } +template +using shared_t = std::shared_ptr; + +template +shared_t

make_shared(T *pointer) { + return shared_t

(reinterpret_cast(pointer), typename P::deleter_type()); +} + template class wrap_ptr { public: