From 6702802829869547708dfec98db5b8cbef39be89 Mon Sep 17 00:00:00 2001 From: Loki Date: Tue, 3 Aug 2021 20:31:27 +0200 Subject: [PATCH 01/21] Load X11 libraries at runtime --- CMakeLists.txt | 27 +- FindFFmpeg.cmake => cmake/FindFFMPEG.cmake | 0 sunshine/platform/linux/display.cpp | 344 +++++++++++++++++---- sunshine/platform/linux/publish.cpp | 2 +- 4 files changed, 295 insertions(+), 78 deletions(-) rename FindFFmpeg.cmake => cmake/FindFFMPEG.cmake (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 704334bf..5570d278 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0) project(Sunshine) -set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) add_subdirectory(third-party/Simple-Web-Server) @@ -106,16 +106,23 @@ else() add_compile_definitions(SUNSHINE_PLATFORM="linux") list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") - find_package(X11 REQUIRED) - find_package(FFmpeg REQUIRED) + find_package(X11) - set(PLATFORM_TARGET_FILES + if(X11_FOUND) + include_directories(${X11_INCLUDE_DIR}) + list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/display.cpp) + else() + list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1) + endif() + + find_package(FFMPEG REQUIRED) + + list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/publish.cpp sunshine/platform/linux/vaapi.h sunshine/platform/linux/vaapi.cpp sunshine/platform/linux/misc.h sunshine/platform/linux/misc.cpp - sunshine/platform/linux/display.cpp sunshine/platform/linux/audio.cpp sunshine/platform/linux/input.cpp third-party/glad/src/egl.c @@ -126,21 +133,13 @@ else() third-party/glad/include/glad/egl.h) set(PLATFORM_LIBRARIES - Xfixes - Xtst - xcb - xcb-shm - xcb-xfixes - Xrandr - ${X11_LIBRARIES} dl evdev pulse pulse-simple ) - set(PLATFORM_INCLUDE_DIRS - ${X11_INCLUDE_DIR} + include_directories( /usr/include/libevdev-1.0 third-party/glad/include) diff --git a/FindFFmpeg.cmake b/cmake/FindFFMPEG.cmake similarity index 100% rename from FindFFmpeg.cmake rename to cmake/FindFFMPEG.cmake diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/display.cpp index f6a89511..55dd619b 100644 --- a/sunshine/platform/linux/display.cpp +++ b/sunshine/platform/linux/display.cpp @@ -20,25 +20,268 @@ #include "sunshine/main.h" #include "sunshine/task_pool.h" +#include "misc.h" #include "vaapi.h" using namespace std::literals; namespace platf { +namespace x11 { +#define _FN(x, ret, args) \ + typedef ret(*x##_fn) args; \ + static x##_fn x + +using XID = unsigned long; +using Time = unsigned long; +using Rotation = unsigned short; + +_FN(GetImage, XImage *, + ( + Display * display, + Drawable d, + int x, int y, + unsigned int width, unsigned int height, + unsigned long plane_mask, + int format)); + +_FN(OpenDisplay, Display *, (_Xconst char *display_name)); +_FN(GetWindowAttributes, Status, + ( + Display * display, + Window w, + XWindowAttributes *window_attributes_return)); + +_FN(CloseDisplay, int, (Display * display)); +_FN(Free, int, (void *data)); +_FN(InitThreads, Status, (void)); + +namespace rr { +using Mode = XID; +using Output = XID; + +struct CrtcInfo { + Time timestamp; + int x, y; + unsigned int width, height; + Mode mode; + Rotation rotation; + int noutput; + Output *outputs; + Rotation rotations; + int npossible; + Output *possible; +}; + +_FN(GetScreenResources, XRRScreenResources *, (Display * dpy, Window window)); +_FN(GetOutputInfo, XRROutputInfo *, (Display * dpy, XRRScreenResources *resources, RROutput output)); +_FN(GetCrtcInfo, XRRCrtcInfo *, (Display * dpy, XRRScreenResources *resources, RRCrtc crtc)); +_FN(FreeScreenResources, void, (XRRScreenResources * resources)); +_FN(FreeOutputInfo, void, (XRROutputInfo * outputInfo)); +_FN(FreeCrtcInfo, void, (XRRCrtcInfo * crtcInfo)); + +int init() { + static void *handle { nullptr }; + static bool funcs_loaded = false; + + if(funcs_loaded) return 0; + + if(!handle) { + handle = dyn::handle({ "libXrandr.so.2", "libXrandr.so" }); + if(!handle) { + return -1; + } + } + + std::vector> funcs { + { (dyn::apiproc *)&GetScreenResources, "XRRGetScreenResources" }, + { (dyn::apiproc *)&GetOutputInfo, "XRRGetOutputInfo" }, + { (dyn::apiproc *)&GetCrtcInfo, "XRRGetCrtcInfo" }, + { (dyn::apiproc *)&FreeScreenResources, "XRRFreeScreenResources" }, + { (dyn::apiproc *)&FreeOutputInfo, "XRRFreeOutputInfo" }, + { (dyn::apiproc *)&FreeCrtcInfo, "XRRFreeCrtcInfo" }, + }; + + if(dyn::load(handle, funcs)) { + return -1; + } + + funcs_loaded = true; + return 0; +} + +} // namespace rr +namespace fix { +_FN(GetCursorImage, XFixesCursorImage *, (Display * dpy)); + +int init() { + static void *handle { nullptr }; + static bool funcs_loaded = false; + + if(funcs_loaded) return 0; + + if(!handle) { + handle = dyn::handle({ "libXfixes.so.3", "libXfixes.so" }); + if(!handle) { + return -1; + } + } + + std::vector> funcs { + { (dyn::apiproc *)&GetCursorImage, "XFixesGetCursorImage" }, + }; + + if(dyn::load(handle, funcs)) { + return -1; + } + + funcs_loaded = true; + return 0; +} +} // namespace fix + +int init() { + static void *handle { nullptr }; + static bool funcs_loaded = false; + + if(funcs_loaded) return 0; + + if(!handle) { + handle = dyn::handle({ "libX11.so.6", "libX11.so" }); + if(!handle) { + return -1; + } + } + + std::vector> funcs { + { (dyn::apiproc *)&GetImage, "XGetImage" }, + { (dyn::apiproc *)&OpenDisplay, "XOpenDisplay" }, + { (dyn::apiproc *)&GetWindowAttributes, "XGetWindowAttributes" }, + { (dyn::apiproc *)&Free, "XFree" }, + { (dyn::apiproc *)&CloseDisplay, "XCloseDisplay" }, + { (dyn::apiproc *)&InitThreads, "XInitThreads" }, + }; + + if(dyn::load(handle, funcs)) { + return -1; + } + + funcs_loaded = true; + return 0; +} +} // namespace x11 + +namespace xcb { +static xcb_extension_t *shm_id; + +_FN(shm_get_image_reply, xcb_shm_get_image_reply_t *, + ( + xcb_connection_t * c, + xcb_shm_get_image_cookie_t cookie, + xcb_generic_error_t **e)); + +_FN(shm_get_image_unchecked, xcb_shm_get_image_cookie_t, + ( + xcb_connection_t * c, + xcb_drawable_t drawable, + int16_t x, int16_t y, + uint16_t width, uint16_t height, + uint32_t plane_mask, + uint8_t format, + xcb_shm_seg_t shmseg, + uint32_t offset)); + +_FN(shm_attach, xcb_void_cookie_t, + (xcb_connection_t * c, + xcb_shm_seg_t shmseg, + uint32_t shmid, + uint8_t read_only)); + +_FN(get_extension_data, xcb_query_extension_reply_t *, + (xcb_connection_t * c, xcb_extension_t *ext)); + +_FN(get_setup, xcb_setup_t *, (xcb_connection_t * c)); +_FN(disconnect, void, (xcb_connection_t * c)); +_FN(connection_has_error, int, (xcb_connection_t * c)); +_FN(connect, xcb_connection_t *, (const char *displayname, int *screenp)); +_FN(setup_roots_iterator, xcb_screen_iterator_t, (const xcb_setup_t *R)); +_FN(generate_id, std::uint32_t, (xcb_connection_t * c)); + +int init_shm() { + static void *handle { nullptr }; + static bool funcs_loaded = false; + + if(funcs_loaded) return 0; + + if(!handle) { + handle = dyn::handle({ "libxcb-shm.so.0", "libxcb-shm.so" }); + if(!handle) { + return -1; + } + } + + std::vector> funcs { + { (dyn::apiproc *)&shm_id, "xcb_shm_id" }, + { (dyn::apiproc *)&shm_get_image_reply, "xcb_shm_get_image_reply" }, + { (dyn::apiproc *)&shm_get_image_unchecked, "xcb_shm_get_image_unchecked" }, + { (dyn::apiproc *)&shm_attach, "xcb_shm_attach" }, + }; + + if(dyn::load(handle, funcs)) { + return -1; + } + + funcs_loaded = true; + return 0; +} + +int init() { + static void *handle { nullptr }; + static bool funcs_loaded = false; + + if(funcs_loaded) return 0; + + if(!handle) { + handle = dyn::handle({ "libxcb.so.1", "libxcb.so" }); + if(!handle) { + return -1; + } + } + + std::vector> funcs { + { (dyn::apiproc *)&get_extension_data, "xcb_get_extension_data" }, + { (dyn::apiproc *)&get_setup, "xcb_get_setup" }, + { (dyn::apiproc *)&disconnect, "xcb_disconnect" }, + { (dyn::apiproc *)&connection_has_error, "xcb_connection_has_error" }, + { (dyn::apiproc *)&connect, "xcb_connect" }, + { (dyn::apiproc *)&setup_roots_iterator, "xcb_setup_roots_iterator" }, + { (dyn::apiproc *)&generate_id, "xcb_generate_id" }, + }; + + if(dyn::load(handle, funcs)) { + return -1; + } + + funcs_loaded = true; + return 0; +} + +#undef _FN +} // namespace xcb + void freeImage(XImage *); void freeX(XFixesCursorImage *); -using xcb_connect_t = util::safe_ptr; +using xcb_connect_t = util::dyn_safe_ptr; using xcb_img_t = util::c_ptr; -using xdisplay_t = util::safe_ptr_v2; +using xdisplay_t = util::dyn_safe_ptr_v2; using ximg_t = util::safe_ptr; using xcursor_t = util::safe_ptr; -using crtc_info_t = util::safe_ptr<_XRRCrtcInfo, XRRFreeCrtcInfo>; -using output_info_t = util::safe_ptr<_XRROutputInfo, XRRFreeOutputInfo>; -using screen_res_t = util::safe_ptr<_XRRScreenResources, XRRFreeScreenResources>; +using crtc_info_t = util::dyn_safe_ptr<_XRRCrtcInfo, &x11::rr::FreeCrtcInfo>; +using output_info_t = util::dyn_safe_ptr<_XRROutputInfo, &x11::rr::FreeOutputInfo>; +using screen_res_t = util::dyn_safe_ptr<_XRRScreenResources, &x11::rr::FreeScreenResources>; class shm_id_t { public: @@ -87,7 +330,7 @@ struct shm_img_t : public img_t { }; void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) { - xcursor_t overlay { XFixesGetCursorImage(display) }; + xcursor_t overlay { x11::fix::GetCursorImage(display) }; if(!overlay) { BOOST_LOG(error) << "Couldn't get cursor from XFixesGetCursorImage"sv; @@ -151,11 +394,11 @@ struct x11_attr_t : public display_t { */ // int env_width, env_height; - x11_attr_t(mem_type_e mem_type) : xdisplay { XOpenDisplay(nullptr) }, xwindow {}, xattr {}, mem_type { mem_type } { - XInitThreads(); + x11_attr_t(mem_type_e mem_type) : xdisplay { x11::OpenDisplay(nullptr) }, xwindow {}, xattr {}, mem_type { mem_type } { + x11::InitThreads(); } - int init(int framerate, const std::string &output_name) { + int init(int framerate) { if(!xdisplay) { BOOST_LOG(error) << "Could not open X11 display"sv; return -1; @@ -168,19 +411,19 @@ struct x11_attr_t : public display_t { refresh(); int streamedMonitor = -1; - if(!output_name.empty()) { - streamedMonitor = (int)util::from_view(output_name); + if(!config::video.output_name.empty()) { + streamedMonitor = (int)util::from_view(config::video.output_name); } if(streamedMonitor != -1) { BOOST_LOG(info) << "Configuring selected monitor ("sv << streamedMonitor << ") to stream"sv; - screen_res_t screenr { XRRGetScreenResources(xdisplay.get(), xwindow) }; + screen_res_t screenr { x11::rr::GetScreenResources(xdisplay.get(), xwindow) }; int output = screenr->noutput; output_info_t result; int monitor = 0; for(int x = 0; x < output; ++x) { - output_info_t out_info { XRRGetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) }; + output_info_t out_info { x11::rr::GetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) }; if(out_info && out_info->connection == RR_Connected) { if(monitor++ == streamedMonitor) { result = std::move(out_info); @@ -194,7 +437,7 @@ struct x11_attr_t : public display_t { return -1; } - crtc_info_t crt_info { XRRGetCrtcInfo(xdisplay.get(), screenr.get(), result->crtc) }; + crtc_info_t crt_info { x11::rr::GetCrtcInfo(xdisplay.get(), screenr.get(), result->crtc) }; BOOST_LOG(info) << "Streaming display: "sv << result->name << " with res "sv << crt_info->width << 'x' << crt_info->height << " offset by "sv << crt_info->x << 'x' << crt_info->y; @@ -218,7 +461,7 @@ struct x11_attr_t : public display_t { * Called when the display attributes should change. */ void refresh() { - XGetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's + x11::GetWindowAttributes(xdisplay.get(), xwindow, &xattr); //Update xattr's } capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override { @@ -263,7 +506,7 @@ struct x11_attr_t : public display_t { BOOST_LOG(warning) << "X dimensions changed in non-SHM mode, request reinit"sv; return capture_e::reinit; } - XImage *img { XGetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) }; + XImage *img { x11::GetImage(xdisplay.get(), xwindow, offset_x, offset_y, width, height, AllPlanes, ZPixmap) }; auto img_out = (x11_img_t *)img_out_base; img_out->width = img->width; @@ -316,7 +559,7 @@ struct shm_attr_t : public x11_attr_t { refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; } - shm_attr_t(mem_type_e mem_type) : x11_attr_t(mem_type), shm_xdisplay { XOpenDisplay(nullptr) } { + shm_attr_t(mem_type_e mem_type) : x11_attr_t(mem_type), shm_xdisplay { x11::OpenDisplay(nullptr) } { refresh_task_id = task_pool.pushDelayed(&shm_attr_t::delayed_refresh, 2s, this).task_id; } @@ -366,9 +609,9 @@ struct shm_attr_t : public x11_attr_t { return capture_e::reinit; } else { - auto img_cookie = xcb_shm_get_image_unchecked(xcb.get(), display->root, offset_x, offset_y, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0); + auto img_cookie = xcb::shm_get_image_unchecked(xcb.get(), display->root, offset_x, offset_y, width, height, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, seg, 0); - xcb_img_t img_reply { xcb_shm_get_image_reply(xcb.get(), img_cookie, nullptr) }; + xcb_img_t img_reply { xcb::shm_get_image_reply(xcb.get(), img_cookie, nullptr) }; if(!img_reply) { BOOST_LOG(error) << "Could not get image reply"sv; return capture_e::reinit; @@ -399,26 +642,26 @@ struct shm_attr_t : public x11_attr_t { return 0; } - int init(int framerate, const std::string &output_name) { - if(x11_attr_t::init(framerate, output_name)) { + int init(int framerate) { + if(x11_attr_t::init(framerate)) { return 1; } - shm_xdisplay.reset(XOpenDisplay(nullptr)); - xcb.reset(xcb_connect(nullptr, nullptr)); - if(xcb_connection_has_error(xcb.get())) { + shm_xdisplay.reset(x11::OpenDisplay(nullptr)); + xcb.reset(xcb::connect(nullptr, nullptr)); + if(xcb::connection_has_error(xcb.get())) { return -1; } - if(!xcb_get_extension_data(xcb.get(), &xcb_shm_id)->present) { + if(!xcb::get_extension_data(xcb.get(), xcb::shm_id)->present) { BOOST_LOG(error) << "Missing SHM extension"sv; return -1; } - auto iter = xcb_setup_roots_iterator(xcb_get_setup(xcb.get())); + auto iter = xcb::setup_roots_iterator(xcb::get_setup(xcb.get())); display = iter.data; - seg = xcb_generate_id(xcb.get()); + seg = xcb::generate_id(xcb.get()); shm_id.id = shmget(IPC_PRIVATE, frame_size(), IPC_CREAT | 0777); if(shm_id.id == -1) { @@ -426,7 +669,7 @@ struct shm_attr_t : public x11_attr_t { return -1; } - xcb_shm_attach(xcb.get(), seg, shm_id.id, false); + xcb::shm_attach(xcb.get(), seg, shm_id.id, false); data.data = shmat(shm_id.id, nullptr, 0); if((uintptr_t)data.data == -1) { @@ -443,16 +686,22 @@ struct shm_attr_t : public x11_attr_t { } }; -std::shared_ptr display(platf::mem_type_e hwdevice_type, const std::string &output_name, int framerate) { +std::shared_ptr display(platf::mem_type_e hwdevice_type, 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; } + 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; + } + // 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, output_name); + auto status = shm_disp->init(framerate); if(status > 0) { // x11_attr_t::init() failed, don't bother trying again. return nullptr; @@ -464,48 +713,17 @@ std::shared_ptr display(platf::mem_type_e hwdevice_type, const std::s // Fallback auto x11_disp = std::make_shared(hwdevice_type); - if(x11_disp->init(framerate, output_name)) { + if(x11_disp->init(framerate)) { return nullptr; } return x11_disp; } -std::vector display_names() { - BOOST_LOG(info) << "Detecting connected monitors"sv; - - xdisplay_t xdisplay { XOpenDisplay(nullptr) }; - if(!xdisplay) { - return {}; - } - - auto xwindow = DefaultRootWindow(xdisplay.get()); - screen_res_t screenr { XRRGetScreenResources(xdisplay.get(), xwindow) }; - int output = screenr->noutput; - - int monitor = 0; - for(int x = 0; x < output; ++x) { - output_info_t out_info { XRRGetOutputInfo(xdisplay.get(), screenr.get(), screenr->outputs[x]) }; - if(out_info && out_info->connection == RR_Connected) { - ++monitor; - } - } - - std::vector names; - names.reserve(monitor); - - for(auto x = 0; x < monitor; ++x) { - BOOST_LOG(fatal) << x; - names.emplace_back(std::to_string(x)); - } - - return names; -} - void freeImage(XImage *p) { XDestroyImage(p); } void freeX(XFixesCursorImage *p) { - XFree(p); + x11::Free(p); } } // namespace platf diff --git a/sunshine/platform/linux/publish.cpp b/sunshine/platform/linux/publish.cpp index 05208cbb..825f3740 100644 --- a/sunshine/platform/linux/publish.cpp +++ b/sunshine/platform/linux/publish.cpp @@ -426,4 +426,4 @@ public: return std::make_unique(std::thread { avahi::simple_poll_loop, poll.get() }); } -}; // namespace platf::publish \ No newline at end of file +} // namespace platf::publish \ No newline at end of file From ac5f43983900ca961fb54307dc22cbb6889d1ebc Mon Sep 17 00:00:00 2001 From: Loki Date: Thu, 5 Aug 2021 21:24:52 +0200 Subject: [PATCH 02/21] Skeleton of grabbing image with kms --- CMakeLists.txt | 18 +- cmake/FindLIBDRM.cmake | 21 + sunshine/platform/linux/graphics.cpp | 687 +++++++++++++++ sunshine/platform/linux/graphics.h | 264 ++++++ sunshine/platform/linux/kmsgrab.cpp | 248 ++++++ sunshine/platform/linux/vaapi.cpp | 833 ++---------------- sunshine/platform/linux/vaapi.h | 6 +- .../linux/{display.cpp => x11grab.cpp} | 0 8 files changed, 1286 insertions(+), 791 deletions(-) create mode 100644 cmake/FindLIBDRM.cmake create mode 100644 sunshine/platform/linux/graphics.cpp create mode 100644 sunshine/platform/linux/graphics.h create mode 100644 sunshine/platform/linux/kmsgrab.cpp rename sunshine/platform/linux/{display.cpp => x11grab.cpp} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5570d278..fc28a460 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,20 +107,28 @@ else() list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") find_package(X11) + find_package(LIBDRM) + + find_package(FFMPEG REQUIRED) if(X11_FOUND) include_directories(${X11_INCLUDE_DIR}) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/display.cpp) - else() + list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/x11grab.cpp) + elseif(LIBDRM_FOUND) + 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() + message(FATAL "Couldn't find either x11 or libdrm") endif() - find_package(FFMPEG REQUIRED) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/publish.cpp sunshine/platform/linux/vaapi.h sunshine/platform/linux/vaapi.cpp + sunshine/platform/linux/graphics.h + sunshine/platform/linux/graphics.cpp sunshine/platform/linux/misc.h sunshine/platform/linux/misc.cpp sunshine/platform/linux/audio.cpp @@ -132,7 +140,7 @@ else() third-party/glad/include/glad/gl.h third-party/glad/include/glad/egl.h) - set(PLATFORM_LIBRARIES + list(APPEND PLATFORM_LIBRARIES dl evdev pulse diff --git a/cmake/FindLIBDRM.cmake b/cmake/FindLIBDRM.cmake new file mode 100644 index 00000000..fd0548c1 --- /dev/null +++ b/cmake/FindLIBDRM.cmake @@ -0,0 +1,21 @@ +# - Try to find Libdrm +# Once done this will define +# +# LIBDRM_FOUND - system has Libdrm +# LIBDRM_INCLUDE_DIRS - the Libdrm include directory +# LIBDRM_LIBRARIES - the libraries needed to use Libdrm +# LIBDRM_DEFINITIONS - Compiler switches required for using Libdrm + +# Use pkg-config to get the directories and then use these values +# in the find_path() and find_library() calls +find_package(PkgConfig) +pkg_check_modules(PC_LIBDRM libdrm) + +set(LIBDRM_DEFINITIONS ${PC_LIBDRM_CFLAGS}) + +find_path(LIBDRM_INCLUDE_DIRS drm.h PATHS ${PC_LIBDRM_INCLUDEDIR} ${PC_LIBDRM_INCLUDE_DIRS} PATH_SUFFIXES libdrm) +find_library(LIBDRM_LIBRARIES NAMES libdrm.so PATHS ${PC_LIBDRM_LIBDIR} ${PC_LIBDRM_LIBRARY_DIRS}) +mark_as_advanced(LIBDRM_INCLUDE_DIRS LIBDRM_LIBRARIES) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LIBDRM REQUIRED_VARS LIBDRM_LIBRARIES LIBDRM_INCLUDE_DIRS) \ No newline at end of file diff --git a/sunshine/platform/linux/graphics.cpp b/sunshine/platform/linux/graphics.cpp new file mode 100644 index 00000000..5093255c --- /dev/null +++ b/sunshine/platform/linux/graphics.cpp @@ -0,0 +1,687 @@ +#include "graphics.h" +#include "sunshine/video.h" + +extern "C" { +#include +} + +#include + +// I want to have as little build dependencies as possible +// There aren't that many DRM_FORMAT I need to use, so define them here +// +// They aren't likely to change any time soon. +#define fourcc_code(a, b, c, d) ((std::uint32_t)(a) | ((std::uint32_t)(b) << 8) | \ + ((std::uint32_t)(c) << 16) | ((std::uint32_t)(d) << 24)) +#define DRM_FORMAT_R8 fourcc_code('R', '8', ' ', ' ') /* [7:0] R */ +#define DRM_FORMAT_GR88 fourcc_code('G', 'R', '8', '8') /* [15:0] G:R 8:8 little endian */ +#define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4') /* [31:0] A:R:G:B 8:8:8:8 little endian */ +#define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */ +#define DRM_FORMAT_XBGR8888 fourcc_code('X', 'B', '2', '4') /* [31:0] x:B:G:R 8:8:8:8 little endian */ + + +#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/opengl" + +#define STRINGIFY(x) #x +#define gl_drain_errors_helper(x) gl::drain_errors("line " STRINGIFY(x)) +#define gl_drain_errors gl_drain_errors_helper(__LINE__) + +using namespace std::literals; +namespace gl { +GladGLContext ctx; + +void drain_errors(const std::string_view &prefix) { + GLenum err; + while((err = ctx.GetError()) != GL_NO_ERROR) { + BOOST_LOG(error) << "GL: "sv << prefix << ": ["sv << util::hex(err).to_string_view() << ']'; + } +} + +tex_t::~tex_t() { + if(!size() == 0) { + ctx.DeleteTextures(size(), begin()); + } +} + +tex_t tex_t::make(std::size_t count) { + tex_t textures { count }; + + ctx.GenTextures(textures.size(), textures.begin()); + + float color[] = { 0.0f, 0.0f, 0.0f, 1.0f }; + + for(auto tex : textures) { + gl::ctx.BindTexture(GL_TEXTURE_2D, tex); + gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // x + gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // y + gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl::ctx.TexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color); + } + + return textures; +} + +frame_buf_t::~frame_buf_t() { + if(begin()) { + ctx.DeleteFramebuffers(size(), begin()); + } +} + +frame_buf_t frame_buf_t::make(std::size_t count) { + frame_buf_t frame_buf { count }; + + ctx.GenFramebuffers(frame_buf.size(), frame_buf.begin()); + + return frame_buf; +} + +std::string shader_t::err_str() { + int length; + ctx.GetShaderiv(handle(), GL_INFO_LOG_LENGTH, &length); + + std::string string; + string.resize(length); + + ctx.GetShaderInfoLog(handle(), length, &length, string.data()); + + string.resize(length - 1); + + return string; +} + +util::Either shader_t::compile(const std::string_view &source, GLenum type) { + shader_t shader; + + auto data = source.data(); + GLint length = source.length(); + + shader._shader.el = ctx.CreateShader(type); + ctx.ShaderSource(shader.handle(), 1, &data, &length); + ctx.CompileShader(shader.handle()); + + int status = 0; + ctx.GetShaderiv(shader.handle(), GL_COMPILE_STATUS, &status); + + if(!status) { + return shader.err_str(); + } + + return shader; +} + +GLuint shader_t::handle() const { + return _shader.el; +} + +buffer_t buffer_t::make(util::buffer_t &&offsets, const char *block, const std::string_view &data) { + buffer_t buffer; + buffer._block = block; + buffer._size = data.size(); + buffer._offsets = std::move(offsets); + + ctx.GenBuffers(1, &buffer._buffer.el); + ctx.BindBuffer(GL_UNIFORM_BUFFER, buffer.handle()); + ctx.BufferData(GL_UNIFORM_BUFFER, data.size(), (const std::uint8_t *)data.data(), GL_DYNAMIC_DRAW); + + return buffer; +} + +GLuint buffer_t::handle() const { + return _buffer.el; +} + +const char *buffer_t::block() const { + return _block; +} + +void buffer_t::update(const std::string_view &view, std::size_t offset) { + ctx.BindBuffer(GL_UNIFORM_BUFFER, handle()); + ctx.BufferSubData(GL_UNIFORM_BUFFER, offset, view.size(), (const void *)view.data()); +} + +void buffer_t::update(std::string_view *members, std::size_t count, std::size_t offset) { + util::buffer_t buffer { _size }; + + for(int x = 0; x < count; ++x) { + auto val = members[x]; + + std::copy_n((const std::uint8_t *)val.data(), val.size(), &buffer[_offsets[x]]); + } + + update(util::view(buffer.begin(), buffer.end()), offset); +} + +std::string program_t::err_str() { + int length; + ctx.GetProgramiv(handle(), GL_INFO_LOG_LENGTH, &length); + + std::string string; + string.resize(length); + + ctx.GetShaderInfoLog(handle(), length, &length, string.data()); + + string.resize(length - 1); + + return string; +} + +util::Either program_t::link(const shader_t &vert, const shader_t &frag) { + program_t program; + + program._program.el = ctx.CreateProgram(); + + ctx.AttachShader(program.handle(), vert.handle()); + ctx.AttachShader(program.handle(), frag.handle()); + + // p_handle stores a copy of the program handle, since program will be moved before + // the fail guard funcion is called. + auto fg = util::fail_guard([p_handle = program.handle(), &vert, &frag]() { + ctx.DetachShader(p_handle, vert.handle()); + ctx.DetachShader(p_handle, frag.handle()); + }); + + ctx.LinkProgram(program.handle()); + + int status = 0; + ctx.GetProgramiv(program.handle(), GL_LINK_STATUS, &status); + + if(!status) { + return program.err_str(); + } + + return program; +} + +void program_t::bind(const buffer_t &buffer) { + ctx.UseProgram(handle()); + auto i = ctx.GetUniformBlockIndex(handle(), buffer.block()); + + ctx.BindBufferBase(GL_UNIFORM_BUFFER, i, buffer.handle()); +} + +std::optional program_t::uniform(const char *block, std::pair *members, std::size_t count) { + auto i = ctx.GetUniformBlockIndex(handle(), block); + if(i == GL_INVALID_INDEX) { + BOOST_LOG(error) << "Couldn't find index of ["sv << block << ']'; + return std::nullopt; + } + + int size; + ctx.GetActiveUniformBlockiv(handle(), i, GL_UNIFORM_BLOCK_DATA_SIZE, &size); + + bool error_flag = false; + + util::buffer_t offsets { count }; + auto indices = (std::uint32_t *)alloca(count * sizeof(std::uint32_t)); + auto names = (const char **)alloca(count * sizeof(const char *)); + auto names_p = names; + + std::for_each_n(members, count, [names_p](auto &member) mutable { + *names_p++ = std::get<0>(member); + }); + + std::fill_n(indices, count, GL_INVALID_INDEX); + ctx.GetUniformIndices(handle(), count, names, indices); + + for(int x = 0; x < count; ++x) { + if(indices[x] == GL_INVALID_INDEX) { + error_flag = true; + + BOOST_LOG(error) << "Couldn't find ["sv << block << '.' << members[x].first << ']'; + } + } + + if(error_flag) { + return std::nullopt; + } + + ctx.GetActiveUniformsiv(handle(), count, indices, GL_UNIFORM_OFFSET, offsets.begin()); + util::buffer_t buffer { (std::size_t)size }; + + for(int x = 0; x < count; ++x) { + auto val = std::get<1>(members[x]); + + std::copy_n((const std::uint8_t *)val.data(), val.size(), &buffer[offsets[x]]); + } + + return buffer_t::make(std::move(offsets), block, std::string_view { (char *)buffer.begin(), buffer.size() }); +} + +GLuint program_t::handle() const { + return _program.el; +} + +} // namespace gl + +namespace gbm { +device_destroy_fn device_destroy; +create_device_fn create_device; + +int init() { + static void *handle { nullptr }; + static bool funcs_loaded = false; + + if(funcs_loaded) return 0; + + if(!handle) { + handle = dyn::handle({ "libgbm.so.1", "libgbm.so" }); + if(!handle) { + return -1; + } + } + + std::vector> funcs { + { (GLADapiproc *)&device_destroy, "gbm_device_destroy" }, + { (GLADapiproc *)&create_device, "gbm_create_device" }, + }; + + if(dyn::load(handle, funcs)) { + return -1; + } + + funcs_loaded = true; + return 0; +} +} // namespace gbm + +namespace egl { +constexpr auto EGL_LINUX_DMA_BUF_EXT = 0x3270; +constexpr auto EGL_LINUX_DRM_FOURCC_EXT = 0x3271; +constexpr auto EGL_DMA_BUF_PLANE0_FD_EXT = 0x3272; +constexpr auto EGL_DMA_BUF_PLANE0_OFFSET_EXT = 0x3273; +constexpr auto EGL_DMA_BUF_PLANE0_PITCH_EXT = 0x3274; + +bool fail() { + return eglGetError() != EGL_SUCCESS; +} + +display_t make_display(gbm::gbm_t::pointer gbm) { + constexpr auto EGL_PLATFORM_GBM_MESA = 0x31D7; + + display_t display = eglGetPlatformDisplay(EGL_PLATFORM_GBM_MESA, gbm, nullptr); + + if(fail()) { + BOOST_LOG(error) << "Couldn't open EGL display: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return nullptr; + } + + int major, minor; + if(!eglInitialize(display.get(), &major, &minor)) { + BOOST_LOG(error) << "Couldn't initialize EGL display: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return nullptr; + } + + const char *extension_st = eglQueryString(display.get(), EGL_EXTENSIONS); + const char *version = eglQueryString(display.get(), EGL_VERSION); + const char *vendor = eglQueryString(display.get(), EGL_VENDOR); + const char *apis = eglQueryString(display.get(), EGL_CLIENT_APIS); + + BOOST_LOG(debug) << "EGL: ["sv << vendor << "]: version ["sv << version << ']'; + BOOST_LOG(debug) << "API's supported: ["sv << apis << ']'; + + const char *extensions[] { + "EGL_KHR_create_context", + "EGL_KHR_surfaceless_context", + "EGL_EXT_image_dma_buf_import", + "EGL_KHR_image_pixmap" + }; + + for(auto ext : extensions) { + if(!std::strstr(extension_st, ext)) { + BOOST_LOG(error) << "Missing extension: ["sv << ext << ']'; + return nullptr; + } + } + + return display; +} + +std::optional make_ctx(display_t::pointer display) { + constexpr int conf_attr[] { + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE + }; + + int count; + EGLConfig conf; + if(!eglChooseConfig(display, conf_attr, &conf, 1, &count)) { + BOOST_LOG(error) << "Couldn't set config attributes: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return std::nullopt; + } + + if(!eglBindAPI(EGL_OPENGL_API)) { + BOOST_LOG(error) << "Couldn't bind API: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return std::nullopt; + } + + constexpr int attr[] { + EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE + }; + + ctx_t ctx { display, eglCreateContext(display, conf, EGL_NO_CONTEXT, attr) }; + if(fail()) { + BOOST_LOG(error) << "Couldn't create EGL context: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return std::nullopt; + } + + TUPLE_EL_REF(ctx_p, 1, ctx.el); + if(!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx_p)) { + BOOST_LOG(error) << "Couldn't make current display"sv; + return std::nullopt; + } + + if(!gladLoadGLContext(&gl::ctx, eglGetProcAddress)) { + BOOST_LOG(error) << "Couldn't load OpenGL library"sv; + return std::nullopt; + } + + BOOST_LOG(debug) << "GL: vendor: "sv << gl::ctx.GetString(GL_VENDOR); + BOOST_LOG(debug) << "GL: renderer: "sv << gl::ctx.GetString(GL_RENDERER); + BOOST_LOG(debug) << "GL: version: "sv << gl::ctx.GetString(GL_VERSION); + BOOST_LOG(debug) << "GL: shader: "sv << gl::ctx.GetString(GL_SHADING_LANGUAGE_VERSION); + + gl::ctx.PixelStorei(GL_UNPACK_ALIGNMENT, 1); + + return ctx; +} +std::optional import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) { + EGLAttrib img_attr_planes[13] { + EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_XRGB8888, + EGL_WIDTH, xrgb.width, + EGL_HEIGHT, xrgb.height, + EGL_DMA_BUF_PLANE0_FD_EXT, xrgb.fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, xrgb.offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, xrgb.pitch, + EGL_NONE + }; + + rgb_t rgb { + egl_display, + eglCreateImage(egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, img_attr_planes), + gl::tex_t::make(1) + }; + + if(!rgb->xrgb8) { + BOOST_LOG(error) << "Couldn't import RGB Image: "sv << util::hex(eglGetError()).to_string_view(); + + return std::nullopt; + } + + gl::ctx.BindTexture(GL_TEXTURE_2D, rgb->tex[0]); + gl::ctx.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, rgb->xrgb8); + + gl::ctx.BindTexture(GL_TEXTURE_2D, 0); + + gl_drain_errors; + + return rgb; +} + +std::optional import_target(display_t::pointer egl_display, std::array &&fds, const surface_descriptor_t &r8, const surface_descriptor_t &gr88) { + int img_attr_planes[2][13] { + { EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_R8, + EGL_WIDTH, r8.width, + EGL_HEIGHT, r8.height, + EGL_DMA_BUF_PLANE0_FD_EXT, r8.fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, r8.offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, r8.pitch, + EGL_NONE }, + + { EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_GR88, + EGL_WIDTH, gr88.width, + EGL_HEIGHT, gr88.height, + EGL_DMA_BUF_PLANE0_FD_EXT, r8.fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, gr88.offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, gr88.pitch, + EGL_NONE }, + }; + + nv12_t nv12 { + egl_display, + eglCreateImageKHR(egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, img_attr_planes[0]), + eglCreateImageKHR(egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, img_attr_planes[1]), + gl::tex_t::make(2), + gl::frame_buf_t::make(2), + std::move(fds) + }; + + if(!nv12->r8 || !nv12->bg88) { + BOOST_LOG(error) << "Couldn't create KHR Image"sv; + + return std::nullopt; + } + + gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[0]); + gl::ctx.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, nv12->r8); + + gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[1]); + gl::ctx.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, nv12->bg88); + + nv12->buf.bind(std::begin(nv12->tex), std::end(nv12->tex)); + + gl_drain_errors; + + return nv12; +} + +void egl_t::set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) { + video::color_t *color_p; + switch(colorspace) { + case 5: // SWS_CS_SMPTE170M + color_p = &video::colors[0]; + break; + case 1: // SWS_CS_ITU709 + color_p = &video::colors[2]; + break; + case 9: // SWS_CS_BT2020 + default: + BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; + color_p = &video::colors[0]; + }; + + if(color_range > 1) { + // Full range + ++color_p; + } + + std::string_view members[] { + util::view(color_p->color_vec_y), + util::view(color_p->color_vec_u), + util::view(color_p->color_vec_v), + util::view(color_p->range_y), + util::view(color_p->range_uv), + }; + + color_matrix.update(members, sizeof(members) / sizeof(decltype(members[0]))); +} + +int egl_t::init(int in_width, int in_height, file_t &&fd) { + file = std::move(fd); + + if(!gbm::create_device) { + BOOST_LOG(warning) << "libgbm not initialized"sv; + return -1; + } + + gbm.reset(gbm::create_device(file.el)); + if(!gbm) { + BOOST_LOG(error) << "Couldn't create GBM device: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return -1; + } + + display = make_display(gbm.get()); + if(!display) { + return -1; + } + + auto ctx_opt = make_ctx(display.get()); + if(!ctx_opt) { + return -1; + } + + ctx = std::move(*ctx_opt); + + { + const char *sources[] { + SUNSHINE_SHADERS_DIR "/ConvertUV.frag", + SUNSHINE_SHADERS_DIR "/ConvertUV.vert", + SUNSHINE_SHADERS_DIR "/ConvertY.frag", + SUNSHINE_SHADERS_DIR "/Scene.vert", + SUNSHINE_SHADERS_DIR "/Scene.frag", + }; + + GLenum shader_type[2] { + GL_FRAGMENT_SHADER, + GL_VERTEX_SHADER, + }; + + constexpr auto count = sizeof(sources) / sizeof(const char *); + + util::Either compiled_sources[count]; + + bool error_flag = false; + for(int x = 0; x < count; ++x) { + auto &compiled_source = compiled_sources[x]; + + compiled_source = gl::shader_t::compile(read_file(sources[x]), shader_type[x % 2]); + gl_drain_errors; + + if(compiled_source.has_right()) { + BOOST_LOG(error) << sources[x] << ": "sv << compiled_source.right(); + error_flag = true; + } + } + + if(error_flag) { + return -1; + } + + auto program = gl::program_t::link(compiled_sources[1].left(), compiled_sources[0].left()); + if(program.has_right()) { + BOOST_LOG(error) << "GL linker: "sv << program.right(); + return -1; + } + + // UV - shader + this->program[1] = std::move(program.left()); + + program = gl::program_t::link(compiled_sources[3].left(), compiled_sources[2].left()); + if(program.has_right()) { + BOOST_LOG(error) << "GL linker: "sv << program.right(); + return -1; + } + + // Y - shader + this->program[0] = std::move(program.left()); + } + + auto color_p = &video::colors[0]; + std::pair members[] { + std::make_pair("color_vec_y", util::view(color_p->color_vec_y)), + std::make_pair("color_vec_u", util::view(color_p->color_vec_u)), + std::make_pair("color_vec_v", util::view(color_p->color_vec_v)), + std::make_pair("range_y", util::view(color_p->range_y)), + std::make_pair("range_uv", util::view(color_p->range_uv)), + }; + + auto color_matrix = program[0].uniform("ColorMatrix", members, sizeof(members) / sizeof(decltype(members[0]))); + if(!color_matrix) { + return -1; + } + + this->color_matrix = std::move(*color_matrix); + + tex_in = gl::tex_t::make(1); + + this->in_width = in_width; + this->in_height = in_height; + return 0; +} + +int egl_t::convert(platf::img_t &img) { + auto tex = tex_in[0]; + + gl::ctx.BindTexture(GL_TEXTURE_2D, tex); + gl::ctx.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, in_width, in_height, GL_BGRA, GL_UNSIGNED_BYTE, img.data); + + GLenum attachments[] { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1 + }; + + for(int x = 0; x < sizeof(attachments) / sizeof(decltype(attachments[0])); ++x) { + gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]); + gl::ctx.DrawBuffers(1, &attachments[x]); + + auto status = gl::ctx.CheckFramebufferStatus(GL_FRAMEBUFFER); + if(status != GL_FRAMEBUFFER_COMPLETE) { + BOOST_LOG(error) << "Pass "sv << x << ": CheckFramebufferStatus() --> [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + gl::ctx.BindTexture(GL_TEXTURE_2D, tex); + + gl::ctx.UseProgram(program[x].handle()); + program[x].bind(color_matrix); + + gl::ctx.Viewport(offsetX / (x + 1), offsetY / (x + 1), out_width / (x + 1), out_height / (x + 1)); + gl::ctx.DrawArrays(GL_TRIANGLES, 0, 3); + } + + return 0; +} + +int egl_t::_set_frame(AVFrame *frame) { + this->hwframe.reset(frame); + this->frame = frame; + + if(av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0)) { + BOOST_LOG(error) << "Couldn't get hwframe for VAAPI"sv; + + return -1; + } + + // Ensure aspect ratio is maintained + auto scalar = std::fminf(frame->width / (float)in_width, frame->height / (float)in_height); + auto out_width_f = in_width * scalar; + auto out_height_f = in_height * scalar; + + // result is always positive + auto offsetX_f = (frame->width - out_width_f) / 2; + auto offsetY_f = (frame->height - out_height_f) / 2; + + out_width = out_width_f; + out_height = out_height_f; + + offsetX = offsetX_f; + offsetY = offsetY_f; + + auto tex = tex_in[0]; + + gl::ctx.BindTexture(GL_TEXTURE_2D, tex); + gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, in_width, in_height); + + auto loc_width_i = gl::ctx.GetUniformLocation(program[1].handle(), "width_i"); + if(loc_width_i < 0) { + BOOST_LOG(error) << "Couldn't find uniform [width_i]"sv; + return -1; + } + + auto width_i = 1.0f / out_width; + gl::ctx.UseProgram(program[1].handle()); + gl::ctx.Uniform1fv(loc_width_i, 1, &width_i); + + gl_drain_errors; + return 0; +} + +egl_t::~egl_t() { + if(gl::ctx.GetError) { + gl_drain_errors; + } +} +} // namespace egl + +void free_frame(AVFrame *frame) { + av_frame_free(&frame); +} \ No newline at end of file diff --git a/sunshine/platform/linux/graphics.h b/sunshine/platform/linux/graphics.h new file mode 100644 index 00000000..f0f05883 --- /dev/null +++ b/sunshine/platform/linux/graphics.h @@ -0,0 +1,264 @@ +#ifndef SUNSHINE_PLATFORM_LINUX_OPENGL_H +#define SUNSHINE_PLATFORM_LINUX_OPENGL_H + +#include +#include + +#include +#include + +#include "misc.h" +#include "sunshine/main.h" +#include "sunshine/platform/common.h" +#include "sunshine/utility.h" + +extern "C" int close(int __fd); + +struct AVFrame; +void free_frame(AVFrame *frame); + +using frame_t = util::safe_ptr; + +namespace gl { +extern GladGLContext ctx; +void drain_errors(const std::string_view &prefix); + +class tex_t : public util::buffer_t { + using util::buffer_t::buffer_t; + +public: + tex_t(tex_t &&) = default; + tex_t &operator=(tex_t &&) = default; + + ~tex_t(); + + static tex_t make(std::size_t count); +}; + +class frame_buf_t : public util::buffer_t { + using util::buffer_t::buffer_t; + +public: + frame_buf_t(frame_buf_t &&) = default; + frame_buf_t &operator=(frame_buf_t &&) = default; + + ~frame_buf_t(); + + static frame_buf_t make(std::size_t count); + + template + void bind(It it_begin, It it_end) { + using namespace std::literals; + if(std::distance(it_begin, it_end) > size()) { + BOOST_LOG(warning) << "To many elements to bind"sv; + return; + } + + int x = 0; + std::for_each(it_begin, it_end, [&](auto tex) { + ctx.BindFramebuffer(GL_FRAMEBUFFER, (*this)[x]); + ctx.BindTexture(GL_TEXTURE_2D, tex); + + ctx.FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, tex, 0); + + ++x; + }); + } +}; + +class shader_t { + KITTY_USING_MOVE_T(shader_internal_t, GLuint, std::numeric_limits::max(), { + if(el != std::numeric_limits::max()) { + ctx.DeleteShader(el); + } + }); + +public: + std::string err_str(); + + static util::Either compile(const std::string_view &source, GLenum type); + + GLuint handle() const; + +private: + shader_internal_t _shader; +}; + +class buffer_t { + KITTY_USING_MOVE_T(buffer_internal_t, GLuint, std::numeric_limits::max(), { + if(el != std::numeric_limits::max()) { + ctx.DeleteBuffers(1, &el); + } + }); + +public: + static buffer_t make(util::buffer_t &&offsets, const char *block, const std::string_view &data); + + GLuint handle() const; + + const char *block() const; + + void update(const std::string_view &view, std::size_t offset = 0); + void update(std::string_view *members, std::size_t count, std::size_t offset = 0); + +private: + const char *_block; + + std::size_t _size; + + util::buffer_t _offsets; + + buffer_internal_t _buffer; +}; + +class program_t { + KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits::max(), { + if(el != std::numeric_limits::max()) { + ctx.DeleteProgram(el); + } + }); + +public: + std::string err_str(); + + static util::Either link(const shader_t &vert, const shader_t &frag); + + void bind(const buffer_t &buffer); + + std::optional uniform(const char *block, std::pair *members, std::size_t count); + + GLuint handle() const; + +private: + program_internal_t _program; +}; +} // namespace gl + +namespace gbm { +struct device; +typedef void (*device_destroy_fn)(device *gbm); +typedef device *(*create_device_fn)(int fd); + +extern device_destroy_fn device_destroy; +extern create_device_fn create_device; + +using gbm_t = util::dyn_safe_ptr; + +int init(); + +} // namespace gbm + +namespace egl { +using display_t = util::dyn_safe_ptr_v2; + +KITTY_USING_MOVE_T(file_t, int, -1, { + if(el >= 0) { + close(el); + } +}); + +struct rgb_img_t { + display_t::pointer display; + EGLImage xrgb8; + + gl::tex_t tex; +}; + +struct nv12_img_t { + display_t::pointer display; + EGLImage r8; + EGLImage bg88; + + gl::tex_t tex; + gl::frame_buf_t buf; + + // sizeof(va::DRMPRIMESurfaceDescriptor::objects) / sizeof(va::DRMPRIMESurfaceDescriptor::objects[0]); + static constexpr std::size_t num_fds = 4; + + std::array fds; +}; + +KITTY_USING_MOVE_T(rgb_t, rgb_img_t, , { + if(el.xrgb8) { + eglDestroyImage(el.display, el.xrgb8); + } +}); + +KITTY_USING_MOVE_T(nv12_t, nv12_img_t, , { + if(el.r8) { + eglDestroyImageKHR(el.display, el.r8); + } + + if(el.bg88) { + eglDestroyImageKHR(el.display, el.bg88); + } +}); + +KITTY_USING_MOVE_T(ctx_t, (std::tuple), , { + TUPLE_2D_REF(disp, ctx, el); + if(ctx) { + if(ctx == eglGetCurrentContext()) { + eglMakeCurrent(disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + } + eglDestroyContext(disp, ctx); + } +}); + +struct surface_descriptor_t { + int fd; + + int width; + int height; + int offset; + int pitch; +}; + +display_t make_display(gbm::gbm_t::pointer gbm); +std::optional make_ctx(display_t::pointer display); + +std::optional import_source( + display_t::pointer egl_display, + const surface_descriptor_t &xrgb); + +std::optional import_target( + display_t::pointer egl_display, + std::array &&fds, + const surface_descriptor_t &r8, const surface_descriptor_t &gr88); + +class egl_t : public platf::hwdevice_t { +public: + void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override; + + int init(int in_width, int in_height, file_t &&fd); + + int convert(platf::img_t &img) override; + + /** + * Any specialization needs to populate nv12_t nv12 + * Then call this function + */ + int _set_frame(AVFrame *frame); + + ~egl_t() override; + + int in_width, in_height; + int out_width, out_height; + int offsetX, offsetY; + + frame_t hwframe; + + file_t file; + gbm::gbm_t gbm; + display_t display; + ctx_t ctx; + + gl::tex_t tex_in; + nv12_t nv12; + gl::program_t program[2]; + gl::buffer_t color_matrix; +}; + +bool fail(); +} // namespace egl + +#endif \ No newline at end of file diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp new file mode 100644 index 00000000..3cd51e5c --- /dev/null +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include +#include + +#include "sunshine/main.h" +#include "sunshine/platform/common.h" +#include "sunshine/utility.h" + +#include "graphics.h" + +using namespace std::literals; + +namespace platf { + +namespace kms { +using plane_res_t = util::safe_ptr; +using plane_t = util::safe_ptr; +using fb_t = util::safe_ptr; +using fb2_t = util::safe_ptr; + +struct kms_img_t : public img_t { + ~kms_img_t() override { + delete[] data; + data = nullptr; + } +}; + +class display_t : public platf::display_t { +public: + display_t() : platf::display_t() {} + + int init(const std::string &display_name, int framerate) { + if(!gbm::create_device) { + BOOST_LOG(warning) << "libgbm not initialized"sv; + return -1; + } + + 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); + 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 = 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); + continue; + } + + if(!plane->fb_id) { + continue; + } + + fb_t fb = drmModeGetFB(fd.el, plane->fb_id); + 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; + } + + 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; + } + + BOOST_LOG(info) << "Opened Framebuffer for plane ["sv << plane->fb_id << ']'; + + auto status = drmPrimeHandleToFD(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); + }); + + width = fb->width; + height = fb->height; + pitch = fb->pitch; + env_width = width; + env_height = height; + } + + gbm.reset(gbm::create_device(fd.el)); + if(!gbm) { + BOOST_LOG(error) << "Couldn't create GBM device: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return -1; + } + + display = egl::make_display(gbm.get()); + if(!display) { + return -1; + } + + auto ctx_opt = egl::make_ctx(display.get()); + if(!ctx_opt) { + return -1; + } + + ctx = std::move(*ctx_opt); + + auto rgb_opt = egl::import_source(display.get(), + { + fb_fd.el, + width, + height, + 0, + pitch, + }); + + if(!rgb_opt) { + return -1; + } + + rgb = std::move(*rgb_opt); + + return 0; + } + + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) { + auto next_frame = std::chrono::steady_clock::now(); + + while(img) { + auto now = std::chrono::steady_clock::now(); + + if(next_frame > now) { + std::this_thread::sleep_for((next_frame - now) / 3 * 2); + } + while(next_frame > now) { + now = std::chrono::steady_clock::now(); + } + next_frame = now + delay; + + auto status = snapshot(img.get(), 1000ms, *cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; + } + + capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) { + + gl::ctx.BindTexture(GL_TEXTURE_2D, rgb->tex[0]); + + gl::ctx.GetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->data); + + return capture_e::ok; + } + + std::shared_ptr alloc_img() override { + auto img = std::make_shared(); + img->width = width; + img->height = height; + img->pixel_pitch = 4; + img->row_pitch = img->pixel_pitch * width; + img->data = new std::uint8_t[height * img->row_pitch]; + + return img; + } + + int dummy_img(platf::img_t *img) override { + return 0; + } + + std::chrono::nanoseconds delay; + + egl::file_t fd; + egl::file_t fb_fd; + + gbm::gbm_t gbm; + egl::display_t display; + egl::ctx_t ctx; + + egl::rgb_t rgb; +}; +} // namespace kms + +std::shared_ptr 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 {}; +} + +} // namespace platf \ No newline at end of file diff --git a/sunshine/platform/linux/vaapi.cpp b/sunshine/platform/linux/vaapi.cpp index c9d0426b..905c0bbe 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/sunshine/platform/linux/vaapi.cpp @@ -4,42 +4,21 @@ #include #include -#include extern "C" { #include } +#include "graphics.h" #include "misc.h" #include "sunshine/config.h" #include "sunshine/main.h" #include "sunshine/platform/common.h" #include "sunshine/utility.h" -#include "sunshine/video.h" - -// I want to have as little build dependencies as possible -// There aren't that many DRM_FORMAT I need to use, so define them here -// -// They aren't likely to change any time soon. -#define fourcc_code(a, b, c, d) ((std::uint32_t)(a) | ((std::uint32_t)(b) << 8) | \ - ((std::uint32_t)(c) << 16) | ((std::uint32_t)(d) << 24)) -#define DRM_FORMAT_R8 fourcc_code('R', '8', ' ', ' ') /* [7:0] R */ -#define DRM_FORMAT_GR88 fourcc_code('G', 'R', '8', '8') /* [15:0] G:R 8:8 little endian */ - - -#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/opengl" - -#define STRINGIFY(x) #x -#define gl_drain_errors_helper(x) gl::drain_errors("line " STRINGIFY(x)) -#define gl_drain_errors gl_drain_errors_helper(__LINE__) using namespace std::literals; -static void free_frame(AVFrame *frame) { - av_frame_free(&frame); -} - -using frame_t = util::safe_ptr; +extern "C" struct AVBufferRef; namespace va { constexpr auto SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 = 0x40000000; @@ -178,409 +157,35 @@ int init_drm() { funcs_loaded = true; return 0; } -} // namespace va - -namespace gbm { -struct device; -typedef void (*device_destroy_fn)(device *gbm); -typedef device *(*create_device_fn)(int fd); - -device_destroy_fn device_destroy; -create_device_fn create_device; - -int init() { - static void *handle { nullptr }; - static bool funcs_loaded = false; - - if(funcs_loaded) return 0; - - if(!handle) { - handle = dyn::handle({ "libgbm.so.1", "libgbm.so" }); - if(!handle) { - return -1; - } - } - - std::vector> funcs { - { (GLADapiproc *)&device_destroy, "gbm_device_destroy" }, - { (GLADapiproc *)&create_device, "gbm_create_device" }, - }; - - if(dyn::load(handle, funcs)) { - return -1; - } - - funcs_loaded = true; - return 0; -} - -} // namespace gbm - -namespace gl { -static GladGLContext ctx; - -void drain_errors(const std::string_view &prefix) { - GLenum err; - while((err = ctx.GetError()) != GL_NO_ERROR) { - BOOST_LOG(error) << "GL: "sv << prefix << ": ["sv << util::hex(err).to_string_view() << ']'; - } -} - -class tex_t : public util::buffer_t { - using util::buffer_t::buffer_t; - -public: - tex_t(tex_t &&) = default; - tex_t &operator=(tex_t &&) = default; - - ~tex_t() { - if(!size() == 0) { - ctx.DeleteTextures(size(), begin()); - } - } - - static tex_t make(std::size_t count) { - tex_t textures { count }; - - ctx.GenTextures(textures.size(), textures.begin()); - - float color[] = { 0.0f, 0.0f, 0.0f, 1.0f }; - - for(auto tex : textures) { - gl::ctx.BindTexture(GL_TEXTURE_2D, tex); - gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // x - gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // y - gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - gl::ctx.TexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color); - } - - return textures; - } -}; - -class frame_buf_t : public util::buffer_t { - using util::buffer_t::buffer_t; - -public: - frame_buf_t(frame_buf_t &&) = default; - frame_buf_t &operator=(frame_buf_t &&) = default; - - ~frame_buf_t() { - if(begin()) { - ctx.DeleteFramebuffers(size(), begin()); - } - } - - static frame_buf_t make(std::size_t count) { - frame_buf_t frame_buf { count }; - - ctx.GenFramebuffers(frame_buf.size(), frame_buf.begin()); - - return frame_buf; - } - - template - void bind(It it_begin, It it_end) { - if(std::distance(it_begin, it_end) > size()) { - BOOST_LOG(warning) << "To many elements to bind"sv; - return; - } - - int x = 0; - std::for_each(it_begin, it_end, [&](auto tex) { - ctx.BindFramebuffer(GL_FRAMEBUFFER, (*this)[x]); - ctx.BindTexture(GL_TEXTURE_2D, tex); - - ctx.FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, tex, 0); - - ++x; - }); - } -}; - -class shader_t { - KITTY_USING_MOVE_T(shader_internal_t, GLuint, std::numeric_limits::max(), { - if(el != std::numeric_limits::max()) { - ctx.DeleteShader(el); - } - }); - -public: - std::string err_str() { - int length; - ctx.GetShaderiv(handle(), GL_INFO_LOG_LENGTH, &length); - - std::string string; - string.resize(length); - - ctx.GetShaderInfoLog(handle(), length, &length, string.data()); - - string.resize(length - 1); - - return string; - } - - static util::Either compile(const std::string_view &source, GLenum type) { - shader_t shader; - - auto data = source.data(); - GLint length = source.length(); - - shader._shader.el = ctx.CreateShader(type); - ctx.ShaderSource(shader.handle(), 1, &data, &length); - ctx.CompileShader(shader.handle()); - - int status = 0; - ctx.GetShaderiv(shader.handle(), GL_COMPILE_STATUS, &status); - - if(!status) { - return shader.err_str(); - } - - return shader; - } - - GLuint handle() const { - return _shader.el; - } - -private: - shader_internal_t _shader; -}; - -class buffer_t { - KITTY_USING_MOVE_T(buffer_internal_t, GLuint, std::numeric_limits::max(), { - if(el != std::numeric_limits::max()) { - ctx.DeleteBuffers(1, &el); - } - }); - -public: - static buffer_t make(util::buffer_t &&offsets, const char *block, const std::string_view &data) { - buffer_t buffer; - buffer._block = block; - buffer._size = data.size(); - buffer._offsets = std::move(offsets); - - ctx.GenBuffers(1, &buffer._buffer.el); - ctx.BindBuffer(GL_UNIFORM_BUFFER, buffer.handle()); - ctx.BufferData(GL_UNIFORM_BUFFER, data.size(), (const std::uint8_t *)data.data(), GL_DYNAMIC_DRAW); - - return buffer; - } - - GLuint handle() const { - return _buffer.el; - } - - const char *block() const { - return _block; - } - - void update(const std::string_view &view, std::size_t offset = 0) { - ctx.BindBuffer(GL_UNIFORM_BUFFER, handle()); - ctx.BufferSubData(GL_UNIFORM_BUFFER, offset, view.size(), (const void *)view.data()); - } - - void update(std::string_view *members, std::size_t count, std::size_t offset = 0) { - util::buffer_t buffer { _size }; - - for(int x = 0; x < count; ++x) { - auto val = members[x]; - - std::copy_n((const std::uint8_t *)val.data(), val.size(), &buffer[_offsets[x]]); - } - - update(util::view(buffer.begin(), buffer.end()), offset); - } - -private: - const char *_block; - - std::size_t _size; - - util::buffer_t _offsets; - - buffer_internal_t _buffer; -}; - -class program_t { - KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits::max(), { - if(el != std::numeric_limits::max()) { - ctx.DeleteProgram(el); - } - }); - -public: - std::string err_str() { - int length; - ctx.GetProgramiv(handle(), GL_INFO_LOG_LENGTH, &length); - - std::string string; - string.resize(length); - - ctx.GetShaderInfoLog(handle(), length, &length, string.data()); - - string.resize(length - 1); - - return string; - } - - static util::Either link(const shader_t &vert, const shader_t &frag) { - program_t program; - - program._program.el = ctx.CreateProgram(); - - ctx.AttachShader(program.handle(), vert.handle()); - ctx.AttachShader(program.handle(), frag.handle()); - - // p_handle stores a copy of the program handle, since program will be moved before - // the fail guard funcion is called. - auto fg = util::fail_guard([p_handle = program.handle(), &vert, &frag]() { - ctx.DetachShader(p_handle, vert.handle()); - ctx.DetachShader(p_handle, frag.handle()); - }); - - ctx.LinkProgram(program.handle()); - - int status = 0; - ctx.GetProgramiv(program.handle(), GL_LINK_STATUS, &status); - - if(!status) { - return program.err_str(); - } - - return program; - } - - void bind(const buffer_t &buffer) { - ctx.UseProgram(handle()); - auto i = ctx.GetUniformBlockIndex(handle(), buffer.block()); - - ctx.BindBufferBase(GL_UNIFORM_BUFFER, i, buffer.handle()); - } - - std::optional uniform(const char *block, std::pair *members, std::size_t count) { - auto i = ctx.GetUniformBlockIndex(handle(), block); - if(i == GL_INVALID_INDEX) { - BOOST_LOG(error) << "Couldn't find index of ["sv << block << ']'; - return std::nullopt; - } - - int size; - ctx.GetActiveUniformBlockiv(handle(), i, GL_UNIFORM_BLOCK_DATA_SIZE, &size); - - bool error_flag = false; - - util::buffer_t offsets { count }; - auto indices = (std::uint32_t *)alloca(count * sizeof(std::uint32_t)); - auto names = (const char **)alloca(count * sizeof(const char *)); - auto names_p = names; - - std::for_each_n(members, count, [names_p](auto &member) mutable { - *names_p++ = std::get<0>(member); - }); - - std::fill_n(indices, count, GL_INVALID_INDEX); - ctx.GetUniformIndices(handle(), count, names, indices); - - for(int x = 0; x < count; ++x) { - if(indices[x] == GL_INVALID_INDEX) { - error_flag = true; - - BOOST_LOG(error) << "Couldn't find ["sv << block << '.' << members[x].first << ']'; - } - } - - if(error_flag) { - return std::nullopt; - } - - ctx.GetActiveUniformsiv(handle(), count, indices, GL_UNIFORM_OFFSET, offsets.begin()); - util::buffer_t buffer { (std::size_t)size }; - - for(int x = 0; x < count; ++x) { - auto val = std::get<1>(members[x]); - - std::copy_n((const std::uint8_t *)val.data(), val.size(), &buffer[offsets[x]]); - } - - return buffer_t::make(std::move(offsets), block, std::string_view { (char *)buffer.begin(), buffer.size() }); - } - - GLuint handle() const { - return _program.el; - } - -private: - program_internal_t _program; -}; -} // namespace gl - -namespace platf { -namespace egl { - -constexpr auto EGL_LINUX_DMA_BUF_EXT = 0x3270; -constexpr auto EGL_LINUX_DRM_FOURCC_EXT = 0x3271; -constexpr auto EGL_DMA_BUF_PLANE0_FD_EXT = 0x3272; -constexpr auto EGL_DMA_BUF_PLANE0_OFFSET_EXT = 0x3273; -constexpr auto EGL_DMA_BUF_PLANE0_PITCH_EXT = 0x3274; - -using display_t = util::dyn_safe_ptr_v2; -using gbm_t = util::dyn_safe_ptr; int vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf); -KITTY_USING_MOVE_T(file_t, int, -1, { - if(el >= 0) { - close(el); - } -}); - -struct nv12_img_t { - display_t::pointer display; - EGLImage r8; - EGLImage bg88; - - gl::tex_t tex; - gl::frame_buf_t buf; - - static constexpr std::size_t num_fds = - sizeof(va::DRMPRIMESurfaceDescriptor::objects) / sizeof(va::DRMPRIMESurfaceDescriptor::objects[0]); - - std::array fds; -}; - -KITTY_USING_MOVE_T(nv12_t, nv12_img_t, , { - if(el.r8) { - eglDestroyImageKHR(el.display, el.r8); - } - - if(el.bg88) { - eglDestroyImageKHR(el.display, el.bg88); - } -}); - -KITTY_USING_MOVE_T(ctx_t, (std::tuple), , { - TUPLE_2D_REF(disp, ctx, el); - if(ctx) { - if(ctx == eglGetCurrentContext()) { - eglMakeCurrent(disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - eglDestroyContext(disp, ctx); - } -}); - -bool fail() { - return eglGetError() != EGL_SUCCESS; -} - -class egl_t : public platf::hwdevice_t { +class va_t : public egl::egl_t { public: - std::optional import(va::VASurfaceID surface) { + int init(int in_width, int in_height, const char *render_device) { + if(!va::initialize) { + BOOST_LOG(warning) << "libva not initialized"sv; + return -1; + } + + data = (void *)vaapi_make_hwdevice_ctx; + + egl::file_t fd = open(render_device, O_RDWR); + if(fd.el < 0) { + char string[1024]; + BOOST_LOG(error) << "Couldn't open "sv << render_device << ": " << strerror_r(errno, string, sizeof(string)); + + return -1; + } + + return egl::egl_t::init(in_width, in_height, std::move(fd)); + } + + int set_frame(AVFrame *frame) override { // No deallocation necessary + va::DRMPRIMESurfaceDescriptor prime; + va::VASurfaceID surface = (std::uintptr_t)frame->data[3]; auto status = va::exportSurfaceHandle( va_display, @@ -592,382 +197,43 @@ public: BOOST_LOG(error) << "Couldn't export va surface handle: ["sv << (int)surface << "]: "sv << va::errorStr(status); - return std::nullopt; + return -1; } // Keep track of file descriptors - std::array fds; + std::array fds; for(int x = 0; x < prime.num_objects; ++x) { fds[x] = prime.objects[x].fd; } - int img_attr_planes[2][13] { - { EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_R8, - EGL_WIDTH, (int)prime.width, - EGL_HEIGHT, (int)prime.height, - EGL_DMA_BUF_PLANE0_FD_EXT, prime.objects[prime.layers[0].object_index[0]].fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, (int)prime.layers[0].offset[0], - EGL_DMA_BUF_PLANE0_PITCH_EXT, (int)prime.layers[0].pitch[0], - EGL_NONE }, - - { EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_GR88, - EGL_WIDTH, (int)prime.width / 2, - EGL_HEIGHT, (int)prime.height / 2, - EGL_DMA_BUF_PLANE0_FD_EXT, prime.objects[prime.layers[0].object_index[1]].fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, (int)prime.layers[0].offset[1], - EGL_DMA_BUF_PLANE0_PITCH_EXT, (int)prime.layers[0].pitch[1], - EGL_NONE }, - }; - - nv12_t nv12 { + auto nv12_opt = egl::import_target( display.get(), - eglCreateImageKHR(display.get(), EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, img_attr_planes[0]), - eglCreateImageKHR(display.get(), EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, img_attr_planes[1]), - gl::tex_t::make(2), - gl::frame_buf_t::make(2), - std::move(fds) - }; + std::move(fds), + { + prime.objects[prime.layers[0].object_index[0]].fd, + (int)prime.width, + (int)prime.height, + (int)prime.layers[0].offset[0], + (int)prime.layers[0].pitch[0], + }, + { + prime.objects[prime.layers[0].object_index[1]].fd, + (int)prime.width / 2, + (int)prime.height / 2, + (int)prime.layers[0].offset[1], + (int)prime.layers[0].pitch[1], + }); - if(!nv12->r8 || !nv12->bg88) { - BOOST_LOG(error) << "Couldn't create KHR Image"sv; - - return std::nullopt; - } - - gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[0]); - gl::ctx.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, nv12->r8); - - gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[1]); - gl::ctx.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, nv12->bg88); - - nv12->buf.bind(std::begin(nv12->tex), std::end(nv12->tex)); - - gl_drain_errors; - - return nv12; - } - - void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { - video::color_t *color_p; - switch(colorspace) { - case 5: // SWS_CS_SMPTE170M - color_p = &video::colors[0]; - break; - case 1: // SWS_CS_ITU709 - color_p = &video::colors[2]; - break; - case 9: // SWS_CS_BT2020 - default: - BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; - color_p = &video::colors[0]; - }; - - if(color_range > 1) { - // Full range - ++color_p; - } - - std::string_view members[] { - util::view(color_p->color_vec_y), - util::view(color_p->color_vec_u), - util::view(color_p->color_vec_v), - util::view(color_p->range_y), - util::view(color_p->range_uv), - }; - - color_matrix.update(members, sizeof(members) / sizeof(decltype(members[0]))); - } - - int init(int in_width, int in_height, const char *render_device) { - if(!va::initialize || !gbm::create_device) { - if(!va::initialize) BOOST_LOG(warning) << "libva not initialized"sv; - if(!gbm::create_device) BOOST_LOG(warning) << "libgbm not initialized"sv; - return -1; - } - - file.el = open(render_device, O_RDWR); - - if(file.el < 0) { - char error_buf[1024]; - BOOST_LOG(error) << "Couldn't open ["sv << render_device << "]: "sv << strerror_r(errno, error_buf, sizeof(error_buf)); - return -1; - } - - gbm.reset(gbm::create_device(file.el)); - if(!gbm) { - BOOST_LOG(error) << "Couldn't create GBM device: ["sv << util::hex(eglGetError()).to_string_view() << ']'; - return -1; - } - - constexpr auto EGL_PLATFORM_GBM_MESA = 0x31D7; - - display.reset(eglGetPlatformDisplay(EGL_PLATFORM_GBM_MESA, gbm.get(), nullptr)); - if(fail()) { - BOOST_LOG(error) << "Couldn't open EGL display: ["sv << util::hex(eglGetError()).to_string_view() << ']'; - return -1; - } - - int major, minor; - if(!eglInitialize(display.get(), &major, &minor)) { - BOOST_LOG(error) << "Couldn't initialize EGL display: ["sv << util::hex(eglGetError()).to_string_view() << ']'; - return -1; - } - - const char *extension_st = eglQueryString(display.get(), EGL_EXTENSIONS); - const char *version = eglQueryString(display.get(), EGL_VERSION); - const char *vendor = eglQueryString(display.get(), EGL_VENDOR); - const char *apis = eglQueryString(display.get(), EGL_CLIENT_APIS); - - BOOST_LOG(debug) << "EGL: ["sv << vendor << "]: version ["sv << version << ']'; - BOOST_LOG(debug) << "API's supported: ["sv << apis << ']'; - - const char *extensions[] { - "EGL_KHR_create_context", - "EGL_KHR_surfaceless_context", - "EGL_EXT_image_dma_buf_import", - "EGL_KHR_image_pixmap" - }; - - for(auto ext : extensions) { - if(!std::strstr(extension_st, ext)) { - BOOST_LOG(error) << "Missing extension: ["sv << ext << ']'; - return -1; - } - } - - constexpr int conf_attr[] { - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE - }; - - int count; - EGLConfig conf; - if(!eglChooseConfig(display.get(), conf_attr, &conf, 1, &count)) { - BOOST_LOG(error) << "Couldn't set config attributes: ["sv << util::hex(eglGetError()).to_string_view() << ']'; - return -1; - } - - if(!eglBindAPI(EGL_OPENGL_API)) { - BOOST_LOG(error) << "Couldn't bind API: ["sv << util::hex(eglGetError()).to_string_view() << ']'; - return -1; - } - - constexpr int attr[] { - EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE - }; - - ctx.el = { display.get(), eglCreateContext(display.get(), conf, EGL_NO_CONTEXT, attr) }; - if(fail()) { - BOOST_LOG(error) << "Couldn't create EGL context: ["sv << util::hex(eglGetError()).to_string_view() << ']'; - return -1; - } - - TUPLE_EL_REF(ctx_p, 1, ctx.el); - if(!eglMakeCurrent(display.get(), EGL_NO_SURFACE, EGL_NO_SURFACE, ctx_p)) { - BOOST_LOG(error) << "Couldn't make current display"sv; - return -1; - } - - if(!gladLoadGLContext(&gl::ctx, eglGetProcAddress)) { - BOOST_LOG(error) << "Couldn't load OpenGL library"sv; - return -1; - } - - BOOST_LOG(debug) << "GL: vendor: "sv << gl::ctx.GetString(GL_VENDOR); - BOOST_LOG(debug) << "GL: renderer: "sv << gl::ctx.GetString(GL_RENDERER); - BOOST_LOG(debug) << "GL: version: "sv << gl::ctx.GetString(GL_VERSION); - BOOST_LOG(debug) << "GL: shader: "sv << gl::ctx.GetString(GL_SHADING_LANGUAGE_VERSION); - - gl::ctx.PixelStorei(GL_UNPACK_ALIGNMENT, 1); - - { - const char *sources[] { - SUNSHINE_SHADERS_DIR "/ConvertUV.frag", - SUNSHINE_SHADERS_DIR "/ConvertUV.vert", - SUNSHINE_SHADERS_DIR "/ConvertY.frag", - SUNSHINE_SHADERS_DIR "/Scene.vert", - SUNSHINE_SHADERS_DIR "/Scene.frag", - }; - - GLenum shader_type[2] { - GL_FRAGMENT_SHADER, - GL_VERTEX_SHADER, - }; - - constexpr auto count = sizeof(sources) / sizeof(const char *); - - util::Either compiled_sources[count]; - - bool error_flag = false; - for(int x = 0; x < count; ++x) { - auto &compiled_source = compiled_sources[x]; - - compiled_source = gl::shader_t::compile(read_file(sources[x]), shader_type[x % 2]); - gl_drain_errors; - - if(compiled_source.has_right()) { - BOOST_LOG(error) << sources[x] << ": "sv << compiled_source.right(); - error_flag = true; - } - } - - if(error_flag) { - return -1; - } - - auto program = gl::program_t::link(compiled_sources[1].left(), compiled_sources[0].left()); - if(program.has_right()) { - BOOST_LOG(error) << "GL linker: "sv << program.right(); - return -1; - } - - // UV - shader - this->program[1] = std::move(program.left()); - - program = gl::program_t::link(compiled_sources[3].left(), compiled_sources[2].left()); - if(program.has_right()) { - BOOST_LOG(error) << "GL linker: "sv << program.right(); - return -1; - } - - // Y - shader - this->program[0] = std::move(program.left()); - } - - auto color_p = &video::colors[0]; - std::pair members[] { - std::make_pair("color_vec_y", util::view(color_p->color_vec_y)), - std::make_pair("color_vec_u", util::view(color_p->color_vec_u)), - std::make_pair("color_vec_v", util::view(color_p->color_vec_v)), - std::make_pair("range_y", util::view(color_p->range_y)), - std::make_pair("range_uv", util::view(color_p->range_uv)), - }; - - auto color_matrix = program[0].uniform("ColorMatrix", members, sizeof(members) / sizeof(decltype(members[0]))); - if(!color_matrix) { - return -1; - } - - this->color_matrix = std::move(*color_matrix); - - tex_in = gl::tex_t::make(1); - - this->in_width = in_width; - this->in_height = in_height; - - data = (void *)vaapi_make_hwdevice_ctx; - gl_drain_errors; - return 0; - } - - int convert(platf::img_t &img) override { - auto tex = tex_in[0]; - - gl::ctx.BindTexture(GL_TEXTURE_2D, tex); - gl::ctx.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, in_width, in_height, GL_BGRA, GL_UNSIGNED_BYTE, img.data); - - GLenum attachments[] { - GL_COLOR_ATTACHMENT0, - GL_COLOR_ATTACHMENT1 - }; - - for(int x = 0; x < sizeof(attachments) / sizeof(decltype(attachments[0])); ++x) { - gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]); - gl::ctx.DrawBuffers(1, &attachments[x]); - - auto status = gl::ctx.CheckFramebufferStatus(GL_FRAMEBUFFER); - if(status != GL_FRAMEBUFFER_COMPLETE) { - BOOST_LOG(error) << "Pass "sv << x << ": CheckFramebufferStatus() --> [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - gl::ctx.BindTexture(GL_TEXTURE_2D, tex); - - gl::ctx.UseProgram(program[x].handle()); - program[x].bind(color_matrix); - - gl::ctx.Viewport(offsetX / (x + 1), offsetY / (x + 1), out_width / (x + 1), out_height / (x + 1)); - gl::ctx.DrawArrays(GL_TRIANGLES, 0, 3); - } - - return 0; - } - - int set_frame(AVFrame *frame) override { - this->hwframe.reset(frame); - this->frame = frame; - - if(av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0)) { - BOOST_LOG(error) << "Couldn't get hwframe for VAAPI"sv; - - return -1; - } - - va::VASurfaceID surface = (std::uintptr_t)frame->data[3]; - - auto nv12_opt = import(surface); if(!nv12_opt) { return -1; } nv12 = std::move(*nv12_opt); - // // Ensure aspect ratio is maintained - auto scalar = std::fminf(frame->width / (float)in_width, frame->height / (float)in_height); - auto out_width_f = in_width * scalar; - auto out_height_f = in_height * scalar; - - // result is always positive - auto offsetX_f = (frame->width - out_width_f) / 2; - auto offsetY_f = (frame->height - out_height_f) / 2; - - out_width = out_width_f; - out_height = out_height_f; - - offsetX = offsetX_f; - offsetY = offsetY_f; - - auto tex = tex_in[0]; - - gl::ctx.BindTexture(GL_TEXTURE_2D, tex); - gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, in_width, in_height); - - auto loc_width_i = gl::ctx.GetUniformLocation(program[1].handle(), "width_i"); - if(loc_width_i < 0) { - BOOST_LOG(error) << "Couldn't find uniform [width_i]"sv; - return -1; - } - - auto width_i = 1.0f / out_width; - gl::ctx.UseProgram(program[1].handle()); - gl::ctx.Uniform1fv(loc_width_i, 1, &width_i); - - gl_drain_errors; - return 0; + return egl::egl_t::_set_frame(frame); } - ~egl_t() override { - if(gl::ctx.GetError) { - gl_drain_errors; - } - } - - int in_width, in_height; - int out_width, out_height; - int offsetX, offsetY; - - frame_t hwframe; - va::display_t::pointer va_display; - - file_t file; - gbm_t gbm; - display_t display; - ctx_t ctx; - - gl::tex_t tex_in; - nv12_t nv12; - gl::program_t program[2]; - gl::buffer_t color_matrix; }; /** @@ -1019,8 +285,8 @@ int vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf return -1; } - auto egl = (platf::egl::egl_t *)base; - auto fd = dup(egl->file.el); + auto va = (va::va_t *)base; + auto fd = dup(va->file.el); auto *priv = (VAAPIDevicePriv *)av_mallocz(sizeof(VAAPIDevicePriv)); priv->drm_fd = fd; @@ -1039,7 +305,7 @@ int vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf return -1; } - egl->va_display = display.get(); + va->va_display = display.get(); va::setErrorCallback(display.get(), __log, &error); va::setErrorCallback(display.get(), __log, &info); @@ -1071,7 +337,7 @@ int vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf } std::shared_ptr make_hwdevice(int width, int height) { - auto egl = std::make_shared(); + auto egl = std::make_shared(); auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str(); if(egl->init(width, height, render_device)) { @@ -1080,8 +346,9 @@ std::shared_ptr make_hwdevice(int width, int height) { return egl; } -} // namespace egl +} // namespace va +namespace platf { std::unique_ptr init() { gbm::init(); va::init_drm(); diff --git a/sunshine/platform/linux/vaapi.h b/sunshine/platform/linux/vaapi.h index 10b1b3f1..f6f18389 100644 --- a/sunshine/platform/linux/vaapi.h +++ b/sunshine/platform/linux/vaapi.h @@ -2,7 +2,7 @@ #define SUNSHINE_DISPLAY_H #include "sunshine/platform/common.h" -namespace platf::egl { -std::shared_ptr make_hwdevice(int width, int height); -} // namespace platf::egl +namespace va { +std::shared_ptr make_hwdevice(int width, int height); +} // namespace va #endif \ No newline at end of file diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/x11grab.cpp similarity index 100% rename from sunshine/platform/linux/display.cpp rename to sunshine/platform/linux/x11grab.cpp From 065e9e718a448b9784a1dac28e1f06b3db4cd1aa Mon Sep 17 00:00:00 2001 From: loki Date: Fri, 6 Aug 2021 15:55:38 +0200 Subject: [PATCH 03/21] 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: From 9ed2141fc852a2e143bd671605771de6ebe89b42 Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 7 Aug 2021 14:39:18 +0200 Subject: [PATCH 04/21] Fix X11 screengrabbing with vaapi --- sunshine/platform/linux/graphics.cpp | 6 ------ sunshine/platform/linux/kmsgrab.cpp | 17 +++++++++++++---- sunshine/platform/linux/vaapi.cpp | 6 ++++++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/sunshine/platform/linux/graphics.cpp b/sunshine/platform/linux/graphics.cpp index 5093255c..bfc10b97 100644 --- a/sunshine/platform/linux/graphics.cpp +++ b/sunshine/platform/linux/graphics.cpp @@ -635,12 +635,6 @@ int egl_t::_set_frame(AVFrame *frame) { this->hwframe.reset(frame); this->frame = frame; - if(av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0)) { - BOOST_LOG(error) << "Couldn't get hwframe for VAAPI"sv; - - return -1; - } - // Ensure aspect ratio is maintained auto scalar = std::fminf(frame->width / (float)in_width, frame->height / (float)in_height); auto out_width_f = in_width * scalar; diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index b04f83f4..e4cc7127 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -11,6 +11,7 @@ #include "sunshine/utility.h" #include "graphics.h" +#include "vaapi.h" using namespace std::literals; @@ -154,7 +155,7 @@ void print(plane_t::pointer plane, fb_t::pointer fb) { class display_t : public platf::display_t { public: - display_t() : platf::display_t() {} + display_t(mem_type_e mem_type) : platf::display_t(), mem_type { mem_type } {} int init(const std::string &display_name, int framerate) { if(!gbm::create_device) { @@ -287,10 +288,16 @@ public: return capture_e::ok; } + std::shared_ptr make_hwdevice(pix_fmt_e pix_fmt) override { + if(mem_type == mem_type_e::vaapi) { + return va::make_hwdevice(width, height); + } + + return std::make_shared(); + } + capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) { - gl::ctx.BindTexture(GL_TEXTURE_2D, rgb->tex[0]); - gl::ctx.GetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->data); return capture_e::ok; @@ -311,6 +318,8 @@ public: return 0; } + mem_type_e mem_type; + std::chrono::nanoseconds delay; card_t card; @@ -325,7 +334,7 @@ public: } // namespace kms std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { - auto disp = std::make_shared(); + auto disp = std::make_shared(hwdevice_type); if(disp->init(display_name, framerate)) { return nullptr; diff --git a/sunshine/platform/linux/vaapi.cpp b/sunshine/platform/linux/vaapi.cpp index 6b1a123a..22031e44 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/sunshine/platform/linux/vaapi.cpp @@ -184,6 +184,12 @@ public: int set_frame(AVFrame *frame) override { // No deallocation necessary + if(av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0)) { + BOOST_LOG(error) << "Couldn't get hwframe for VAAPI"sv; + + return -1; + } + va::DRMPRIMESurfaceDescriptor prime; va::VASurfaceID surface = (std::uintptr_t)frame->data[3]; From 315ec475234cd347161a255614041db2e6153e1e Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 7 Aug 2021 21:31:25 +0200 Subject: [PATCH 05/21] Display single monitor only with kmsgrab --- sunshine/platform/linux/kmsgrab.cpp | 150 +++++++++++++++++++++++++--- 1 file changed, 136 insertions(+), 14 deletions(-) diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index e4cc7127..ee39c26e 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -22,6 +22,25 @@ using plane_res_t = util::safe_ptr; using plane_t = util::safe_ptr; using fb_t = util::safe_ptr; using fb2_t = util::safe_ptr; +using crtc_t = util::safe_ptr; +using obj_prop_t = util::safe_ptr; +using prop_t = util::safe_ptr; + +static int env_width; +static int env_height; + +std::string_view plane_type(std::uint64_t val) { + switch(val) { + case DRM_PLANE_TYPE_OVERLAY: + return "DRM_PLANE_TYPE_OVERLAY"sv; + case DRM_PLANE_TYPE_PRIMARY: + return "DRM_PLANE_TYPE_PRIMARY"sv; + case DRM_PLANE_TYPE_CURSOR: + return "DRM_PLANE_TYPE_CURSOR"sv; + } + + return "UNKNOWN"sv; +} class plane_it_t : public util::it_wrap_t { public: @@ -98,7 +117,53 @@ public: return drmModeGetFB(fd.el, plane->fb_id); } - plane_t operator[](std::uint32_t index) { + fb2_t fb2(plane_t::pointer plane) { + return drmModeGetFB2(fd.el, plane->fb_id); + } + + crtc_t crtc(std::uint32_t id) { + return drmModeGetCrtc(fd.el, id); + } + + egl::file_t handleFD(std::uint32_t handle) { + egl::file_t fb_fd; + + auto status = drmPrimeHandleToFD(fd.el, handle, 0 /* flags */, &fb_fd.el); + if(status) { + return {}; + } + + return fb_fd; + } + + + std::vector> props(std::uint32_t id, std::uint32_t type) { + obj_prop_t obj_prop = drmModeObjectGetProperties(fd.el, id, type); + + std::vector> props; + props.reserve(obj_prop->count_props); + + for(auto x = 0; x < obj_prop->count_props; ++x) { + props.emplace_back(drmModeGetProperty(fd.el, obj_prop->props[x]), obj_prop->prop_values[x]); + } + + return props; + } + + std::vector> plane_props(std::uint32_t id) { + return props(id, DRM_MODE_OBJECT_PLANE); + } + + std::vector> crtc_props(std::uint32_t id) { + return props(id, DRM_MODE_OBJECT_CRTC); + } + + std::vector> connector_props(std::uint32_t id) { + return props(id, DRM_MODE_OBJECT_CONNECTOR); + } + + plane_t + operator[](std::uint32_t index) { return drmModeGetPlane(fd.el, plane_res->planes[index]); } @@ -126,7 +191,13 @@ struct kms_img_t : public img_t { } }; -void print(plane_t::pointer plane, fb_t::pointer fb) { +void print(plane_t::pointer plane, fb_t::pointer fb, crtc_t::pointer crtc) { + if(crtc) { + BOOST_LOG(debug) << "crtc("sv << crtc->x << ", "sv << crtc->y << ')'; + BOOST_LOG(debug) << "crtc("sv << crtc->width << ", "sv << crtc->height << ')'; + BOOST_LOG(debug) << "plane->possible_crtcs == "sv << plane->possible_crtcs; + } + BOOST_LOG(debug) << "x("sv << plane->x << ") y("sv << plane->y @@ -194,20 +265,30 @@ public: return -1; } - auto status = drmPrimeHandleToFD(card.fd.el, fb->handle, 0 /* flags */, &fb_fd.el); - if(status || fb_fd.el < 0) { + fb_fd = card.handleFD(fb->handle); + if(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) << "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; + 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; + + pitch = fb->pitch; + + this->env_width = ::platf::kms::env_width; + this->env_height = ::platf::kms::env_height; + + offset_x = crct->x; + offset_y = crct->y; break; } @@ -239,8 +320,8 @@ public: auto rgb_opt = egl::import_source(display.get(), { fb_fd.el, - width, - height, + img_width, + img_height, 0, pitch, }); @@ -298,7 +379,7 @@ public: capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds timeout, bool cursor) { gl::ctx.BindTexture(GL_TEXTURE_2D, rgb->tex[0]); - gl::ctx.GetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->data); + 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); return capture_e::ok; } @@ -318,6 +399,7 @@ public: return 0; } + int img_width, img_height; mem_type_e mem_type; std::chrono::nanoseconds delay; @@ -357,6 +439,9 @@ std::vector kms_display_names() { return {}; } + kms::env_width = 0; + kms::env_height = 0; + int count = 0; auto end = std::end(card); @@ -373,7 +458,44 @@ std::vector kms_display_names() { break; } - kms::print(plane.get(), fb.get()); + { + BOOST_LOG(verbose) << "PLANE INFO"sv; + auto props = card.plane_props(card.plane_res->planes[count]); + for(auto &[prop, val] : props) { + if(prop->name == "type"sv) { + BOOST_LOG(verbose) << prop->name << "::"sv << kms::plane_type(val); + } + 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; + } + } + + // This appears to return the offset of the monitor + auto crtc = card.crtc(plane->crtc_id); + if(!crtc) { + BOOST_LOG(error) << "Couldn't get crtc info: "sv << strerror(errno); + return {}; + } + + 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)); + + auto fb_2 = card.fb2(plane.get()); + for(int x = 0; x < 4 && fb_2->handles[x]; ++x) { + BOOST_LOG(debug) << "handles::"sv << x << '(' << fb_2->handles[x] << ')'; + BOOST_LOG(debug) << "pixel_format::"sv << util::view(fb_2->pixel_format); + } + + kms::print(plane.get(), fb.get(), crtc.get()); display_names.emplace_back(std::to_string(count++)); } From 13d0106feb1ae67e95a8458c15d571c95bf78542 Mon Sep 17 00:00:00 2001 From: loki Date: Sun, 8 Aug 2021 13:41:09 +0200 Subject: [PATCH 06/21] Don't shutdown stream if audio capture fails --- sunshine/audio.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/sunshine/audio.cpp b/sunshine/audio.cpp index cb4fac28..4279eb4c 100644 --- a/sunshine/audio.cpp +++ b/sunshine/audio.cpp @@ -130,7 +130,7 @@ void capture(safe::mail_t mail, config_t config, void *channel_data) { auto &control = ref->control; if(!control) { - BOOST_LOG(error) << "Couldn't create audio control"sv; + shutdown_event->view(); return; } @@ -223,21 +223,29 @@ int map_stream(int channels, bool quality) { } int start_audio_control(audio_ctx_t &ctx) { + auto fg = util::fail_guard([]() { + BOOST_LOG(warning) << "There will be no audio"sv; + }); + ctx.sink_flag = std::make_unique(false); - if(!(ctx.control = platf::audio_control())) { - return -1; - } - - auto sink = ctx.control->sink_info(); - if(!sink) { - return -1; - } - // The default sink has not been replaced yet. ctx.restore_sink = false; + if(!(ctx.control = platf::audio_control())) { + return 0; + } + + auto sink = ctx.control->sink_info(); + if(!sink) { + // Let the calling code know it failed + ctx.control.reset(); + return 0; + } + ctx.sink = std::move(*sink); + + fg.disable(); return 0; } From 24403cdd25680212c4d4d0a65bf66ab4eb5cc0f8 Mon Sep 17 00:00:00 2001 From: loki Date: Sun, 8 Aug 2021 13:42:25 +0200 Subject: [PATCH 07/21] Fix segfault when switching monitors with kmsgrab --- sunshine/platform/linux/graphics.cpp | 4 ---- sunshine/platform/linux/graphics.h | 8 +++++--- sunshine/platform/linux/kmsgrab.cpp | 1 + sunshine/video.cpp | 13 ++++++++----- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/sunshine/platform/linux/graphics.cpp b/sunshine/platform/linux/graphics.cpp index bfc10b97..63f7a483 100644 --- a/sunshine/platform/linux/graphics.cpp +++ b/sunshine/platform/linux/graphics.cpp @@ -22,10 +22,6 @@ extern "C" { #define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/opengl" -#define STRINGIFY(x) #x -#define gl_drain_errors_helper(x) gl::drain_errors("line " STRINGIFY(x)) -#define gl_drain_errors gl_drain_errors_helper(__LINE__) - using namespace std::literals; namespace gl { GladGLContext ctx; diff --git a/sunshine/platform/linux/graphics.h b/sunshine/platform/linux/graphics.h index f0f05883..8baa0df1 100644 --- a/sunshine/platform/linux/graphics.h +++ b/sunshine/platform/linux/graphics.h @@ -12,6 +12,10 @@ #include "sunshine/platform/common.h" #include "sunshine/utility.h" +#define SUNSHINE_STRINGIFY(x) #x +#define gl_drain_errors_helper(x) gl::drain_errors("line " SUNSHINE_STRINGIFY(x)) +#define gl_drain_errors gl_drain_errors_helper(__LINE__) + extern "C" int close(int __fd); struct AVFrame; @@ -197,9 +201,7 @@ KITTY_USING_MOVE_T(nv12_t, nv12_img_t, , { KITTY_USING_MOVE_T(ctx_t, (std::tuple), , { TUPLE_2D_REF(disp, ctx, el); if(ctx) { - if(ctx == eglGetCurrentContext()) { - eglMakeCurrent(disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } + eglMakeCurrent(disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(disp, ctx); } }); diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index ee39c26e..990e11c1 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -396,6 +396,7 @@ public: } int dummy_img(platf::img_t *img) override { + snapshot(img, 1s, false); return 0; } diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 5ba90323..6fbc72be 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -675,11 +675,10 @@ void captureThread( img.reset(); } - // Some classes of display cannot have multiple instances at once - disp.reset(); - // display_wp is modified in this thread only - while(!display_wp->expired()) { + // Wait for the other shared_ptr's of display to be destroyed. + // New displays will only be created in this thread. + while(display_wp->use_count() != 1) { std::this_thread::sleep_for(100ms); } @@ -695,7 +694,11 @@ void captureThread( return; } - display_wp = disp; + { + auto lg = display_wp.lock(); + display_wp = disp; + } + // Re-allocate images for(auto &img : imgs) { img = disp->alloc_img(); From b8bfc13cf9a184125700ebaee5f96498d6973b57 Mon Sep 17 00:00:00 2001 From: Loki Date: Sun, 8 Aug 2021 13:59:43 +0200 Subject: [PATCH 08/21] Prevent segfault on empty string_view --- sunshine/utility.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sunshine/utility.h b/sunshine/utility.h index be63f475..af2da53e 100644 --- a/sunshine/utility.h +++ b/sunshine/utility.h @@ -389,6 +389,10 @@ auto enm(T &val) -> std::underlying_type_t & { } inline std::int64_t from_chars(const char *begin, const char *end) { + if(begin == end) { + return 0; + } + std::int64_t res {}; std::int64_t mul = 1; while(begin != --end) { From 6721155155f1927ec4b04dbdbecd7d256b21d799 Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 12 Aug 2021 21:11:40 +0200 Subject: [PATCH 09/21] Omit copy to RAM when possible with VAAPI --- CMakeLists.txt | 7 +- sunshine/platform/linux/graphics.cpp | 150 ++++++++++-------------- sunshine/platform/linux/graphics.h | 41 ++----- sunshine/platform/linux/kmsgrab.cpp | 135 +++++++++++++++++++--- sunshine/platform/linux/misc.h | 10 ++ sunshine/platform/linux/vaapi.cpp | 167 ++++++++++++++++++++++++--- sunshine/platform/linux/vaapi.h | 10 +- 7 files changed, 361 insertions(+), 159 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0e36791..5550e991 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,10 +106,13 @@ else() add_compile_definitions(SUNSHINE_PLATFORM="linux") list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") - if(NOT DEFINED SUNSHINE_DISABLE_X11) + option(SUNSHINE_ENABLE_DRM "Enable KMS grab if available" ON) + option(SUNSHINE_ENABLE_X11 "Enable X11 grab if available" ON) + + if(${SUNSHINE_ENABLE_X11}) find_package(X11) endif() - if(NOT DEFINED SUNSHINE_DISABLE_DRM) + if(${SUNSHINE_ENABLE_DRM}) find_package(LIBDRM) endif() diff --git a/sunshine/platform/linux/graphics.cpp b/sunshine/platform/linux/graphics.cpp index 63f7a483..2f9e157f 100644 --- a/sunshine/platform/linux/graphics.cpp +++ b/sunshine/platform/linux/graphics.cpp @@ -1,10 +1,6 @@ #include "graphics.h" #include "sunshine/video.h" -extern "C" { -#include -} - #include // I want to have as little build dependencies as possible @@ -460,7 +456,7 @@ std::optional import_target(display_t::pointer egl_display, std::array sws_t::make(int in_width, int in_height, int out_width, int out_heigth, gl::tex_t &&tex) { + sws_t sws; - if(!gbm::create_device) { - BOOST_LOG(warning) << "libgbm not initialized"sv; - return -1; - } + // Ensure aspect ratio is maintained + auto scalar = std::fminf(out_width / (float)in_width, out_heigth / (float)in_height); + auto out_width_f = in_width * scalar; + auto out_height_f = in_height * scalar; - gbm.reset(gbm::create_device(file.el)); - if(!gbm) { - BOOST_LOG(error) << "Couldn't create GBM device: ["sv << util::hex(eglGetError()).to_string_view() << ']'; - return -1; - } + // result is always positive + auto offsetX_f = (out_width - out_width_f) / 2; + auto offsetY_f = (out_heigth - out_height_f) / 2; - display = make_display(gbm.get()); - if(!display) { - return -1; - } + sws.width = out_width_f; + sws.height = out_height_f; - auto ctx_opt = make_ctx(display.get()); - if(!ctx_opt) { - return -1; - } + sws.offsetX = offsetX_f; + sws.offsetY = offsetY_f; - ctx = std::move(*ctx_opt); + auto width_i = 1.0f / sws.width; { const char *sources[] { @@ -549,28 +539,37 @@ int egl_t::init(int in_width, int in_height, file_t &&fd) { } if(error_flag) { - return -1; + return std::nullopt; } auto program = gl::program_t::link(compiled_sources[1].left(), compiled_sources[0].left()); if(program.has_right()) { BOOST_LOG(error) << "GL linker: "sv << program.right(); - return -1; + return std::nullopt; } // UV - shader - this->program[1] = std::move(program.left()); + sws.program[1] = std::move(program.left()); program = gl::program_t::link(compiled_sources[3].left(), compiled_sources[2].left()); if(program.has_right()) { BOOST_LOG(error) << "GL linker: "sv << program.right(); - return -1; + return std::nullopt; } // Y - shader - this->program[0] = std::move(program.left()); + sws.program[0] = std::move(program.left()); } + auto loc_width_i = gl::ctx.GetUniformLocation(sws.program[1].handle(), "width_i"); + if(loc_width_i < 0) { + BOOST_LOG(error) << "Couldn't find uniform [width_i]"sv; + return std::nullopt; + } + + gl::ctx.UseProgram(sws.program[1].handle()); + gl::ctx.Uniform1fv(loc_width_i, 1, &width_i); + auto color_p = &video::colors[0]; std::pair members[] { std::make_pair("color_vec_y", util::view(color_p->color_vec_y)), @@ -580,25 +579,46 @@ int egl_t::init(int in_width, int in_height, file_t &&fd) { std::make_pair("range_uv", util::view(color_p->range_uv)), }; - auto color_matrix = program[0].uniform("ColorMatrix", members, sizeof(members) / sizeof(decltype(members[0]))); + auto color_matrix = sws.program[0].uniform("ColorMatrix", members, sizeof(members) / sizeof(decltype(members[0]))); if(!color_matrix) { - return -1; + return std::nullopt; } - this->color_matrix = std::move(*color_matrix); + sws.color_matrix = std::move(*color_matrix); - tex_in = gl::tex_t::make(1); + sws.tex = std::move(tex); - this->in_width = in_width; - this->in_height = in_height; - return 0; + gl_drain_errors; + + return std::move(sws); } -int egl_t::convert(platf::img_t &img) { - auto tex = tex_in[0]; +std::optional sws_t::make(int in_width, int in_height, int out_width, int out_heigth) { + auto tex = gl::tex_t::make(1); + gl::ctx.BindTexture(GL_TEXTURE_2D, tex[0]); + gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, in_width, in_height); - gl::ctx.BindTexture(GL_TEXTURE_2D, tex); - gl::ctx.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, in_width, in_height, GL_BGRA, GL_UNSIGNED_BYTE, img.data); + return make(in_width, in_height, out_width, out_heigth, std::move(tex)); +} + +void sws_t::load_ram(platf::img_t &img) { + gl::ctx.BindTexture(GL_TEXTURE_2D, tex[0]); + gl::ctx.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, img.width, img.height, GL_BGRA, GL_UNSIGNED_BYTE, img.data); +} + +void sws_t::load_vram(platf::img_t &img, int offset_x, int offset_y, int framebuffer) { + gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, framebuffer); + gl::ctx.ReadBuffer(GL_COLOR_ATTACHMENT0); + gl::ctx.BindTexture(GL_TEXTURE_2D, tex[0]); + gl::ctx.CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, offset_x, offset_y, img.width, img.height); + + gl::ctx.Flush(); +} + +int sws_t::convert(nv12_t &nv12) { + auto texture = tex[0]; + + gl::ctx.BindTexture(GL_TEXTURE_2D, texture); GLenum attachments[] { GL_COLOR_ATTACHMENT0, @@ -615,61 +635,17 @@ int egl_t::convert(platf::img_t &img) { return -1; } - gl::ctx.BindTexture(GL_TEXTURE_2D, tex); + gl::ctx.BindTexture(GL_TEXTURE_2D, texture); gl::ctx.UseProgram(program[x].handle()); program[x].bind(color_matrix); - gl::ctx.Viewport(offsetX / (x + 1), offsetY / (x + 1), out_width / (x + 1), out_height / (x + 1)); + gl::ctx.Viewport(offsetX / (x + 1), offsetY / (x + 1), width / (x + 1), height / (x + 1)); gl::ctx.DrawArrays(GL_TRIANGLES, 0, 3); } return 0; } - -int egl_t::_set_frame(AVFrame *frame) { - this->hwframe.reset(frame); - this->frame = frame; - - // Ensure aspect ratio is maintained - auto scalar = std::fminf(frame->width / (float)in_width, frame->height / (float)in_height); - auto out_width_f = in_width * scalar; - auto out_height_f = in_height * scalar; - - // result is always positive - auto offsetX_f = (frame->width - out_width_f) / 2; - auto offsetY_f = (frame->height - out_height_f) / 2; - - out_width = out_width_f; - out_height = out_height_f; - - offsetX = offsetX_f; - offsetY = offsetY_f; - - auto tex = tex_in[0]; - - gl::ctx.BindTexture(GL_TEXTURE_2D, tex); - gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, in_width, in_height); - - auto loc_width_i = gl::ctx.GetUniformLocation(program[1].handle(), "width_i"); - if(loc_width_i < 0) { - BOOST_LOG(error) << "Couldn't find uniform [width_i]"sv; - return -1; - } - - auto width_i = 1.0f / out_width; - gl::ctx.UseProgram(program[1].handle()); - gl::ctx.Uniform1fv(loc_width_i, 1, &width_i); - - gl_drain_errors; - return 0; -} - -egl_t::~egl_t() { - if(gl::ctx.GetError) { - gl_drain_errors; - } -} } // namespace egl void free_frame(AVFrame *frame) { diff --git a/sunshine/platform/linux/graphics.h b/sunshine/platform/linux/graphics.h index 8baa0df1..74c9ef49 100644 --- a/sunshine/platform/linux/graphics.h +++ b/sunshine/platform/linux/graphics.h @@ -155,12 +155,6 @@ int init(); namespace egl { using display_t = util::dyn_safe_ptr_v2; -KITTY_USING_MOVE_T(file_t, int, -1, { - if(el >= 0) { - close(el); - } -}); - struct rgb_img_t { display_t::pointer display; EGLImage xrgb8; @@ -227,37 +221,24 @@ std::optional import_target( std::array &&fds, const surface_descriptor_t &r8, const surface_descriptor_t &gr88); -class egl_t : public platf::hwdevice_t { +class sws_t { public: - void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override; + static std::optional make(int in_width, int in_height, int out_width, int out_heigth, gl::tex_t &&tex); + static std::optional make(int in_width, int in_height, int out_width, int out_heigth); - int init(int in_width, int in_height, file_t &&fd); + int convert(nv12_t &nv12); - int convert(platf::img_t &img) override; + void load_ram(platf::img_t &img); + void load_vram(platf::img_t &img, int offset_x, int offset_y, int framebuffer); - /** - * Any specialization needs to populate nv12_t nv12 - * Then call this function - */ - int _set_frame(AVFrame *frame); + void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range); - ~egl_t() override; - - int in_width, in_height; - int out_width, out_height; - int offsetX, offsetY; - - frame_t hwframe; - - file_t file; - gbm::gbm_t gbm; - display_t display; - ctx_t ctx; - - gl::tex_t tex_in; - nv12_t nv12; + gl::tex_t tex; gl::program_t program[2]; gl::buffer_t color_matrix; + + int width, height; + int offsetX, offsetY; }; bool fail(); diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index 990e11c1..42d0f5df 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -125,8 +125,8 @@ public: return drmModeGetCrtc(fd.el, id); } - egl::file_t handleFD(std::uint32_t handle) { - egl::file_t fb_fd; + file_t handleFD(std::uint32_t handle) { + file_t fb_fd; auto status = drmPrimeHandleToFD(fd.el, handle, 0 /* flags */, &fb_fd.el); if(status) { @@ -180,7 +180,7 @@ public: } - egl::file_t fd; + file_t fd; plane_res_t plane_res; }; @@ -228,12 +228,11 @@ class display_t : public platf::display_t { public: display_t(mem_type_e mem_type) : platf::display_t(), mem_type { mem_type } {} - int init(const std::string &display_name, int framerate) { - if(!gbm::create_device) { - BOOST_LOG(warning) << "libgbm not initialized"sv; - return -1; - } + mem_type_e mem_type; + std::chrono::nanoseconds delay; + + int init(const std::string &display_name, int framerate) { delay = std::chrono::nanoseconds { 1s } / framerate; constexpr auto path = "/dev/dri/card1"; @@ -244,8 +243,6 @@ public: int monitor_index = util::from_view(display_name); int monitor = 0; - int pitch; - auto end = std::end(card); for(auto plane = std::begin(card); plane != end; ++plane) { if(monitor != monitor_index) { @@ -299,6 +296,30 @@ public: return -1; } + return 0; + } + + int img_width, img_height; + int pitch; + + card_t card; + file_t fb_fd; +}; + +class display_ram_t : public display_t { +public: + display_ram_t(mem_type_e mem_type) : display_t(mem_type) {} + + int init(const std::string &display_name, int framerate) { + if(!gbm::create_device) { + BOOST_LOG(warning) << "libgbm not initialized"sv; + return -1; + } + + if(display_t::init(display_name, framerate)) { + 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() << ']'; @@ -400,24 +421,100 @@ public: return 0; } - int img_width, img_height; - mem_type_e mem_type; - - std::chrono::nanoseconds delay; - - card_t card; - egl::file_t fb_fd; - gbm::gbm_t gbm; egl::display_t display; egl::ctx_t ctx; egl::rgb_t rgb; }; + +class display_vram_t : public display_t { +public: + display_vram_t(mem_type_e mem_type) : display_t(mem_type) {} + + 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, + { + fb_fd.el, + img_width, + img_height, + 0, + pitch, + }); + } + + BOOST_LOG(error) << "Unsupported pixel format for egl::display_vram_t: "sv << platf::from_pix_fmt(pix_fmt); + return nullptr; + } + + std::shared_ptr alloc_img() override { + auto img = std::make_shared(); + + img->width = width; + img->height = height; + img->pixel_pitch = 4; + img->row_pitch = img->pixel_pitch * width; + + return img; + } + + int dummy_img(platf::img_t *img) override { + return 0; + } + + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) { + auto next_frame = std::chrono::steady_clock::now(); + + while(img) { + auto now = std::chrono::steady_clock::now(); + + if(next_frame > now) { + std::this_thread::sleep_for((next_frame - now) / 3 * 2); + } + while(next_frame > now) { + now = std::chrono::steady_clock::now(); + } + next_frame = now + delay; + + auto status = snapshot(img.get(), 1000ms, *cursor); + switch(status) { + case platf::capture_e::reinit: + case platf::capture_e::error: + return status; + case platf::capture_e::timeout: + std::this_thread::sleep_for(1ms); + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return capture_e::ok; + } + + capture_e snapshot(img_t * /*img_out_base */, std::chrono::milliseconds /* timeout */, bool /* cursor */) { + return capture_e::ok; + } +}; } // namespace kms std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { - auto disp = std::make_shared(hwdevice_type); + if(hwdevice_type == mem_type_e::vaapi) { + auto disp = std::make_shared(hwdevice_type); + + if(!disp->init(display_name, framerate)) { + return disp; + } + + // In the case of failure, attempt the old method for VAAPI + } + + auto disp = std::make_shared(hwdevice_type); if(disp->init(display_name, framerate)) { return nullptr; diff --git a/sunshine/platform/linux/misc.h b/sunshine/platform/linux/misc.h index d25fc031..cdefff64 100644 --- a/sunshine/platform/linux/misc.h +++ b/sunshine/platform/linux/misc.h @@ -1,7 +1,17 @@ #ifndef SUNSHINE_PLATFORM_MISC_H #define SUNSHINE_PLATFORM_MISC_H +#include #include + +#include "sunshine/utility.h" + +KITTY_USING_MOVE_T(file_t, int, -1, { + if(el >= 0) { + close(el); + } +}); + namespace dyn { typedef void (*apiproc)(void); diff --git a/sunshine/platform/linux/vaapi.cpp b/sunshine/platform/linux/vaapi.cpp index 22031e44..d7961705 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/sunshine/platform/linux/vaapi.cpp @@ -160,29 +160,47 @@ int init() { int vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf); -class va_t : public egl::egl_t { +class va_t : public platf::hwdevice_t { public: - int init(int in_width, int in_height, const char *render_device) { - if(!va::initialize) { - BOOST_LOG(warning) << "libva not initialized"sv; + int init(int in_width, int in_height, file_t &&render_device) { + file = std::move(render_device); + + if(!va::initialize || !gbm::create_device) { + if(!va::initialize) BOOST_LOG(warning) << "libva not initialized"sv; + if(!gbm::create_device) BOOST_LOG(warning) << "libgbm not initialized"sv; return -1; } - data = (void *)vaapi_make_hwdevice_ctx; + this->data = (void *)vaapi_make_hwdevice_ctx; - egl::file_t fd = open(render_device, O_RDWR); - if(fd.el < 0) { + gbm.reset(gbm::create_device(file.el)); + if(!gbm) { char string[1024]; - BOOST_LOG(error) << "Couldn't open "sv << render_device << ": " << strerror_r(errno, string, sizeof(string)); - + BOOST_LOG(error) << "Couldn't create GBM device: ["sv << strerror_r(errno, string, sizeof(string)) << ']'; return -1; } - return egl::egl_t::init(in_width, in_height, std::move(fd)); + display = egl::make_display(gbm.get()); + if(!display) { + return -1; + } + + auto ctx_opt = egl::make_ctx(display.get()); + if(!ctx_opt) { + return -1; + } + + ctx = std::move(*ctx_opt); + + width = in_width; + height = in_height; + + return 0; } - int set_frame(AVFrame *frame) override { - // No deallocation necessary + int _set_frame(AVFrame *frame) { + this->hwframe.reset(frame); + this->frame = frame; if(av_hwframe_get_buffer(frame->hw_frames_ctx, frame, 0)) { BOOST_LOG(error) << "Couldn't get hwframe for VAAPI"sv; @@ -194,7 +212,7 @@ public: va::VASurfaceID surface = (std::uintptr_t)frame->data[3]; auto status = va::exportSurfaceHandle( - va_display, + this->va_display, surface, va::SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, va::EXPORT_SURFACE_WRITE_ONLY | va::EXPORT_SURFACE_COMPOSED_LAYERS, @@ -207,7 +225,7 @@ public: } // Keep track of file descriptors - std::array fds; + std::array fds; for(int x = 0; x < prime.num_objects; ++x) { fds[x] = prime.objects[x].fd; } @@ -234,12 +252,106 @@ public: return -1; } - nv12 = std::move(*nv12_opt); + this->nv12 = std::move(*nv12_opt); - return egl::egl_t::_set_frame(frame); + return 0; + } + + void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { + sws.set_colorspace(colorspace, color_range); } va::display_t::pointer va_display; + file_t file; + + frame_t hwframe; + + gbm::gbm_t gbm; + egl::display_t display; + egl::ctx_t ctx; + + egl::sws_t sws; + egl::nv12_t nv12; + + int width, height; +}; + +class va_ram_t : public va_t { +public: + int convert(platf::img_t &img) override { + sws.load_ram(img); + + sws.convert(nv12); + return 0; + } + + int set_frame(AVFrame *frame) override { + if(_set_frame(frame)) { + return -1; + } + + auto sws_opt = egl::sws_t::make(width, height, frame->width, frame->height); + if(!sws_opt) { + return -1; + } + + this->sws = std::move(*sws_opt); + + return 0; + } +}; + +class va_vram_t : public va_t { +public: + int convert(platf::img_t &img) override { + sws.load_vram(img, offset_x, offset_y, framebuffer[0]); + + sws.convert(nv12); + return 0; + } + + int init(int in_width, int in_height, file_t &&render_device, int offset_x, int offset_y, const egl::surface_descriptor_t &sd) { + if(va_t::init(in_width, in_height, std::move(render_device))) { + return -1; + } + + auto rgb_opt = egl::import_source(display.get(), sd); + if(!rgb_opt) { + return -1; + } + + rgb = std::move(*rgb_opt); + + framebuffer = gl::frame_buf_t::make(1); + framebuffer.bind(std::begin(rgb->tex), std::end(rgb->tex)); + + this->offset_x = offset_x; + this->offset_y = offset_y; + + return 0; + } + + int set_frame(AVFrame *frame) override { + if(_set_frame(frame)) { + return -1; + } + + auto sws_opt = egl::sws_t::make(width, height, frame->width, frame->height); + if(!sws_opt) { + return -1; + } + + this->sws = std::move(*sws_opt); + + return 0; + } + + file_t fb_fd; + + egl::rgb_t rgb; + gl::frame_buf_t framebuffer; + + int offset_x, offset_y; }; /** @@ -343,10 +455,27 @@ int vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf } std::shared_ptr make_hwdevice(int width, int height) { - auto egl = std::make_shared(); - auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str(); - if(egl->init(width, height, render_device)) { + + file_t file = open(render_device, O_RDWR); + if(file.el < 0) { + char string[1024]; + BOOST_LOG(error) << "Couldn't open "sv << render_device << ": " << strerror_r(errno, string, sizeof(string)); + + return nullptr; + } + + auto egl = std::make_shared(); + if(egl->init(width, height, std::move(file))) { + return nullptr; + } + + return egl; +} + +std::shared_ptr make_hwdevice(int width, int height, file_t &&card, int offset_x, int offset_y, const egl::surface_descriptor_t &sd) { + auto egl = std::make_shared(); + if(egl->init(width, height, std::move(card), offset_x, offset_y, sd)) { return nullptr; } diff --git a/sunshine/platform/linux/vaapi.h b/sunshine/platform/linux/vaapi.h index 1299fdc9..5025ca2c 100644 --- a/sunshine/platform/linux/vaapi.h +++ b/sunshine/platform/linux/vaapi.h @@ -1,9 +1,15 @@ -#ifndef SUNSHINE_DISPLAY_H -#define SUNSHINE_DISPLAY_H +#ifndef SUNSHINE_VAAPI_H +#define SUNSHINE_VAAPI_H +#include "misc.h" #include "sunshine/platform/common.h" + +namespace egl { +struct surface_descriptor_t; +} namespace va { std::shared_ptr make_hwdevice(int width, int height); +std::shared_ptr make_hwdevice(int width, int height, file_t &&card, int offset_x, int offset_y, const egl::surface_descriptor_t &sd); int init(); } // namespace va From e007ee9976968d8e7654e9a126df2f0599ef33b6 Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 12 Aug 2021 22:07:00 +0200 Subject: [PATCH 10/21] Handle monitors in different GPU's --- sunshine/platform/linux/kmsgrab.cpp | 224 ++++++++++++++++------------ 1 file changed, 125 insertions(+), 99 deletions(-) diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index 42d0f5df..4180c815 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "sunshine/main.h" #include "sunshine/platform/common.h" #include "sunshine/round_robin.h" @@ -14,6 +16,7 @@ #include "vaapi.h" using namespace std::literals; +namespace fs = std::filesystem; namespace platf { @@ -235,61 +238,74 @@ public: int init(const std::string &display_name, int framerate) { delay = std::chrono::nanoseconds { 1s } / framerate; - constexpr auto path = "/dev/dri/card1"; - if(card.init(path)) { - return -1; - } - int monitor_index = util::from_view(display_name); int monitor = 0; - auto end = std::end(card); - for(auto plane = std::begin(card); plane != end; ++plane) { - if(monitor != monitor_index) { - ++monitor; + fs::path card_dir { "/dev/dri"sv }; + for(auto &entry : fs::directory_iterator { card_dir }) { + auto file = entry.path().filename(); + + auto filestring = file.generic_u8string(); + if(std::string_view { filestring }.substr(0, 4) != "card"sv) { continue; } - 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); - return -1; + kms::card_t card; + if(card.init(entry.path().c_str())) { + return {}; } - 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; - return -1; + auto end = std::end(card); + for(auto plane = std::begin(card); plane != end; ++plane) { + if(monitor != monitor_index) { + ++monitor; + continue; + } + + 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); + return -1; + } + + 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; + return -1; + } + + fb_fd = card.handleFD(fb->handle); + if(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) << "Found monitor for DRM screencasting"sv; + + 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; + + pitch = fb->pitch; + + this->env_width = ::platf::kms::env_width; + this->env_height = ::platf::kms::env_height; + + offset_x = crct->x; + offset_y = crct->y; + + this->card = std::move(card); + goto break_loop; } - - fb_fd = card.handleFD(fb->handle); - if(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) << "Found monitor for DRM screencasting"sv; - - 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; - - pitch = fb->pitch; - - this->env_width = ::platf::kms::env_width; - this->env_height = ::platf::kms::env_height; - - offset_x = crct->x; - offset_y = crct->y; - - break; } + // Neatly break from nested for loop + break_loop: if(monitor != monitor_index) { BOOST_LOG(error) << "Couldn't find monitor ["sv << monitor_index << ']'; @@ -525,6 +541,11 @@ std::shared_ptr kms_display(mem_type_e hwdevice_type, const std::stri // 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) { BOOST_LOG(warning) << "libgbm not initialized"sv; return {}; @@ -532,70 +553,75 @@ std::vector kms_display_names() { std::vector display_names; - kms::card_t card; - if(card.init("/dev/dri/card1")) { - return {}; - } + fs::path card_dir { "/dev/dri"sv }; + for(auto &entry : fs::directory_iterator { card_dir }) { + auto file = entry.path().filename(); - kms::env_width = 0; - kms::env_height = 0; - - 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); + auto filestring = file.generic_u8string(); + if(std::string_view { filestring }.substr(0, 4) != "card"sv) { 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; - } - - { - BOOST_LOG(verbose) << "PLANE INFO"sv; - auto props = card.plane_props(card.plane_res->planes[count]); - for(auto &[prop, val] : props) { - if(prop->name == "type"sv) { - BOOST_LOG(verbose) << prop->name << "::"sv << kms::plane_type(val); - } - 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; - } - } - - // This appears to return the offset of the monitor - auto crtc = card.crtc(plane->crtc_id); - if(!crtc) { - BOOST_LOG(error) << "Couldn't get crtc info: "sv << strerror(errno); + kms::card_t card; + if(card.init(entry.path().c_str())) { return {}; } - 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)); + 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; + } - auto fb_2 = card.fb2(plane.get()); - for(int x = 0; x < 4 && fb_2->handles[x]; ++x) { - BOOST_LOG(debug) << "handles::"sv << x << '(' << fb_2->handles[x] << ')'; - BOOST_LOG(debug) << "pixel_format::"sv << util::view(fb_2->pixel_format); + 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; + } + + { + BOOST_LOG(verbose) << "PLANE INFO"sv; + auto props = card.plane_props(card.plane_res->planes[count]); + for(auto &[prop, val] : props) { + if(prop->name == "type"sv) { + BOOST_LOG(verbose) << prop->name << "::"sv << kms::plane_type(val); + } + 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; + } + } + + // This appears to return the offset of the monitor + auto crtc = card.crtc(plane->crtc_id); + if(!crtc) { + BOOST_LOG(error) << "Couldn't get crtc info: "sv << strerror(errno); + return {}; + } + + 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)); + + auto fb_2 = card.fb2(plane.get()); + for(int x = 0; x < 4 && fb_2->handles[x]; ++x) { + BOOST_LOG(debug) << "handles::"sv << x << '(' << fb_2->handles[x] << ')'; + BOOST_LOG(debug) << "pixel_format::"sv << util::view(fb_2->pixel_format); + } + + kms::print(plane.get(), fb.get(), crtc.get()); + + display_names.emplace_back(std::to_string(count++)); } - - kms::print(plane.get(), fb.get(), crtc.get()); - - display_names.emplace_back(std::to_string(count++)); } return display_names; From 898d62bad9f43b7437e357eb92aa163f0850d193 Mon Sep 17 00:00:00 2001 From: loki Date: Fri, 13 Aug 2021 16:09:05 +0200 Subject: [PATCH 11/21] Filter out cursors from drm planes --- sunshine/platform/linux/kmsgrab.cpp | 37 +++++++++++++++++++++++++---- sunshine/platform/linux/x11grab.cpp | 16 ------------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index 4180c815..8848d862 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -103,10 +103,14 @@ public: } if(drmSetClientCap(fd.el, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { - BOOST_LOG(error) << "Couldn't expose some/all drm planes"sv; + BOOST_LOG(error) << "Couldn't expose some/all drm planes for card: "sv << path; return -1; } + if(drmSetClientCap(fd.el, DRM_CLIENT_CAP_ATOMIC, 1)) { + BOOST_LOG(warning) << "Couldn't expose some properties for card: "sv << path; + } + plane_res.reset(drmModeGetPlaneResources(fd.el)); if(!plane_res) { BOOST_LOG(error) << "Couldn't get drm plane resources"sv; @@ -257,6 +261,24 @@ public: auto end = std::end(card); for(auto plane = std::begin(card); plane != end; ++plane) { + bool cursor; + 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) { + continue; + } + if(monitor != monitor_index) { ++monitor; continue; @@ -581,12 +603,17 @@ std::vector kms_display_names() { break; } + bool cursor = false; { - BOOST_LOG(verbose) << "PLANE INFO"sv; - auto props = card.plane_props(card.plane_res->planes[count]); + 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; @@ -620,7 +647,9 @@ std::vector kms_display_names() { kms::print(plane.get(), fb.get(), crtc.get()); - display_names.emplace_back(std::to_string(count++)); + if(!cursor) { + display_names.emplace_back(std::to_string(count++)); + } } } diff --git a/sunshine/platform/linux/x11grab.cpp b/sunshine/platform/linux/x11grab.cpp index a8f9eb34..d10cfa88 100644 --- a/sunshine/platform/linux/x11grab.cpp +++ b/sunshine/platform/linux/x11grab.cpp @@ -57,22 +57,6 @@ _FN(Free, int, (void *data)); _FN(InitThreads, Status, (void)); namespace rr { -using Mode = XID; -using Output = XID; - -struct CrtcInfo { - Time timestamp; - int x, y; - unsigned int width, height; - Mode mode; - Rotation rotation; - int noutput; - Output *outputs; - Rotation rotations; - int npossible; - Output *possible; -}; - _FN(GetScreenResources, XRRScreenResources *, (Display * dpy, Window window)); _FN(GetOutputInfo, XRROutputInfo *, (Display * dpy, XRRScreenResources *resources, RROutput output)); _FN(GetCrtcInfo, XRRCrtcInfo *, (Display * dpy, XRRScreenResources *resources, RRCrtc crtc)); From fdb7754043282dbe26f6f8d58d7f1add30f97bdb Mon Sep 17 00:00:00 2001 From: loki Date: Sun, 15 Aug 2021 20:38:30 +0200 Subject: [PATCH 12/21] Attempt to render cursor when X11 is available --- CMakeLists.txt | 1 + sunshine/platform/common.h | 4 -- sunshine/platform/linux/graphics.cpp | 86 ++++++++++++++++++++++------ sunshine/platform/linux/graphics.h | 27 ++++++++- sunshine/platform/linux/kmsgrab.cpp | 35 +++++++++-- sunshine/platform/linux/vaapi.cpp | 40 +++---------- sunshine/platform/linux/x11grab.cpp | 77 +++++++++++++++++++++++-- sunshine/platform/linux/x11grab.h | 48 ++++++++++++++++ 8 files changed, 251 insertions(+), 67 deletions(-) create mode 100644 sunshine/platform/linux/x11grab.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f4f17e47..45d4bf74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,6 +140,7 @@ else() sunshine/platform/linux/misc.cpp sunshine/platform/linux/audio.cpp sunshine/platform/linux/input.cpp + sunshine/platform/linux/x11grab.h third-party/glad/src/egl.c third-party/glad/src/gl.c third-party/glad/include/EGL/eglplatform.h diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 1f6b8498..4fe87b7c 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -147,10 +147,6 @@ public: std::int32_t pixel_pitch {}; std::int32_t row_pitch {}; - img_t() = default; - img_t(const img_t &) = delete; - img_t(img_t &&) = delete; - virtual ~img_t() = default; }; diff --git a/sunshine/platform/linux/graphics.cpp b/sunshine/platform/linux/graphics.cpp index 2f9e157f..2799e4af 100644 --- a/sunshine/platform/linux/graphics.cpp +++ b/sunshine/platform/linux/graphics.cpp @@ -485,11 +485,16 @@ void sws_t::set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) }; color_matrix.update(members, sizeof(members) / sizeof(decltype(members[0]))); + + program[0].bind(color_matrix); + program[1].bind(color_matrix); } std::optional sws_t::make(int in_width, int in_height, int out_width, int out_heigth, gl::tex_t &&tex) { sws_t sws; + sws.serial = std::numeric_limits::max(); + // Ensure aspect ratio is maintained auto scalar = std::fminf(out_width / (float)in_width, out_heigth / (float)in_height); auto out_width_f = in_width * scalar; @@ -499,13 +504,16 @@ std::optional sws_t::make(int in_width, int in_height, int out_width, int auto offsetX_f = (out_width - out_width_f) / 2; auto offsetY_f = (out_heigth - out_height_f) / 2; - sws.width = out_width_f; - sws.height = out_height_f; + sws.out_width = out_width_f; + sws.out_height = out_height_f; + + sws.in_width = in_width; + sws.in_height = in_height; sws.offsetX = offsetX_f; sws.offsetY = offsetY_f; - auto width_i = 1.0f / sws.width; + auto width_i = 1.0f / sws.out_width; { const char *sources[] { @@ -542,7 +550,16 @@ std::optional sws_t::make(int in_width, int in_height, int out_width, int return std::nullopt; } - auto program = gl::program_t::link(compiled_sources[1].left(), compiled_sources[0].left()); + auto program = gl::program_t::link(compiled_sources[3].left(), compiled_sources[4].left()); + if(program.has_right()) { + BOOST_LOG(error) << "GL linker: "sv << program.right(); + return std::nullopt; + } + + // Cursor - shader + sws.program[2] = std::move(program.left()); + + program = gl::program_t::link(compiled_sources[1].left(), compiled_sources[0].left()); if(program.has_right()) { BOOST_LOG(error) << "GL linker: "sv << program.right(); return std::nullopt; @@ -588,13 +605,21 @@ std::optional sws_t::make(int in_width, int in_height, int out_width, int sws.tex = std::move(tex); + sws.cursor_framebuffer = gl::frame_buf_t::make(1); + sws.cursor_framebuffer.bind(&sws.tex[0], &sws.tex[1]); + + sws.program[0].bind(sws.color_matrix); + sws.program[1].bind(sws.color_matrix); + + gl::ctx.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gl_drain_errors; return std::move(sws); } std::optional sws_t::make(int in_width, int in_height, int out_width, int out_heigth) { - auto tex = gl::tex_t::make(1); + auto tex = gl::tex_t::make(2); gl::ctx.BindTexture(GL_TEXTURE_2D, tex[0]); gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, in_width, in_height); @@ -606,19 +631,46 @@ void sws_t::load_ram(platf::img_t &img) { gl::ctx.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, img.width, img.height, GL_BGRA, GL_UNSIGNED_BYTE, img.data); } -void sws_t::load_vram(platf::img_t &img, int offset_x, int offset_y, int framebuffer) { +void sws_t::load_vram(cursor_t &img, int offset_x, int offset_y, int framebuffer) { gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, framebuffer); gl::ctx.ReadBuffer(GL_COLOR_ATTACHMENT0); gl::ctx.BindTexture(GL_TEXTURE_2D, tex[0]); - gl::ctx.CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, offset_x, offset_y, img.width, img.height); + gl::ctx.CopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, offset_x, offset_y, in_width, in_height); - gl::ctx.Flush(); + if(img.data) { + gl::ctx.BindTexture(GL_TEXTURE_2D, tex[1]); + if(serial != img.serial) { + serial = img.serial; + + gl::ctx.TexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, img.width, img.height); + gl::ctx.TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, img.width, img.height, GL_BGRA, GL_UNSIGNED_BYTE, img.data); + } + + gl::ctx.Enable(GL_BLEND); + GLenum attachment = GL_COLOR_ATTACHMENT0; + gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, cursor_framebuffer[0]); + gl::ctx.DrawBuffers(1, &attachment); + +#ifndef NDEBUG + auto status = gl::ctx.CheckFramebufferStatus(GL_FRAMEBUFFER); + if(status != GL_FRAMEBUFFER_COMPLETE) { + BOOST_LOG(error) << "Pass Cursor: CheckFramebufferStatus() --> [0x"sv << util::hex(status).to_string_view() << ']'; + return; + } +#endif + + gl::ctx.UseProgram(program[2].handle()); + gl::ctx.Viewport(img.x, img.y, img.width, img.height); + gl::ctx.DrawArrays(GL_TRIANGLES, 0, 3); + + gl::ctx.Disable(GL_BLEND); + } + + gl::ctx.BindTexture(GL_TEXTURE_2D, 0); } int sws_t::convert(nv12_t &nv12) { - auto texture = tex[0]; - - gl::ctx.BindTexture(GL_TEXTURE_2D, texture); + gl::ctx.BindTexture(GL_TEXTURE_2D, tex[0]); GLenum attachments[] { GL_COLOR_ATTACHMENT0, @@ -629,21 +681,23 @@ int sws_t::convert(nv12_t &nv12) { gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]); gl::ctx.DrawBuffers(1, &attachments[x]); +#ifndef NDEBUG auto status = gl::ctx.CheckFramebufferStatus(GL_FRAMEBUFFER); if(status != GL_FRAMEBUFFER_COMPLETE) { BOOST_LOG(error) << "Pass "sv << x << ": CheckFramebufferStatus() --> [0x"sv << util::hex(status).to_string_view() << ']'; return -1; } - - gl::ctx.BindTexture(GL_TEXTURE_2D, texture); +#endif gl::ctx.UseProgram(program[x].handle()); - program[x].bind(color_matrix); - - gl::ctx.Viewport(offsetX / (x + 1), offsetY / (x + 1), width / (x + 1), height / (x + 1)); + gl::ctx.Viewport(offsetX / (x + 1), offsetY / (x + 1), out_width / (x + 1), out_height / (x + 1)); gl::ctx.DrawArrays(GL_TRIANGLES, 0, 3); } + gl::ctx.BindTexture(GL_TEXTURE_2D, 0); + + gl::ctx.Flush(); + return 0; } } // namespace egl diff --git a/sunshine/platform/linux/graphics.h b/sunshine/platform/linux/graphics.h index 74c9ef49..a42b6257 100644 --- a/sunshine/platform/linux/graphics.h +++ b/sunshine/platform/linux/graphics.h @@ -221,6 +221,16 @@ std::optional import_target( std::array &&fds, const surface_descriptor_t &r8, const surface_descriptor_t &gr88); +class cursor_t : public platf::img_t { +public: + int x, y; + int xhot, yhot; + + unsigned long serial; + + std::vector buffer; +}; + class sws_t { public: static std::optional make(int in_width, int in_height, int out_width, int out_heigth, gl::tex_t &&tex); @@ -229,16 +239,27 @@ public: int convert(nv12_t &nv12); void load_ram(platf::img_t &img); - void load_vram(platf::img_t &img, int offset_x, int offset_y, int framebuffer); + void load_vram(cursor_t &img, int offset_x, int offset_y, int framebuffer); void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range); + // The first texture is the monitor image. + // The second texture is the cursor image gl::tex_t tex; - gl::program_t program[2]; + + // The cursor image will be blended into this framebuffer + gl::frame_buf_t cursor_framebuffer; + + // Y - shader, UV - shader, Cursor - shader + gl::program_t program[3]; gl::buffer_t color_matrix; - int width, height; + int out_width, out_height; + int in_width, in_height; int offsetX, offsetY; + + // Store latest cursor for load_vram + std::uint64_t serial; }; bool fail(); diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index 8848d862..bcaad026 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -12,8 +12,10 @@ #include "sunshine/round_robin.h" #include "sunshine/utility.h" +// Cursor rendering support through x11 #include "graphics.h" #include "vaapi.h" +#include "x11grab.h" using namespace std::literals; namespace fs = std::filesystem; @@ -261,7 +263,8 @@ public: auto end = std::end(card); for(auto plane = std::begin(card); plane != end; ++plane) { - bool cursor; + bool cursor = false; + auto props = card.plane_props(plane->plane_id); for(auto &[prop, val] : props) { if(prop->name == "type"sv) { @@ -334,6 +337,8 @@ public: return -1; } + cursor_opt = x11::cursor_t::make(); + return 0; } @@ -342,6 +347,8 @@ public: card_t card; file_t fb_fd; + + std::optional cursor_opt; }; class display_ram_t : public display_t { @@ -440,6 +447,10 @@ public: 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); + if(cursor_opt && cursor) { + cursor_opt->blend(*img_out_base, offset_x, offset_y); + } + return capture_e::ok; } @@ -487,12 +498,11 @@ public: } std::shared_ptr alloc_img() override { - auto img = std::make_shared(); + auto img = std::make_shared(); - img->width = width; - img->height = height; + img->serial = std::numeric_limitsserial)>::max(); + img->data = nullptr; img->pixel_pitch = 4; - img->row_pitch = img->pixel_pitch * width; return img; } @@ -535,7 +545,20 @@ public: return capture_e::ok; } - capture_e snapshot(img_t * /*img_out_base */, std::chrono::milliseconds /* timeout */, bool /* cursor */) { + capture_e snapshot(img_t *img_out_base, std::chrono::milliseconds /* timeout */, bool cursor) { + if(!cursor || !cursor_opt) { + img_out_base->data = nullptr; + return capture_e::ok; + } + + auto img = (egl::cursor_t *)img_out_base; + cursor_opt->capture(*img); + + img->x -= offset_x; + img->xhot -= offset_x; + img->yhot -= offset_y; + img->y -= offset_y; + return capture_e::ok; } }; diff --git a/sunshine/platform/linux/vaapi.cpp b/sunshine/platform/linux/vaapi.cpp index d7961705..4f2304ba 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/sunshine/platform/linux/vaapi.cpp @@ -198,7 +198,7 @@ public: return 0; } - int _set_frame(AVFrame *frame) { + int set_frame(AVFrame *frame) override { this->hwframe.reset(frame); this->frame = frame; @@ -252,6 +252,12 @@ public: return -1; } + auto sws_opt = egl::sws_t::make(width, height, frame->width, frame->height); + if(!sws_opt) { + return -1; + } + + this->sws = std::move(*sws_opt); this->nv12 = std::move(*nv12_opt); return 0; @@ -284,27 +290,12 @@ public: sws.convert(nv12); return 0; } - - int set_frame(AVFrame *frame) override { - if(_set_frame(frame)) { - return -1; - } - - auto sws_opt = egl::sws_t::make(width, height, frame->width, frame->height); - if(!sws_opt) { - return -1; - } - - this->sws = std::move(*sws_opt); - - return 0; - } }; class va_vram_t : public va_t { public: int convert(platf::img_t &img) override { - sws.load_vram(img, offset_x, offset_y, framebuffer[0]); + sws.load_vram((egl::cursor_t &)img, offset_x, offset_y, framebuffer[0]); sws.convert(nv12); return 0; @@ -331,21 +322,6 @@ public: return 0; } - int set_frame(AVFrame *frame) override { - if(_set_frame(frame)) { - return -1; - } - - auto sws_opt = egl::sws_t::make(width, height, frame->width, frame->height); - if(!sws_opt) { - return -1; - } - - this->sws = std::move(*sws_opt); - - return 0; - } - file_t fb_fd; egl::rgb_t rgb; diff --git a/sunshine/platform/linux/x11grab.cpp b/sunshine/platform/linux/x11grab.cpp index d10cfa88..434bbf3d 100644 --- a/sunshine/platform/linux/x11grab.cpp +++ b/sunshine/platform/linux/x11grab.cpp @@ -20,22 +20,22 @@ #include "sunshine/main.h" #include "sunshine/task_pool.h" +#include "graphics.h" #include "misc.h" #include "vaapi.h" +#include "x11grab.h" using namespace std::literals; namespace platf { +int load_xcb(); +int load_x11(); namespace x11 { #define _FN(x, ret, args) \ typedef ret(*x##_fn) args; \ static x##_fn x -using XID = unsigned long; -using Time = unsigned long; -using Rotation = unsigned short; - _FN(GetImage, XImage *, ( Display * display, @@ -313,7 +313,7 @@ struct shm_img_t : public img_t { } }; -void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) { +static void blend_cursor(Display *display, img_t &img, int offsetX, int offsetY) { xcursor_t overlay { x11::fix::GetCursorImage(display) }; if(!overlay) { @@ -705,7 +705,7 @@ std::shared_ptr x11_display(platf::mem_type_e hwdevice_type, const st } std::vector x11_display_names() { - if(xcb::init_shm() || xcb::init() || x11::init() || x11::rr::init() || x11::fix::init()) { + if(load_x11() || load_xcb()) { BOOST_LOG(error) << "Couldn't init x11 libraries"sv; return {}; @@ -746,4 +746,69 @@ void freeImage(XImage *p) { void freeX(XFixesCursorImage *p) { x11::Free(p); } + +int load_xcb() { + // This will be called once only + static int xcb_status = xcb::init_shm() || xcb::init(); + + return xcb_status; +} + +int load_x11() { + // This will be called once only + static int x11_status = x11::init() || x11::rr::init() || x11::fix::init(); + + return x11_status; +} + +namespace x11 { +std::optional cursor_t::make() { + if(load_x11()) { + return std::nullopt; + } + + cursor_t cursor; + + cursor.ctx.reset((cursor_ctx_t::pointer)x11::OpenDisplay(nullptr)); + + return cursor; +} + +void cursor_t::capture(egl::cursor_t &img) { + auto display = (xdisplay_t::pointer)ctx.get(); + + xcursor_t xcursor = fix::GetCursorImage(display); + + if(img.serial != xcursor->cursor_serial) { + auto buf_size = xcursor->width * xcursor->height * sizeof(int); + + if(img.buffer.size() < buf_size) { + img.buffer.resize(buf_size); + } + + std::transform(xcursor->pixels, xcursor->pixels + buf_size / 4, (int *)img.buffer.data(), [](long pixel) -> int { + return pixel; + }); + } + + img.data = img.buffer.data(); + img.width = xcursor->width; + img.height = xcursor->height; + img.xhot = xcursor->xhot; + img.yhot = xcursor->yhot; + img.x = xcursor->x; + img.y = xcursor->y; + img.pixel_pitch = 4; + img.row_pitch = img.pixel_pitch * img.width; + img.serial = xcursor->cursor_serial; +} + +void cursor_t::blend(img_t &img, int offsetX, int offsetY) { + blend_cursor((xdisplay_t::pointer)ctx.get(), img, offsetX, offsetY); +} + +void freeCursorCtx(cursor_ctx_t::pointer ctx) { + x11::CloseDisplay((xdisplay_t::pointer)ctx); +} +} // namespace x11 } // namespace platf diff --git a/sunshine/platform/linux/x11grab.h b/sunshine/platform/linux/x11grab.h new file mode 100644 index 00000000..1440ae76 --- /dev/null +++ b/sunshine/platform/linux/x11grab.h @@ -0,0 +1,48 @@ +#ifndef SUNSHINE_X11_GRAB +#define SUNSHINE_X11_GRAB + +#include + +#include "sunshine/platform/common.h" +#include "sunshine/utility.h" + +namespace egl { +class cursor_t; +} + +namespace platf::x11 { + +#ifdef SUNSHINE_BUILD_X11 +struct cursor_ctx_raw_t; +void freeCursorCtx(cursor_ctx_raw_t *ctx); + +using cursor_ctx_t = util::safe_ptr; + +class cursor_t { +public: + static std::optional make(); + + void capture(egl::cursor_t &img); + + /** + * Capture and blend the cursor into the image + * + * img <-- destination image + * offsetX, offsetY <--- Top left corner of the virtual screen + */ + void blend(img_t &img, int offsetX, int offsetY); + + cursor_ctx_t ctx; +}; +#else +class cursor_t { +public: + static std::optional make() { return std::nullopt; } + + void capture(egl::cursor_t &) {} + void blend(img_t &, int, int) {} +}; +#endif +} // namespace platf::x11 + +#endif \ No newline at end of file From d852bb82a366742dde8af9607efb264d48b68eba Mon Sep 17 00:00:00 2001 From: loki Date: Sun, 15 Aug 2021 22:15:24 +0200 Subject: [PATCH 13/21] Only use graphics card connected to monitor if it's capable of h264 encoding --- sunshine/platform/linux/kmsgrab.cpp | 13 ++ sunshine/platform/linux/vaapi.cpp | 177 ++++++++++++++++++++++++++-- sunshine/platform/linux/vaapi.h | 3 + 3 files changed, 185 insertions(+), 8 deletions(-) diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index bcaad026..98d4da5e 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -561,6 +561,19 @@ public: return capture_e::ok; } + + int init(const std::string &display_name, int framerate) { + if(display_t::init(display_name, framerate)) { + return -1; + } + + if(!va::validate(card.fd.el)) { + BOOST_LOG(warning) << "Monitor "sv << display_name << " doesn't support hardware encoding. Reverting back to GPU -> RAM -> GPU"sv; + return -1; + } + + return 0; + } }; } // namespace kms diff --git a/sunshine/platform/linux/vaapi.cpp b/sunshine/platform/linux/vaapi.cpp index 4f2304ba..25c7788b 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/sunshine/platform/linux/vaapi.cpp @@ -74,6 +74,119 @@ struct DRMPRIMESurfaceDescriptor { } layers[4]; }; +/** Currently defined profiles */ +enum class profile_e { + // Profile ID used for video processing. + None = -1, + MPEG2Simple = 0, + MPEG2Main = 1, + MPEG4Simple = 2, + MPEG4AdvancedSimple = 3, + MPEG4Main = 4, + H264Baseline = 5, + H264Main = 6, + H264High = 7, + VC1Simple = 8, + VC1Main = 9, + VC1Advanced = 10, + H263Baseline = 11, + JPEGBaseline = 12, + H264ConstrainedBaseline = 13, + VP8Version0_3 = 14, + H264MultiviewHigh = 15, + H264StereoHigh = 16, + HEVCMain = 17, + HEVCMain10 = 18, + VP9Profile0 = 19, + VP9Profile1 = 20, + VP9Profile2 = 21, + VP9Profile3 = 22, + HEVCMain12 = 23, + HEVCMain422_10 = 24, + HEVCMain422_12 = 25, + HEVCMain444 = 26, + HEVCMain444_10 = 27, + HEVCMain444_12 = 28, + HEVCSccMain = 29, + HEVCSccMain10 = 30, + HEVCSccMain444 = 31, + AV1Profile0 = 32, + AV1Profile1 = 33, + HEVCSccMain444_10 = 34, + + // Profile ID used for protected video playback. + Protected = 35 +}; + +enum class entry_e { + VLD = 1, + IZZ = 2, + IDCT = 3, + MoComp = 4, + Deblocking = 5, + EncSlice = 6, /* slice level encode */ + EncPicture = 7, /* pictuer encode, JPEG, etc */ + /* + * For an implementation that supports a low power/high performance variant + * for slice level encode, it can choose to expose the + * VAEntrypointEncSliceLP entrypoint. Certain encoding tools may not be + * available with this entrypoint (e.g. interlace, MBAFF) and the + * application can query the encoding configuration attributes to find + * out more details if this entrypoint is supported. + */ + EncSliceLP = 8, + VideoProc = 10, /**< Video pre/post-processing. */ + /** + * \brief FEI + * + * The purpose of FEI (Flexible Encoding Infrastructure) is to allow applications to + * have more controls and trade off quality for speed with their own IPs. + * The application can optionally provide input to ENC for extra encode control + * and get the output from ENC. Application can chose to modify the ENC + * output/PAK input during encoding, but the performance impact is significant. + * + * On top of the existing buffers for normal encode, there will be + * one extra input buffer (VAEncMiscParameterFEIFrameControl) and + * three extra output buffers (VAEncFEIMVBufferType, VAEncFEIMBModeBufferType + * and VAEncFEIDistortionBufferType) for FEI entry function. + * If separate PAK is set, two extra input buffers + * (VAEncFEIMVBufferType, VAEncFEIMBModeBufferType) are needed for PAK input. + **/ + FEI = 11, + /** + * \brief Stats + * + * A pre-processing function for getting some statistics and motion vectors is added, + * and some extra controls for Encode pipeline are provided. The application can + * optionally call the statistics function to get motion vectors and statistics like + * variances, distortions before calling Encode function via this entry point. + * + * Checking whether Statistics is supported can be performed with vaQueryConfigEntrypoints(). + * If Statistics entry point is supported, then the list of returned entry-points will + * include #Stats. Supported pixel format, maximum resolution and statistics + * specific attributes can be obtained via normal attribute query. One input buffer + * (VAStatsStatisticsParameterBufferType) and one or two output buffers + * (VAStatsStatisticsBufferType, VAStatsStatisticsBottomFieldBufferType (for interlace only) + * and VAStatsMVBufferType) are needed for this entry point. + **/ + Stats = 12, + /** + * \brief ProtectedTEEComm + * + * A function for communicating with TEE (Trusted Execution Environment). + **/ + ProtectedTEEComm = 13, + /** + * \brief ProtectedContent + * + * A function for protected content to decrypt encrypted content. + **/ + ProtectedContent = 14, +}; + + +typedef VAStatus (*queryConfigEntrypoints_fn)(VADisplay dpy, profile_e profile, entry_e *entrypoint_list, int *num_entrypoints); +typedef int (*maxNumEntrypoints_fn)(VADisplay dpy); typedef VADisplay (*getDisplayDRM_fn)(int fd); typedef VAStatus (*terminate_fn)(VADisplay dpy); typedef VAStatus (*initialize_fn)(VADisplay dpy, int *major_version, int *minor_version); @@ -87,14 +200,16 @@ typedef VAStatus (*exportSurfaceHandle_fn)( uint32_t mem_type, uint32_t flags, void *descriptor); -getDisplayDRM_fn getDisplayDRM; -terminate_fn terminate; -initialize_fn initialize; -errorStr_fn errorStr; -setErrorCallback_fn setErrorCallback; -setInfoCallback_fn setInfoCallback; -queryVendorString_fn queryVendorString; -exportSurfaceHandle_fn exportSurfaceHandle; +static maxNumEntrypoints_fn maxNumEntrypoints; +static queryConfigEntrypoints_fn queryConfigEntrypoints; +static getDisplayDRM_fn getDisplayDRM; +static terminate_fn terminate; +static initialize_fn initialize; +static errorStr_fn errorStr; +static setErrorCallback_fn setErrorCallback; +static setInfoCallback_fn setInfoCallback; +static queryVendorString_fn queryVendorString; +static exportSurfaceHandle_fn exportSurfaceHandle; using display_t = util::dyn_safe_ptr_v2; @@ -112,6 +227,8 @@ int init_main_va() { } std::vector> funcs { + { (dyn::apiproc *)&maxNumEntrypoints, "vaMaxNumEntrypoints" }, + { (dyn::apiproc *)&queryConfigEntrypoints, "vaQueryConfigEntrypoints" }, { (dyn::apiproc *)&terminate, "vaTerminate" }, { (dyn::apiproc *)&initialize, "vaInitialize" }, { (dyn::apiproc *)&errorStr, "vaErrorStr" }, @@ -430,6 +547,50 @@ int vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf return 0; } +bool validate(int fd) { + if(init()) { + return false; + } + + va::display_t display { va::getDisplayDRM(fd) }; + if(!display) { + char string[1024]; + + auto bytes = readlink(("/proc/self/fd/" + std::to_string(fd)).c_str(), string, sizeof(string)); + + std::string_view render_device { string, (std::size_t)bytes }; + + BOOST_LOG(error) << "Couldn't open a va display from DRM with device: "sv << render_device; + return -1; + } + + int major, minor; + auto status = initialize(display.get(), &major, &minor); + if(status) { + BOOST_LOG(error) << "Couldn't initialize va display: "sv << va::errorStr(status); + return -1; + } + + std::vector entrypoints; + entrypoints.resize(maxNumEntrypoints(display.get())); + + int count; + status = queryConfigEntrypoints(display.get(), profile_e::H264Main, entrypoints.data(), &count); + if(status) { + BOOST_LOG(error) << "Couldn't query entrypoints for profile::H264Main "sv << va::errorStr(status); + return -1; + } + entrypoints.resize(count); + + for(auto entrypoint : entrypoints) { + if(entrypoint == entry_e::EncSlice) { + return true; + } + } + + return false; +} + std::shared_ptr make_hwdevice(int width, int height) { auto render_device = config::video.adapter_name.empty() ? "/dev/dri/renderD128" : config::video.adapter_name.c_str(); diff --git a/sunshine/platform/linux/vaapi.h b/sunshine/platform/linux/vaapi.h index 5025ca2c..a7165166 100644 --- a/sunshine/platform/linux/vaapi.h +++ b/sunshine/platform/linux/vaapi.h @@ -11,6 +11,9 @@ namespace va { std::shared_ptr make_hwdevice(int width, int height); std::shared_ptr make_hwdevice(int width, int height, file_t &&card, int offset_x, int offset_y, const egl::surface_descriptor_t &sd); +// Ensure the render device pointed to by fd is capable of encoding h264 +bool validate(int fd); + int init(); } // namespace va #endif \ No newline at end of file From 1d2e042240bfd56556c584174298360c6c4649e4 Mon Sep 17 00:00:00 2001 From: loki Date: Sun, 15 Aug 2021 22:19:08 +0200 Subject: [PATCH 14/21] Use standard function for create egl images --- sunshine/platform/linux/graphics.cpp | 6 +++--- sunshine/platform/linux/graphics.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sunshine/platform/linux/graphics.cpp b/sunshine/platform/linux/graphics.cpp index 2799e4af..d006d33b 100644 --- a/sunshine/platform/linux/graphics.cpp +++ b/sunshine/platform/linux/graphics.cpp @@ -410,7 +410,7 @@ std::optional import_source(display_t::pointer egl_display, const surface } std::optional import_target(display_t::pointer egl_display, std::array &&fds, const surface_descriptor_t &r8, const surface_descriptor_t &gr88) { - int img_attr_planes[2][13] { + EGLAttrib img_attr_planes[2][13] { { EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_R8, EGL_WIDTH, r8.width, EGL_HEIGHT, r8.height, @@ -430,8 +430,8 @@ std::optional import_target(display_t::pointer egl_display, std::array Date: Sun, 15 Aug 2021 22:25:34 +0200 Subject: [PATCH 15/21] Fix incorrect cursor location --- sunshine/platform/linux/graphics.h | 1 - sunshine/platform/linux/kmsgrab.cpp | 2 -- sunshine/platform/linux/x11grab.cpp | 6 ++---- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/sunshine/platform/linux/graphics.h b/sunshine/platform/linux/graphics.h index 21a00062..3513047f 100644 --- a/sunshine/platform/linux/graphics.h +++ b/sunshine/platform/linux/graphics.h @@ -224,7 +224,6 @@ std::optional import_target( class cursor_t : public platf::img_t { public: int x, y; - int xhot, yhot; unsigned long serial; diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index 98d4da5e..2e1ad14c 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -555,8 +555,6 @@ public: cursor_opt->capture(*img); img->x -= offset_x; - img->xhot -= offset_x; - img->yhot -= offset_y; img->y -= offset_y; return capture_e::ok; diff --git a/sunshine/platform/linux/x11grab.cpp b/sunshine/platform/linux/x11grab.cpp index 434bbf3d..8d4f2b4f 100644 --- a/sunshine/platform/linux/x11grab.cpp +++ b/sunshine/platform/linux/x11grab.cpp @@ -794,10 +794,8 @@ void cursor_t::capture(egl::cursor_t &img) { img.data = img.buffer.data(); img.width = xcursor->width; img.height = xcursor->height; - img.xhot = xcursor->xhot; - img.yhot = xcursor->yhot; - img.x = xcursor->x; - img.y = xcursor->y; + img.x = xcursor->x - xcursor->xhot; + img.y = xcursor->y - xcursor->yhot; img.pixel_pitch = 4; img.row_pitch = img.pixel_pitch * img.width; img.serial = xcursor->cursor_serial; From fc7ec9e538b60d890c49fa8d88905594b9340602 Mon Sep 17 00:00:00 2001 From: loki Date: Tue, 17 Aug 2021 21:15:38 +0200 Subject: [PATCH 16/21] Better validation of vaapi capability --- sunshine/platform/linux/vaapi.cpp | 52 ++++++++++++++++++++----------- sunshine/video.cpp | 2 +- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/sunshine/platform/linux/vaapi.cpp b/sunshine/platform/linux/vaapi.cpp index 25c7788b..363c51ad 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/sunshine/platform/linux/vaapi.cpp @@ -547,6 +547,27 @@ int vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf return 0; } +bool query(display_t::pointer display, profile_e profile) { + std::vector entrypoints; + entrypoints.resize(maxNumEntrypoints(display)); + + int count; + auto status = queryConfigEntrypoints(display, profile, entrypoints.data(), &count); + if(status) { + BOOST_LOG(error) << "Couldn't query entrypoints for profile::H264Main "sv << va::errorStr(status); + return false; + } + entrypoints.resize(count); + + for(auto entrypoint : entrypoints) { + if(entrypoint == entry_e::EncSlice || entrypoint == entry_e::EncSliceLP) { + return true; + } + } + + return false; +} + bool validate(int fd) { if(init()) { return false; @@ -561,34 +582,29 @@ bool validate(int fd) { std::string_view render_device { string, (std::size_t)bytes }; BOOST_LOG(error) << "Couldn't open a va display from DRM with device: "sv << render_device; - return -1; + return false; } int major, minor; auto status = initialize(display.get(), &major, &minor); if(status) { BOOST_LOG(error) << "Couldn't initialize va display: "sv << va::errorStr(status); - return -1; + return false; } - std::vector entrypoints; - entrypoints.resize(maxNumEntrypoints(display.get())); - - int count; - status = queryConfigEntrypoints(display.get(), profile_e::H264Main, entrypoints.data(), &count); - if(status) { - BOOST_LOG(error) << "Couldn't query entrypoints for profile::H264Main "sv << va::errorStr(status); - return -1; - } - entrypoints.resize(count); - - for(auto entrypoint : entrypoints) { - if(entrypoint == entry_e::EncSlice) { - return true; - } + if(!query(display.get(), profile_e::H264Main)) { + return false; } - return false; + if(config::video.hevc_mode > 1 && !query(display.get(), profile_e::HEVCMain)) { + return false; + } + + if(config::video.hevc_mode > 2 && !query(display.get(), profile_e::HEVCMain10)) { + return false; + } + + return true; } std::shared_ptr make_hwdevice(int width, int height) { diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 6fbc72be..403db051 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -88,7 +88,7 @@ public: data[0] = sw_frame->data[0] + offsetY; if(sw_frame->format == AV_PIX_FMT_NV12) { - data[1] = sw_frame->data[1] + offsetUV; + data[1] = sw_frame->data[1] + offsetUV * 2; data[2] = nullptr; } else { From 869b6ed89ddb1a78590d288c72d3ceb1bde774f2 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 18 Aug 2021 20:19:15 +0200 Subject: [PATCH 17/21] Fix VAAPI with intel iGPU's --- sunshine/platform/linux/vaapi.cpp | 4 ++-- sunshine/video.cpp | 29 ++++++++++++++++++++--------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/sunshine/platform/linux/vaapi.cpp b/sunshine/platform/linux/vaapi.cpp index 363c51ad..966b6139 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/sunshine/platform/linux/vaapi.cpp @@ -547,14 +547,14 @@ int vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf return 0; } -bool query(display_t::pointer display, profile_e profile) { +static bool query(display_t::pointer display, profile_e profile) { std::vector entrypoints; entrypoints.resize(maxNumEntrypoints(display)); int count; auto status = queryConfigEntrypoints(display, profile, entrypoints.data(), &count); if(status) { - BOOST_LOG(error) << "Couldn't query entrypoints for profile::H264Main "sv << va::errorStr(status); + BOOST_LOG(error) << "Couldn't query entrypoints: "sv << va::errorStr(status); return false; } entrypoints.resize(count); diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 403db051..4099d5e6 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -237,10 +237,11 @@ public: }; enum flag_e { - DEFAULT = 0x00, - SYSTEM_MEMORY = 0x01, - H264_ONLY = 0x02, - LIMITED_GOP_SIZE = 0x04, + DEFAULT = 0x00, + PARALLEL_ENCODING = 0x01, + H264_ONLY = 0x02, // When HEVC is to heavy + LIMITED_GOP_SIZE = 0x04, // Some encoders don't like it when you have an infinite GOP_SIZE. *cough* VAAPI *cough* + SINGLE_SLICE_ONLY = 0x08, // Never use multiple slices <-- Older intel iGPU's ruin it for everyone else :P }; struct encoder_t { @@ -440,7 +441,7 @@ static encoder_t nvenc { DEFAULT, dxgi_make_hwdevice_ctx #else - SYSTEM_MEMORY, + PARALLEL_ENCODING, cuda_make_hwdevice_ctx #endif }; @@ -506,7 +507,7 @@ static encoder_t software { std::make_optional("qp"s, &config::video.qp), "libx264"s, }, - H264_ONLY | SYSTEM_MEMORY, + H264_ONLY | PARALLEL_ENCODING, nullptr }; @@ -534,7 +535,7 @@ static encoder_t vaapi { std::make_optional("qp"s, &config::video.qp), "h264_vaapi"s, }, - LIMITED_GOP_SIZE | SYSTEM_MEMORY, + LIMITED_GOP_SIZE | PARALLEL_ENCODING | SINGLE_SLICE_ONLY, vaapi_make_hwdevice_ctx }; @@ -1374,7 +1375,7 @@ void capture( auto idr_events = mail->event(mail::idr); idr_events->raise(true); - if(encoders.front().flags & SYSTEM_MEMORY) { + if(encoders.front().flags & PARALLEL_ENCODING) { capture_async(std::move(mail), config, channel_data); } else { @@ -1540,8 +1541,13 @@ retry: std::vector> configs { { encoder_t::DYNAMIC_RANGE, { 1920, 1080, 60, 1000, 1, 0, 3, 1, 1 } }, - { encoder_t::SLICE, { 1920, 1080, 60, 1000, 2, 1, 1, 0, 0 } }, }; + + if(!(encoder.flags & SINGLE_SLICE_ONLY)) { + configs.emplace_back( + std::pair { encoder_t::SLICE, { 1920, 1080, 60, 1000, 2, 1, 1, 0, 0 } }); + } + for(auto &[flag, config] : configs) { auto h264 = config; auto hevc = config; @@ -1555,6 +1561,11 @@ retry: } } + if(encoder.flags & SINGLE_SLICE_ONLY) { + encoder.h264.capabilities[encoder_t::SLICE] = false; + encoder.hevc.capabilities[encoder_t::SLICE] = false; + } + encoder.h264[encoder_t::VUI_PARAMETERS] = encoder.h264[encoder_t::VUI_PARAMETERS] && !config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE]; encoder.hevc[encoder_t::VUI_PARAMETERS] = encoder.hevc[encoder_t::VUI_PARAMETERS] && !config::sunshine.flags[config::flag::FORCE_VIDEO_HEADER_REPLACE]; From 0f4cdc2d219a682ea2189c205cfa91484e6bfa60 Mon Sep 17 00:00:00 2001 From: loki Date: Wed, 18 Aug 2021 21:13:55 +0200 Subject: [PATCH 18/21] Fix hanging when switching monitors --- sunshine/video.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 4099d5e6..b630f547 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -695,10 +695,7 @@ void captureThread( return; } - { - auto lg = display_wp.lock(); - display_wp = disp; - } + display_wp = disp; // Re-allocate images for(auto &img : imgs) { From ca9809ca7e95e4e5bb7294e63558e0559a1c0b82 Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 19 Aug 2021 21:40:14 +0200 Subject: [PATCH 19/21] Fix stream based on KMS freezing when switching resolutions --- sunshine/platform/linux/kmsgrab.cpp | 48 +++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index 2e1ad14c..99e87b6d 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -236,11 +236,40 @@ void print(plane_t::pointer plane, fb_t::pointer fb, crtc_t::pointer crtc) { class display_t : public platf::display_t { public: display_t(mem_type_e mem_type) : platf::display_t(), mem_type { mem_type } {} + ~display_t() { + while(!thread_pool.cancel(loop_id)) + ; + } mem_type_e mem_type; std::chrono::nanoseconds delay; + // Done on a seperate thread to prevent additional latency to capture code + // This code detects if the framebuffer has been removed from KMS + void task_loop() { + capture_e capture = capture_e::reinit; + + 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); + capture = capture_e::error; + } + + if(fb->fb_id == framebuffer_id) { + capture = capture_e::ok; + + break; + } + } + + this->status = capture; + + loop_id = thread_pool.pushDelayed(&display_t::task_loop, 2s, this).task_id; + } + int init(const std::string &display_name, int framerate) { delay = std::chrono::nanoseconds { 1s } / framerate; @@ -310,6 +339,8 @@ public: auto crct = card.crtc(plane->crtc_id); kms::print(plane.get(), fb.get(), crct.get()); + framebuffer_id = fb->fb_id; + img_width = fb->width; img_height = fb->height; @@ -339,9 +370,19 @@ public: cursor_opt = x11::cursor_t::make(); + status = capture_e::ok; + + thread_pool.start(1); + loop_id = thread_pool.pushDelayed(&display_t::task_loop, 2s, this).task_id; + return 0; } + // When the framebuffer is reinitialized, this id can no longer be found + std::uint32_t framebuffer_id; + + capture_e status; + int img_width, img_height; int pitch; @@ -349,6 +390,9 @@ public: file_t fb_fd; std::optional cursor_opt; + + util::TaskPool::task_id_t loop_id; + util::ThreadPool thread_pool; }; class display_ram_t : public display_t { @@ -451,7 +495,7 @@ public: cursor_opt->blend(*img_out_base, offset_x, offset_y); } - return capture_e::ok; + return status; } std::shared_ptr alloc_img() override { @@ -557,7 +601,7 @@ public: img->x -= offset_x; img->y -= offset_y; - return capture_e::ok; + return status; } int init(const std::string &display_name, int framerate) { From 12af30b75b8f6344516b7d14c762cdfa49009a0d Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 19 Aug 2021 22:09:09 +0200 Subject: [PATCH 20/21] Update README --- README.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b78bc234..c666dc59 100644 --- a/README.md +++ b/README.md @@ -14,21 +14,38 @@ Sunshine is a Gamestream host for Moonlight ## Linux ### Requirements: + Ubuntu 20.04: -Install the following +Install the following: +#### X11 Only ``` sudo apt install cmake gcc-10 g++-10 libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev ``` +#### X11 + KMS (Requires additional setup) +KMS allows Sunshine to grab the monitor with lower latency then through X11 + +``` +sudo apt install cmake gcc-10 g++-10 libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxrandr-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libdrm-dev +``` + ### Compilation: + +#### X11 Only +- `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules` +- `cd sunshine && mkdir build && cd build` +- `cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DSUNSHINE_BUILD_DRM=OFF ..` +- `make -j ${nproc}` + +#### X11 + KMS - `git clone https://github.com/loki-47-6F-64/sunshine.git --recurse-submodules` - `cd sunshine && mkdir build && cd build` - `cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 ..` - `make -j ${nproc}` - ### Setup: sunshine needs access to uinput to create mouse and gamepad events: + - Add user to group 'input': `usermod -a -G input $USER` - Create udev rules: @@ -52,6 +69,11 @@ sunshine needs access to uinput to create mouse and gamepad events: - `assets/apps.json` is an [example](README.md#application-list) of a list of applications that are started just before running a stream +#### Additional Setup for KMS: +Please note that `cap_sys_admin` may as well be root, except you don't need to be root to run it. +It's necessary to allow Sunshine to use KMS +- `sudo setcap cap_sys_admin+ep sunshine` + ### Trouleshooting: - If you get "Could not create Sunshine Gamepad: Permission Denied", ensure you are part of the group "input": - `groups $USER` From 0b3b78891b95e05491025c83478562098978bef1 Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 19 Aug 2021 22:16:02 +0200 Subject: [PATCH 21/21] Update debian package --- gen-deb.in | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gen-deb.in b/gen-deb.in index 09ec1025..8dae0d09 100755 --- a/gen-deb.in +++ b/gen-deb.in @@ -37,8 +37,8 @@ Package: sunshine Architecture: amd64 Maintainer: @loki Priority: optional -Version: 0.9.1 -Depends: libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0, libboost-log1.67.0 | libboost-log1.71.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2 +Version: 0.10.1 +Depends: libssl1.1, libavdevice58, libboost-thread1.67.0 | libboost-thread1.71.0, libboost-filesystem1.67.0 | libboost-filesystem1.71.0, libboost-log1.67.0 | libboost-log1.71.0, libpulse0, libopus0, libxcb-shm0, libxcb-xfixes0, libxtst6, libevdev2, libdrm2 Description: Gamestream host for Moonlight EOF @@ -88,6 +88,8 @@ if [ -f /etc/sunshine/sunshine.conf ]; then echo "chmod 666 /etc/sunshine/sunshine.conf" chmod 666 /etc/sunshine/sunshine.conf fi + +setcap cap_sys_admin+ep /usr/bin/sunshine EOF cat << 'EOF' > $RULES/85-sunshine-rules.rules