Screencast wlroots based compositors

This commit is contained in:
Loki
2021-08-25 16:09:42 +02:00
parent 05dcff4f87
commit ec184fb2ab
13 changed files with 972 additions and 54 deletions

View File

@@ -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<gbm::gbm_t::pointer, wl_display *> 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) {

View File

@@ -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<gbm::gbm_t::pointer, wl_display *> native_display);
std::optional<ctx_t> make_ctx(display_t::pointer display);
std::optional<rgb_t> import_source(

View File

@@ -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_t> img, bool *cursor) {
capture_e capture(snapshot_cb_t &&snapshot_cb, std::shared_ptr<img_t> img, bool *cursor) override {
auto next_frame = std::chrono::steady_clock::now();
while(img) {

View File

@@ -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<std::string> wl_display_names();
std::shared_ptr<display_t> 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<std::string> kms_display_names();
std::shared_ptr<display_t> kms_display(mem_type_e hwdevice_type, const std::string &display_name, int framerate);
@@ -168,6 +180,10 @@ bool verify_x11() {
std::vector<std::string> 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<std::string> display_names() {
std::shared_ptr<display_t> 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<deinit_t> 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;

View File

@@ -3,8 +3,10 @@
#include <cstdlib>
#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

View File

@@ -1,11 +1,96 @@
#ifndef SUNSHINE_WAYLAND_H
#define SUNSHINE_WAYLAND_H
#include <bitset>
#include <wlr-export-dmabuf-unstable-v1.h>
#include <xdg-output-unstable-v1.h>
#include "graphics.h"
namespace wl {
using display_internal_t = util::safe_ptr<wl_display, wl_display_disconnect>;
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<frame_t, 2> 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<std::unique_ptr<monitor_t>> 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<MAX_INTERFACES> 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;
};

View File

@@ -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<platf::img_t> 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<platf::img_t> alloc_img() override {
auto img = std::make_shared<img_t>();
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<platf::hwdevice_t> 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::hwdevice_t>();
}
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<display_t> 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<wl::wlr_t>();
if(wlr->init(hwdevice_type, display_name, framerate)) {
return nullptr;
}
return wlr;
}
std::vector<std::string> wl_display_names() {
std::vector<std::string> 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