From 6721155155f1927ec4b04dbdbecd7d256b21d799 Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 12 Aug 2021 21:11:40 +0200 Subject: [PATCH] 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