/** * @file src/platform/linux/wlgrab.cpp * @brief todo */ #include "src/platform/common.h" #include "src/main.h" #include "src/video.h" #include "vaapi.h" #include "wayland.h" using namespace std::literals; namespace wl { static int env_width; static int env_height; struct img_t: public platf::img_t { ~img_t() override { delete[] data; data = nullptr; } }; class wlr_t: public platf::display_t { public: int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { delay = std::chrono::nanoseconds { 1s } / config.framerate; mem_type = hwdevice_type; if (display.init()) { return -1; } interface.listen(display.registry()); display.roundtrip(); if (!interface[wl::interface_t::XDG_OUTPUT]) { BOOST_LOG(error) << "Missing Wayland wire for xdg_output"sv; return -1; } if (!interface[wl::interface_t::WLR_EXPORT_DMABUF]) { BOOST_LOG(error) << "Missing Wayland wire for wlr-export-dmabuf"sv; return -1; } auto monitor = interface.monitors[0].get(); if (!display_name.empty()) { auto streamedMonitor = util::from_view(display_name); if (streamedMonitor >= 0 && streamedMonitor < interface.monitors.size()) { monitor = interface.monitors[streamedMonitor].get(); } } monitor->listen(interface.output_manager); display.roundtrip(); output = monitor->output; offset_x = monitor->viewport.offset_x; offset_y = monitor->viewport.offset_y; width = monitor->viewport.width; height = monitor->viewport.height; this->env_width = ::wl::env_width; this->env_height = ::wl::env_height; BOOST_LOG(info) << "Selected monitor ["sv << monitor->description << "] for streaming"sv; BOOST_LOG(debug) << "Offset: "sv << offset_x << 'x' << offset_y; BOOST_LOG(debug) << "Resolution: "sv << width << 'x' << height; BOOST_LOG(debug) << "Desktop Resolution: "sv << env_width << 'x' << env_height; return 0; } int dummy_img(platf::img_t *img) override { return 0; } inline platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) { auto to = std::chrono::steady_clock::now() + timeout; dmabuf.listen(interface.dmabuf_manager, output, cursor); do { display.roundtrip(); if (to < std::chrono::steady_clock::now()) { return platf::capture_e::timeout; } } while (dmabuf.status == dmabuf_t::WAITING); auto current_frame = dmabuf.current_frame; if ( dmabuf.status == dmabuf_t::REINIT || current_frame->sd.width != width || current_frame->sd.height != height) { return platf::capture_e::reinit; } return platf::capture_e::ok; } platf::mem_type_e mem_type; std::chrono::nanoseconds delay; wl::display_t display; interface_t interface; dmabuf_t dmabuf; wl_output *output; }; class wlr_ram_t: public wlr_t { public: platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override { auto next_frame = std::chrono::steady_clock::now(); while (true) { 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; std::shared_ptr img_out; auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor); switch (status) { case platf::capture_e::reinit: case platf::capture_e::error: case platf::capture_e::interrupted: return status; case platf::capture_e::timeout: if (!push_captured_image_cb(std::move(img_out), false)) { return platf::capture_e::ok; } break; case platf::capture_e::ok: if (!push_captured_image_cb(std::move(img_out), true)) { return platf::capture_e::ok; } break; default: BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']'; return status; } } return platf::capture_e::ok; } platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) { auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor); if (status != platf::capture_e::ok) { return status; } auto current_frame = dmabuf.current_frame; auto rgb_opt = egl::import_source(egl_display.get(), current_frame->sd); if (!rgb_opt) { return platf::capture_e::reinit; } if (!pull_free_image_cb(img_out)) { return platf::capture_e::interrupted; } gl::ctx.BindTexture(GL_TEXTURE_2D, (*rgb_opt)->tex[0]); int w, h; gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w); gl::ctx.GetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h); BOOST_LOG(debug) << "width and height: w "sv << w << " h "sv << h; gl::ctx.GetTextureSubImage((*rgb_opt)->tex[0], 0, 0, 0, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out->height * img_out->row_pitch, img_out->data); gl::ctx.BindTexture(GL_TEXTURE_2D, 0); return platf::capture_e::ok; } int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) { if (wlr_t::init(hwdevice_type, display_name, config)) { return -1; } egl_display = egl::make_display(display.get()); if (!egl_display) { return -1; } auto ctx_opt = egl::make_ctx(egl_display.get()); if (!ctx_opt) { return -1; } ctx = std::move(*ctx_opt); return 0; } std::shared_ptr make_hwdevice(platf::pix_fmt_e pix_fmt) override { if (mem_type == platf::mem_type_e::vaapi) { return va::make_hwdevice(width, height, false); } return std::make_shared(); } 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; } egl::display_t egl_display; egl::ctx_t ctx; }; class wlr_vram_t: public wlr_t { public: platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override { auto next_frame = std::chrono::steady_clock::now(); while (true) { 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; std::shared_ptr img_out; auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor); switch (status) { case platf::capture_e::reinit: case platf::capture_e::error: case platf::capture_e::interrupted: return status; case platf::capture_e::timeout: if (!push_captured_image_cb(std::move(img_out), false)) { return platf::capture_e::ok; } break; case platf::capture_e::ok: if (!push_captured_image_cb(std::move(img_out), true)) { return platf::capture_e::ok; } break; default: BOOST_LOG(error) << "Unrecognized capture status ["sv << (int) status << ']'; return status; } } return platf::capture_e::ok; } platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool cursor) { auto status = wlr_t::snapshot(pull_free_image_cb, img_out, timeout, cursor); if (status != platf::capture_e::ok) { return status; } if (!pull_free_image_cb(img_out)) { return platf::capture_e::interrupted; } auto img = (egl::img_descriptor_t *) img_out.get(); img->reset(); auto current_frame = dmabuf.current_frame; ++sequence; img->sequence = sequence; img->sd = current_frame->sd; // Prevent dmabuf from closing the file descriptors. std::fill_n(current_frame->sd.fds, 4, -1); return platf::capture_e::ok; } std::shared_ptr alloc_img() override { auto img = std::make_shared(); img->sequence = 0; img->serial = std::numeric_limitsserial)>::max(); img->data = nullptr; // File descriptors aren't open std::fill_n(img->sd.fds, 4, -1); return img; } std::shared_ptr make_hwdevice(platf::pix_fmt_e pix_fmt) override { if (mem_type == platf::mem_type_e::vaapi) { return va::make_hwdevice(width, height, 0, 0, true); } return std::make_shared(); } int dummy_img(platf::img_t *img) override { // TODO: stop cheating and give black image if (!img) { return -1; }; auto pull_dummy_img_callback = [&img](std::shared_ptr &img_out) -> bool { img_out = img->shared_from_this(); return true; }; std::shared_ptr img_out; return snapshot(pull_dummy_img_callback, img_out, 1000ms, false) != platf::capture_e::ok; } std::uint64_t sequence {}; }; } // namespace wl namespace platf { std::shared_ptr wl_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { 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 (hwdevice_type == platf::mem_type_e::vaapi) { auto wlr = std::make_shared(); if (wlr->init(hwdevice_type, display_name, config)) { return nullptr; } return wlr; } auto wlr = std::make_shared(); if (wlr->init(hwdevice_type, display_name, config)) { return nullptr; } return wlr; } std::vector wl_display_names() { std::vector display_names; wl::display_t display; if (display.init()) { return {}; } wl::interface_t interface; interface.listen(display.registry()); display.roundtrip(); if (!interface[wl::interface_t::XDG_OUTPUT]) { BOOST_LOG(warning) << "Missing Wayland wire for xdg_output"sv; return {}; } if (!interface[wl::interface_t::WLR_EXPORT_DMABUF]) { BOOST_LOG(warning) << "Missing Wayland wire for wlr-export-dmabuf"sv; return {}; } wl::env_width = 0; wl::env_height = 0; for (auto &monitor : interface.monitors) { monitor->listen(interface.output_manager); } display.roundtrip(); for (int x = 0; x < interface.monitors.size(); ++x) { auto monitor = interface.monitors[x].get(); wl::env_width = std::max(wl::env_width, (int) (monitor->viewport.offset_x + monitor->viewport.width)); wl::env_height = std::max(wl::env_height, (int) (monitor->viewport.offset_y + monitor->viewport.height)); display_names.emplace_back(std::to_string(x)); } return display_names; } } // namespace platf