diff --git a/.gitmodules b/.gitmodules index 4550c7d0..153d2de8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,3 @@ [submodule "third-party/miniupnp"] path = third-party/miniupnp url = https://github.com/miniupnp/miniupnp -[submodule "third-party/wayland-protocols"] - path = third-party/wayland-protocols - url = https://github.com/wayland-project/wayland-protocols.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 890a9fa5..11f83774 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,12 +131,13 @@ else() list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1) endif() if(WAYLAND_FOUND) - macro(genWayland FILEPATH FILENAME) - message("wayland-scanner private-code ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILEPATH}/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.c") - message("wayland-scanner client-header ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILEPATH}/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.h") + add_compile_definitions(SUNSHINE_BUILD_WAYLAND) + macro(genWayland FILENAME) + message("wayland-scanner private-code ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.c") + message("wayland-scanner client-header ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.h") execute_process( - COMMAND wayland-scanner private-code ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILEPATH}/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.c - COMMAND wayland-scanner client-header ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILEPATH}/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.h + COMMAND wayland-scanner private-code ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.c + COMMAND wayland-scanner client-header ${CMAKE_SOURCE_DIR}/third-party/wayland-protocols/${FILENAME}.xml ${CMAKE_BINARY_DIR}/generated-src/${FILENAME}.h RESULT_VARIABLE EXIT_INT ) @@ -151,7 +152,8 @@ else() ) endmacro() - genWayland(unstable/xdg-output xdg-output-unstable-v1) + genWayland(xdg-output-unstable-v1) + genWayland(wlr-export-dmabuf-unstable-v1) include_directories( ${WAYLAND_INCLUDE_DIRS} @@ -159,10 +161,12 @@ else() ) list(APPEND PLATFORM_LIBRARIES ${WAYLAND_LIBRARIES}) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/wayland.h) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/wayland.cpp) + list(APPEND PLATFORM_TARGET_FILES + sunshine/platform/linux/wlgrab.cpp + sunshine/platform/linux/wayland.cpp + sunshine/platform/linux/wayland.h) endif() - if(NOT X11_FOUND AND NOT LIBDRM_FOUND) + if(NOT ${X11_FOUND} AND NOT ${LIBDRM_FOUND} AND NOT ${WAYLAND_FOUND}) message(FATAL_ERROR "Couldn't find either x11 or libdrm") endif() diff --git a/sunshine/main.cpp b/sunshine/main.cpp index c95e2752..b2bed72f 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -26,8 +26,6 @@ #include "upnp.h" #include "video.h" -#include "platform/linux/wayland.h" - #include "platform/common.h" extern "C" { #include @@ -241,9 +239,6 @@ int main(int argc, char *argv[]) { shutdown_event->raise(true); }); - wl::test(); - return 0; - proc::refresh(config::stream.file_apps); auto deinit_guard = platf::init(); diff --git a/sunshine/platform/linux/graphics.cpp b/sunshine/platform/linux/graphics.cpp index d006d33b..0d122150 100644 --- a/sunshine/platform/linux/graphics.cpp +++ b/sunshine/platform/linux/graphics.cpp @@ -288,10 +288,23 @@ bool fail() { return eglGetError() != EGL_SUCCESS; } -display_t make_display(gbm::gbm_t::pointer gbm) { - constexpr auto EGL_PLATFORM_GBM_MESA = 0x31D7; +display_t make_display(util::Either native_display) { + constexpr auto EGL_PLATFORM_GBM_MESA = 0x31D7; + constexpr auto EGL_PLATFORM_WAYLAND_KHR = 0x31D8; - display_t display = eglGetPlatformDisplay(EGL_PLATFORM_GBM_MESA, gbm, nullptr); + int egl_platform; + void *native_display_p; + if(native_display.has_left()) { + egl_platform = EGL_PLATFORM_GBM_MESA; + native_display_p = native_display.left(); + } + else { + egl_platform = EGL_PLATFORM_WAYLAND_KHR; + native_display_p = native_display.right(); + } + + // native_display.left() equals native_display.right() + display_t display = eglGetPlatformDisplay(egl_platform, native_display_p, nullptr); if(fail()) { BOOST_LOG(error) << "Couldn't open EGL display: ["sv << util::hex(eglGetError()).to_string_view() << ']'; @@ -316,7 +329,7 @@ display_t make_display(gbm::gbm_t::pointer gbm) { "EGL_KHR_create_context", "EGL_KHR_surfaceless_context", "EGL_EXT_image_dma_buf_import", - "EGL_KHR_image_pixmap" + // "EGL_KHR_image_pixmap" }; for(auto ext : extensions) { diff --git a/sunshine/platform/linux/graphics.h b/sunshine/platform/linux/graphics.h index 3513047f..d565f243 100644 --- a/sunshine/platform/linux/graphics.h +++ b/sunshine/platform/linux/graphics.h @@ -209,7 +209,7 @@ struct surface_descriptor_t { int pitch; }; -display_t make_display(gbm::gbm_t::pointer gbm); +display_t make_display(util::Either native_display); std::optional make_ctx(display_t::pointer display); std::optional import_source( diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp index 784dff64..d3b7616e 100644 --- a/sunshine/platform/linux/kmsgrab.cpp +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -295,7 +295,7 @@ public: auto file = entry.path().filename(); auto filestring = file.generic_u8string(); - if(std::string_view { filestring }.substr(0, 4) != "card"sv) { + if(filestring.size() < 4 || std::string_view { filestring }.substr(0, 4) != "card"sv) { continue; } @@ -464,7 +464,7 @@ public: return 0; } - capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) { + capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override { auto next_frame = std::chrono::steady_clock::now(); while(img) { diff --git a/sunshine/platform/linux/misc.cpp b/sunshine/platform/linux/misc.cpp index 0e80c379..24c8ceb6 100644 --- a/sunshine/platform/linux/misc.cpp +++ b/sunshine/platform/linux/misc.cpp @@ -139,6 +139,9 @@ std::string get_mac_address(const std::string_view &address) { } enum class source_e { +#ifdef SUNSHINE_BUILD_WAYLAND + WAYLAND, +#endif #ifdef SUNSHINE_BUILD_DRM KMS, #endif @@ -148,6 +151,15 @@ enum class source_e { }; static source_e source; +#ifdef SUNSHINE_BUILD_WAYLAND +std::vector wl_display_names(); +std::shared_ptr wl_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate); + +bool verify_wl() { + return !wl_display_names().empty(); +} +#endif + #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); @@ -168,6 +180,10 @@ bool verify_x11() { std::vector display_names() { switch(source) { +#ifdef SUNSHINE_BUILD_WAYLAND + case source_e::WAYLAND: + return wl_display_names(); +#endif #ifdef SUNSHINE_BUILD_DRM case source_e::KMS: return kms_display_names(); @@ -183,6 +199,10 @@ std::vector display_names() { std::shared_ptr display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) { switch(source) { +#ifdef SUNSHINE_BUILD_WAYLAND + case source_e::WAYLAND: + return wl_display(hwdevice_type, display_name, framerate); +#endif #ifdef SUNSHINE_BUILD_DRM case source_e::KMS: return kms_display(hwdevice_type, display_name, framerate); @@ -200,7 +220,13 @@ std::unique_ptr init() { // These are allowed to fail. gbm::init(); va::init(); - +#ifdef SUNSHINE_BUILD_WAYLAND + if(verify_wl()) { + BOOST_LOG(info) << "Using Wayland for screencasting"sv; + source = source_e::WAYLAND; + goto found_source; + } +#endif #ifdef SUNSHINE_BUILD_DRM if(verify_kms()) { BOOST_LOG(info) << "Using KMS for screencasting"sv; diff --git a/sunshine/platform/linux/wayland.cpp b/sunshine/platform/linux/wayland.cpp index f546c7d9..63bea5a8 100644 --- a/sunshine/platform/linux/wayland.cpp +++ b/sunshine/platform/linux/wayland.cpp @@ -3,8 +3,10 @@ #include +#include "graphics.h" #include "sunshine/main.h" #include "sunshine/platform/common.h" +#include "sunshine/round_robin.h" #include "sunshine/utility.h" #include "wayland.h" @@ -17,24 +19,6 @@ using namespace std::literals; #pragma GCC diagnostic ignored "-Wpedantic" namespace wl { -void test() { - display_t display; - - if(display.init()) { - return; - } - - interface_t interface { display.registry() }; - - display.roundtrip(); - - for(auto &monitor : interface.monitors) { - monitor->listen(interface.output_manager); - } - - display.roundtrip(); -} - int display_t::init(const char *display_name) { if(!display_name) { display_name = std::getenv("WAYLAND_DISPLAY"); @@ -72,31 +56,31 @@ inline void monitor_t::xdg_name(zxdg_output_v1 *, const char *name) { BOOST_LOG(info) << "Name: "sv << this->name; } -inline void monitor_t::xdg_description(zxdg_output_v1 *, const char *description) { +void monitor_t::xdg_description(zxdg_output_v1 *, const char *description) { this->description = description; BOOST_LOG(info) << "Found monitor: "sv << this->description; } -inline void monitor_t::xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y) { +void monitor_t::xdg_position(zxdg_output_v1 *, std::int32_t x, std::int32_t y) { viewport.offset_x = x; viewport.offset_y = y; BOOST_LOG(info) << "Offset: "sv << x << 'x' << y; } -inline void monitor_t::xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height) { +void monitor_t::xdg_size(zxdg_output_v1 *, std::int32_t width, std::int32_t height) { viewport.width = width; viewport.height = height; BOOST_LOG(info) << "Resolution: "sv << width << 'x' << height; } -inline void monitor_t::xdg_done(zxdg_output_v1 *) { +void monitor_t::xdg_done(zxdg_output_v1 *) { BOOST_LOG(info) << "All info about monitor ["sv << name << "] has been send"sv; } -inline void monitor_t::listen(zxdg_output_manager_v1 *output_manager) { +void monitor_t::listen(zxdg_output_manager_v1 *output_manager) { auto xdg_output = zxdg_output_manager_v1_get_xdg_output(output_manager, output); #define CLASS_CALL(x, y) x = (decltype(x))&y @@ -111,15 +95,17 @@ inline void monitor_t::listen(zxdg_output_manager_v1 *output_manager) { zxdg_output_v1_add_listener(xdg_output, &listener, this); } -inline interface_t::interface_t(wl_registry *registry) +interface_t::interface_t() noexcept : output_manager { nullptr }, listener { (decltype(wl_registry_listener::global))&interface_t::add_interface, (decltype(wl_registry_listener::global_remove))&interface_t::del_interface, - } { + } {} + +void interface_t::listen(wl_registry *registry) { wl_registry_add_listener(registry, &listener, this); } -inline void interface_t::add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version) { +void interface_t::add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version) { BOOST_LOG(debug) << "Available interface: "sv << interface << '(' << id << ") version "sv << version; if(!std::strcmp(interface, wl_output_interface.name)) { @@ -131,13 +117,149 @@ inline void interface_t::add_interface(wl_registry *registry, std::uint32_t id, else if(!std::strcmp(interface, zxdg_output_manager_v1_interface.name)) { BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; output_manager = (zxdg_output_manager_v1 *)wl_registry_bind(registry, id, &zxdg_output_manager_v1_interface, version); + + this->interface[XDG_OUTPUT] = true; + } + else if(!std::strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name)) { + BOOST_LOG(info) << "Found interface: "sv << interface << '(' << id << ") version "sv << version; + dmabuf_manager = (zwlr_export_dmabuf_manager_v1 *)wl_registry_bind(registry, id, &zwlr_export_dmabuf_manager_v1_interface, version); + + this->interface[WLR_EXPORT_DMABUF] = true; } } -inline void interface_t::del_interface(wl_registry *registry, uint32_t id) { +void interface_t::del_interface(wl_registry *registry, uint32_t id) { BOOST_LOG(info) << "Delete: "sv << id; } +dmabuf_t::dmabuf_t() + : status { REINIT }, frames {}, current_frame { &frames[0] }, listener { + (decltype(zwlr_export_dmabuf_frame_v1_listener::frame))&dmabuf_t::frame, + (decltype(zwlr_export_dmabuf_frame_v1_listener::object))&dmabuf_t::object, + (decltype(zwlr_export_dmabuf_frame_v1_listener::ready))&dmabuf_t::ready, + (decltype(zwlr_export_dmabuf_frame_v1_listener::cancel))&dmabuf_t::cancel, + } { +} + +int dmabuf_t::init(wl_display *display_p) { + display = egl::make_display(display_p); + + if(!display) { + return -1; + } + + auto ctx_opt = egl::make_ctx(display.get()); + + if(!ctx_opt) { + return -1; + } + + ctx = std::move(*ctx_opt); + + status = READY; + + return 0; +} + +void dmabuf_t::listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor) { + auto frame = zwlr_export_dmabuf_manager_v1_capture_output(dmabuf_manager, blend_cursor, output); + zwlr_export_dmabuf_frame_v1_add_listener(frame, &listener, this); + + status = WAITING; +} + +dmabuf_t::~dmabuf_t() { + for(auto &frame : frames) { + frame.destroy(); + } +} + +void dmabuf_t::frame( + zwlr_export_dmabuf_frame_v1 *frame, + std::uint32_t width, std::uint32_t height, + std::uint32_t x, std::uint32_t y, + std::uint32_t buffer_flags, std::uint32_t flags, + std::uint32_t format, + std::uint32_t high, std::uint32_t low, + std::uint32_t obj_count) { + auto next_frame = get_next_frame(); + + next_frame->format = format; + next_frame->width = width; + next_frame->height = height; + next_frame->obj_count = obj_count; + next_frame->frame = frame; +} + +void dmabuf_t::object( + zwlr_export_dmabuf_frame_v1 *frame, + std::uint32_t index, + std::int32_t fd, + std::uint32_t size, + std::uint32_t offset, + std::uint32_t stride, + std::uint32_t plane_index) { + auto next_frame = get_next_frame(); + + next_frame->fds[index] = fd; + next_frame->sizes[index] = size; + next_frame->strides[index] = stride; + next_frame->offsets[index] = offset; + next_frame->plane_indices[index] = plane_index; +} + +void dmabuf_t::ready( + zwlr_export_dmabuf_frame_v1 *frame, + std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec) { + auto next_frame = get_next_frame(); + + auto rgb_opt = egl::import_source(display.get(), + { + next_frame->fds[0], + (int)next_frame->width, + (int)next_frame->height, + (int)next_frame->offsets[0], + (int)next_frame->strides[0], + }); + + if(!rgb_opt) { + status = REINIT; + + return; + } + + next_frame->rgb = std::move(*rgb_opt); + + current_frame->destroy(); + current_frame = next_frame; + + status = READY; +} + +void dmabuf_t::cancel( + zwlr_export_dmabuf_frame_v1 *frame, + zwlr_export_dmabuf_frame_v1_cancel_reason reason) { + auto next_frame = get_next_frame(); + next_frame->destroy(); + + status = REINIT; +} + +void frame_t::destroy() { + if(frame) { + zwlr_export_dmabuf_frame_v1_destroy(frame); + } + + for(auto x = 0; x < obj_count; ++x) { + close(fds[x]); + } + + rgb = egl::rgb_t {}; + + frame = nullptr; + obj_count = 0; +} + } // namespace wl #pragma GCC diagnostic pop \ No newline at end of file diff --git a/sunshine/platform/linux/wayland.h b/sunshine/platform/linux/wayland.h index a0cfa86d..c55b3fab 100644 --- a/sunshine/platform/linux/wayland.h +++ b/sunshine/platform/linux/wayland.h @@ -1,11 +1,96 @@ #ifndef SUNSHINE_WAYLAND_H #define SUNSHINE_WAYLAND_H +#include + +#include #include +#include "graphics.h" + namespace wl { using display_internal_t = util::safe_ptr; +class frame_t { +public: + std::uint32_t format; + std::uint32_t width, height; + std::uint32_t obj_count; + std::uint32_t strides[4]; + std::uint32_t sizes[4]; + std::int32_t fds[4]; + std::uint32_t offsets[4]; + std::uint32_t plane_indices[4]; + + egl::rgb_t rgb; + + zwlr_export_dmabuf_frame_v1 *frame; + + void destroy(); +}; + +class dmabuf_t { +public: + enum status_e { + WAITING, + READY, + REINIT, + }; + + dmabuf_t(dmabuf_t &&) = delete; + dmabuf_t(const dmabuf_t &) = delete; + + dmabuf_t &operator=(const dmabuf_t &) = delete; + dmabuf_t &operator=(dmabuf_t &&) = delete; + + dmabuf_t(); + + int init(wl_display *display); + void listen(zwlr_export_dmabuf_manager_v1 *dmabuf_manager, wl_output *output, bool blend_cursor = false); + + ~dmabuf_t(); + + void frame( + zwlr_export_dmabuf_frame_v1 *frame, + std::uint32_t width, std::uint32_t height, + std::uint32_t x, std::uint32_t y, + std::uint32_t buffer_flags, std::uint32_t flags, + std::uint32_t format, + std::uint32_t high, std::uint32_t low, + std::uint32_t obj_count); + + void object( + zwlr_export_dmabuf_frame_v1 *frame, + std::uint32_t index, + std::int32_t fd, + std::uint32_t size, + std::uint32_t offset, + std::uint32_t stride, + std::uint32_t plane_index); + + void ready( + zwlr_export_dmabuf_frame_v1 *frame, + std::uint32_t tv_sec_hi, std::uint32_t tv_sec_lo, std::uint32_t tv_nsec); + + void cancel( + zwlr_export_dmabuf_frame_v1 *frame, + zwlr_export_dmabuf_frame_v1_cancel_reason reason); + + inline frame_t *get_next_frame() { + return current_frame == &frames[0] ? &frames[1] : &frames[0]; + } + + status_e status; + + egl::display_t display; + egl::ctx_t ctx; + + std::array frames; + frame_t *current_frame; + + zwlr_export_dmabuf_frame_v1_listener listener; +}; + class monitor_t { public: monitor_t(monitor_t &&) = delete; @@ -41,21 +126,37 @@ class interface_t { }; public: + enum interface_e { + XDG_OUTPUT, + WLR_EXPORT_DMABUF, + MAX_INTERFACES, + }; + interface_t(interface_t &&) = delete; interface_t(const interface_t &) = delete; interface_t &operator=(const interface_t &) = delete; interface_t &operator=(interface_t &&) = delete; - interface_t(wl_registry *registry); + interface_t() noexcept; + + void listen(wl_registry *registry); std::vector> monitors; + + zwlr_export_dmabuf_manager_v1 *dmabuf_manager; zxdg_output_manager_v1 *output_manager; + bool operator[](interface_e bit) const { + return interface[bit]; + } + private: void add_interface(wl_registry *registry, std::uint32_t id, const char *interface, std::uint32_t version); void del_interface(wl_registry *registry, uint32_t id); + std::bitset interface; + wl_registry_listener listener; }; @@ -74,6 +175,10 @@ public: // No need to manually free the registry wl_registry *registry(); + inline display_internal_t::pointer get() { + return display_internal.get(); + } + private: display_internal_t display_internal; }; diff --git a/sunshine/platform/linux/wlgrab.cpp b/sunshine/platform/linux/wlgrab.cpp new file mode 100644 index 00000000..7dc4d6bd --- /dev/null +++ b/sunshine/platform/linux/wlgrab.cpp @@ -0,0 +1,234 @@ +#include "sunshine/platform/common.h" + +#include "sunshine/main.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, int framerate) { + delay = std::chrono::nanoseconds { 1s } / framerate; + mem_type = hwdevice_type; + + if(display.init() || dmabuf.init(display.get())) { + 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; + } + + platf::capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr img, bool *cursor) override { + 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: + continue; + case platf::capture_e::ok: + img = snapshot_cb(img); + break; + default: + BOOST_LOG(error) << "Unrecognized capture status ["sv << (int)status << ']'; + return status; + } + } + + return platf::capture_e::ok; + } + + platf::capture_e snapshot(platf::img_t *img_out_base, 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->width != width || + current_frame->height != height) { + + return platf::capture_e::reinit; + } + + auto &rgb = current_frame->rgb; + + gl::ctx.BindTexture(GL_TEXTURE_2D, rgb->tex[0]); + gl::ctx.GetTextureSubImage(rgb->tex[0], 0, 0, 0, 0, width, height, 1, GL_BGRA, GL_UNSIGNED_BYTE, img_out_base->height * img_out_base->row_pitch, img_out_base->data); + gl::ctx.BindTexture(GL_TEXTURE_2D, 0); + + return platf::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::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); + } + + return std::make_shared(); + } + + platf::mem_type_e mem_type; + + std::chrono::nanoseconds delay; + + wl::display_t display; + interface_t interface; + dmabuf_t dmabuf; + + wl_output *output; +}; + +} // namespace wl + +namespace platf { +std::shared_ptr wl_display(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; + } + + auto wlr = std::make_shared(); + if(wlr->init(hwdevice_type, display_name, framerate)) { + 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 \ No newline at end of file diff --git a/third-party/wayland-protocols b/third-party/wayland-protocols deleted file mode 160000 index 7dffa6f3..00000000 --- a/third-party/wayland-protocols +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7dffa6f346ae32f7888177d74a45459997059767 diff --git a/third-party/wayland-protocols/wlr-export-dmabuf-unstable-v1.xml b/third-party/wayland-protocols/wlr-export-dmabuf-unstable-v1.xml new file mode 100644 index 00000000..751f7efb --- /dev/null +++ b/third-party/wayland-protocols/wlr-export-dmabuf-unstable-v1.xml @@ -0,0 +1,203 @@ + + + + Copyright © 2018 Rostislav Pehlivanov + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + An interface to capture surfaces in an efficient way by exporting DMA-BUFs. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This object is a manager with which to start capturing from sources. + + + + + Capture the next frame of a an entire output. + + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object represents a single DMA-BUF frame. + + If the capture is successful, the compositor will first send a "frame" + event, followed by one or several "object". When the frame is available + for readout, the "ready" event is sent. + + If the capture failed, the "cancel" event is sent. This can happen anytime + before the "ready" event. + + Once either a "ready" or a "cancel" event is received, the client should + destroy the frame. Once an "object" event is received, the client is + responsible for closing the associated file descriptor. + + All frames are read-only and may not be written into or altered. + + + + + Special flags that should be respected by the client. + + + + + + + Main event supplying the client with information about the frame. If the + capture didn't fail, this event is always emitted first before any other + events. + + This event is followed by a number of "object" as specified by the + "num_objects" argument. + + + + + + + + + + + + + + + + Event which serves to supply the client with the file descriptors + containing the data for each object. + + After receiving this event, the client must always close the file + descriptor as soon as they're done with it and even if the frame fails. + + + + + + + + + + + + This event is sent as soon as the frame is presented, indicating it is + available for reading. This event includes the time at which + presentation happened at. + + The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, + each component being an unsigned 32-bit value. Whole seconds are in + tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, + and the additional fractional part in tv_nsec as nanoseconds. Hence, + for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part + may have an arbitrary offset at start. + + After receiving this event, the client should destroy this object. + + + + + + + + + Indicates reason for cancelling the frame. + + + + + + + + + If the capture failed or if the frame is no longer valid after the + "frame" event has been emitted, this event will be used to inform the + client to scrap the frame. + + If the failure is temporary, the client may capture again the same + source. If the failure is permanent, any further attempts to capture the + same source will fail again. + + After receiving this event, the client should destroy this object. + + + + + + + Unreferences the frame. This request must be called as soon as its no + longer used. + + It can be called at any time by the client. The client will still have + to close any FDs it has been given. + + + + diff --git a/third-party/wayland-protocols/xdg-output-unstable-v1.xml b/third-party/wayland-protocols/xdg-output-unstable-v1.xml new file mode 100644 index 00000000..9a5b7900 --- /dev/null +++ b/third-party/wayland-protocols/xdg-output-unstable-v1.xml @@ -0,0 +1,220 @@ + + + + + Copyright © 2017 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol aims at describing outputs in a way which is more in line + with the concept of an output on desktop oriented systems. + + Some information are more specific to the concept of an output for + a desktop oriented system and may not make sense in other applications, + such as IVI systems for example. + + Typically, the global compositor space on a desktop system is made of + a contiguous or overlapping set of rectangular regions. + + Some of the information provided in this protocol might be identical + to their counterparts already available from wl_output, in which case + the information provided by this protocol should be preferred to their + equivalent in wl_output. The goal is to move the desktop specific + concepts (such as output location within the global compositor space, + the connector name and types, etc.) out of the core wl_output protocol. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible + changes may be added together with the corresponding interface + version bump. + Backward incompatible changes are done by bumping the version + number in the protocol and interface names and resetting the + interface version. Once the protocol is to be declared stable, + the 'z' prefix and the version number in the protocol and + interface names are removed and the interface version number is + reset. + + + + + A global factory interface for xdg_output objects. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output_manager object anymore. + + Any objects already created through this instance are not affected. + + + + + + This creates a new xdg_output object for the given wl_output. + + + + + + + + + An xdg_output describes part of the compositor geometry. + + This typically corresponds to a monitor that displays part of the + compositor space. + + For objects version 3 onwards, after all xdg_output properties have been + sent (when the object is created and when properties are updated), a + wl_output.done event is sent. This allows changes to the output + properties to be seen as atomic, even if they happen via multiple events. + + + + + Using this request a client can tell the server that it is not + going to use the xdg_output object anymore. + + + + + + The position event describes the location of the wl_output within + the global compositor space. + + The logical_position event is sent after creating an xdg_output + (see xdg_output_manager.get_xdg_output) and whenever the location + of the output changes within the global compositor space. + + + + + + + + The logical_size event describes the size of the output in the + global compositor space. + + For example, a surface without any buffer scale, transformation + nor rotation set, with the size matching the logical_size will + have the same size as the corresponding output when displayed. + + Most regular Wayland clients should not pay attention to the + logical size and would rather rely on xdg_shell interfaces. + + Some clients such as Xwayland, however, need this to configure + their surfaces in the global compositor space as the compositor + may apply a different scale from what is advertised by the output + scaling property (to achieve fractional scaling, for example). + + For example, for a wl_output mode 3840×2160 and a scale factor 2: + + - A compositor not scaling the surface buffers will advertise a + logical size of 3840×2160, + + - A compositor automatically scaling the surface buffers will + advertise a logical size of 1920×1080, + + - A compositor using a fractional scale of 1.5 will advertise a + logical size of 2560×1440. + + For example, for a wl_output mode 1920×1080 and a 90 degree rotation, + the compositor will advertise a logical size of 1080x1920. + + The logical_size event is sent after creating an xdg_output + (see xdg_output_manager.get_xdg_output) and whenever the logical + size of the output changes, either as a result of a change in the + applied scale or because of a change in the corresponding output + mode(see wl_output.mode) or transform (see wl_output.transform). + + + + + + + + This event is sent after all other properties of an xdg_output + have been sent. + + This allows changes to the xdg_output properties to be seen as + atomic, even if they happen via multiple events. + + For objects version 3 onwards, this event is deprecated. Compositors + are not required to send it anymore and must send wl_output.done + instead. + + + + + + + + Many compositors will assign names to their outputs, show them to the + user, allow them to be configured by name, etc. The client may wish to + know this name as well to offer the user similar behaviors. + + The naming convention is compositor defined, but limited to + alphanumeric characters and dashes (-). Each name is unique among all + wl_output globals, but if a wl_output global is destroyed the same name + may be reused later. The names will also remain consistent across + sessions with the same hardware and software configuration. + + Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do + not assume that the name is a reflection of an underlying DRM + connector, X11 connection, etc. + + The name event is sent after creating an xdg_output (see + xdg_output_manager.get_xdg_output). This event is only sent once per + xdg_output, and the name does not change over the lifetime of the + wl_output global. + + + + + + + Many compositors can produce human-readable descriptions of their + outputs. The client may wish to know this description as well, to + communicate the user for various purposes. + + The description is a UTF-8 string with no convention defined for its + contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11 + output via :1'. + + The description event is sent after creating an xdg_output (see + xdg_output_manager.get_xdg_output) and whenever the description + changes. The description is optional, and may not be sent at all. + + For objects of version 2 and lower, this event is only sent once per + xdg_output, and the description does not change over the lifetime of + the wl_output global. + + + + + +