#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