diff --git a/CMakeLists.txt b/CMakeLists.txt index 5570d278..fc28a460 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,20 +107,28 @@ else() list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json") find_package(X11) + find_package(LIBDRM) + + find_package(FFMPEG REQUIRED) if(X11_FOUND) include_directories(${X11_INCLUDE_DIR}) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/display.cpp) - else() + list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/x11grab.cpp) + elseif(LIBDRM_FOUND) + include_directories(${LIBDRM_INCLUDE_DIRS}) + list(APPEND PLATFORM_LIBRARIES ${LIBDRM_LIBRARIES}) + list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/kmsgrab.cpp) list(APPEND SUNSHINE_DEFINITIONS EGL_NO_X11=1) + else() + message(FATAL "Couldn't find either x11 or libdrm") endif() - find_package(FFMPEG REQUIRED) - list(APPEND PLATFORM_TARGET_FILES sunshine/platform/linux/publish.cpp sunshine/platform/linux/vaapi.h sunshine/platform/linux/vaapi.cpp + sunshine/platform/linux/graphics.h + sunshine/platform/linux/graphics.cpp sunshine/platform/linux/misc.h sunshine/platform/linux/misc.cpp sunshine/platform/linux/audio.cpp @@ -132,7 +140,7 @@ else() third-party/glad/include/glad/gl.h third-party/glad/include/glad/egl.h) - set(PLATFORM_LIBRARIES + list(APPEND PLATFORM_LIBRARIES dl evdev pulse diff --git a/cmake/FindLIBDRM.cmake b/cmake/FindLIBDRM.cmake new file mode 100644 index 00000000..fd0548c1 --- /dev/null +++ b/cmake/FindLIBDRM.cmake @@ -0,0 +1,21 @@ +# - Try to find Libdrm +# Once done this will define +# +# LIBDRM_FOUND - system has Libdrm +# LIBDRM_INCLUDE_DIRS - the Libdrm include directory +# LIBDRM_LIBRARIES - the libraries needed to use Libdrm +# LIBDRM_DEFINITIONS - Compiler switches required for using Libdrm + +# Use pkg-config to get the directories and then use these values +# in the find_path() and find_library() calls +find_package(PkgConfig) +pkg_check_modules(PC_LIBDRM libdrm) + +set(LIBDRM_DEFINITIONS ${PC_LIBDRM_CFLAGS}) + +find_path(LIBDRM_INCLUDE_DIRS drm.h PATHS ${PC_LIBDRM_INCLUDEDIR} ${PC_LIBDRM_INCLUDE_DIRS} PATH_SUFFIXES libdrm) +find_library(LIBDRM_LIBRARIES NAMES libdrm.so PATHS ${PC_LIBDRM_LIBDIR} ${PC_LIBDRM_LIBRARY_DIRS}) +mark_as_advanced(LIBDRM_INCLUDE_DIRS LIBDRM_LIBRARIES) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LIBDRM REQUIRED_VARS LIBDRM_LIBRARIES LIBDRM_INCLUDE_DIRS) \ No newline at end of file diff --git a/sunshine/platform/linux/graphics.cpp b/sunshine/platform/linux/graphics.cpp new file mode 100644 index 00000000..5093255c --- /dev/null +++ b/sunshine/platform/linux/graphics.cpp @@ -0,0 +1,687 @@ +#include "graphics.h" +#include "sunshine/video.h" + +extern "C" { +#include +} + +#include + +// I want to have as little build dependencies as possible +// There aren't that many DRM_FORMAT I need to use, so define them here +// +// They aren't likely to change any time soon. +#define fourcc_code(a, b, c, d) ((std::uint32_t)(a) | ((std::uint32_t)(b) << 8) | \ + ((std::uint32_t)(c) << 16) | ((std::uint32_t)(d) << 24)) +#define DRM_FORMAT_R8 fourcc_code('R', '8', ' ', ' ') /* [7:0] R */ +#define DRM_FORMAT_GR88 fourcc_code('G', 'R', '8', '8') /* [15:0] G:R 8:8 little endian */ +#define DRM_FORMAT_ARGB8888 fourcc_code('A', 'R', '2', '4') /* [31:0] A:R:G:B 8:8:8:8 little endian */ +#define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */ +#define DRM_FORMAT_XBGR8888 fourcc_code('X', 'B', '2', '4') /* [31:0] x:B:G:R 8:8:8:8 little endian */ + + +#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/opengl" + +#define STRINGIFY(x) #x +#define gl_drain_errors_helper(x) gl::drain_errors("line " STRINGIFY(x)) +#define gl_drain_errors gl_drain_errors_helper(__LINE__) + +using namespace std::literals; +namespace gl { +GladGLContext ctx; + +void drain_errors(const std::string_view &prefix) { + GLenum err; + while((err = ctx.GetError()) != GL_NO_ERROR) { + BOOST_LOG(error) << "GL: "sv << prefix << ": ["sv << util::hex(err).to_string_view() << ']'; + } +} + +tex_t::~tex_t() { + if(!size() == 0) { + ctx.DeleteTextures(size(), begin()); + } +} + +tex_t tex_t::make(std::size_t count) { + tex_t textures { count }; + + ctx.GenTextures(textures.size(), textures.begin()); + + float color[] = { 0.0f, 0.0f, 0.0f, 1.0f }; + + for(auto tex : textures) { + gl::ctx.BindTexture(GL_TEXTURE_2D, tex); + gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // x + gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // y + gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl::ctx.TexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color); + } + + return textures; +} + +frame_buf_t::~frame_buf_t() { + if(begin()) { + ctx.DeleteFramebuffers(size(), begin()); + } +} + +frame_buf_t frame_buf_t::make(std::size_t count) { + frame_buf_t frame_buf { count }; + + ctx.GenFramebuffers(frame_buf.size(), frame_buf.begin()); + + return frame_buf; +} + +std::string shader_t::err_str() { + int length; + ctx.GetShaderiv(handle(), GL_INFO_LOG_LENGTH, &length); + + std::string string; + string.resize(length); + + ctx.GetShaderInfoLog(handle(), length, &length, string.data()); + + string.resize(length - 1); + + return string; +} + +util::Either shader_t::compile(const std::string_view &source, GLenum type) { + shader_t shader; + + auto data = source.data(); + GLint length = source.length(); + + shader._shader.el = ctx.CreateShader(type); + ctx.ShaderSource(shader.handle(), 1, &data, &length); + ctx.CompileShader(shader.handle()); + + int status = 0; + ctx.GetShaderiv(shader.handle(), GL_COMPILE_STATUS, &status); + + if(!status) { + return shader.err_str(); + } + + return shader; +} + +GLuint shader_t::handle() const { + return _shader.el; +} + +buffer_t buffer_t::make(util::buffer_t &&offsets, const char *block, const std::string_view &data) { + buffer_t buffer; + buffer._block = block; + buffer._size = data.size(); + buffer._offsets = std::move(offsets); + + ctx.GenBuffers(1, &buffer._buffer.el); + ctx.BindBuffer(GL_UNIFORM_BUFFER, buffer.handle()); + ctx.BufferData(GL_UNIFORM_BUFFER, data.size(), (const std::uint8_t *)data.data(), GL_DYNAMIC_DRAW); + + return buffer; +} + +GLuint buffer_t::handle() const { + return _buffer.el; +} + +const char *buffer_t::block() const { + return _block; +} + +void buffer_t::update(const std::string_view &view, std::size_t offset) { + ctx.BindBuffer(GL_UNIFORM_BUFFER, handle()); + ctx.BufferSubData(GL_UNIFORM_BUFFER, offset, view.size(), (const void *)view.data()); +} + +void buffer_t::update(std::string_view *members, std::size_t count, std::size_t offset) { + util::buffer_t buffer { _size }; + + for(int x = 0; x < count; ++x) { + auto val = members[x]; + + std::copy_n((const std::uint8_t *)val.data(), val.size(), &buffer[_offsets[x]]); + } + + update(util::view(buffer.begin(), buffer.end()), offset); +} + +std::string program_t::err_str() { + int length; + ctx.GetProgramiv(handle(), GL_INFO_LOG_LENGTH, &length); + + std::string string; + string.resize(length); + + ctx.GetShaderInfoLog(handle(), length, &length, string.data()); + + string.resize(length - 1); + + return string; +} + +util::Either program_t::link(const shader_t &vert, const shader_t &frag) { + program_t program; + + program._program.el = ctx.CreateProgram(); + + ctx.AttachShader(program.handle(), vert.handle()); + ctx.AttachShader(program.handle(), frag.handle()); + + // p_handle stores a copy of the program handle, since program will be moved before + // the fail guard funcion is called. + auto fg = util::fail_guard([p_handle = program.handle(), &vert, &frag]() { + ctx.DetachShader(p_handle, vert.handle()); + ctx.DetachShader(p_handle, frag.handle()); + }); + + ctx.LinkProgram(program.handle()); + + int status = 0; + ctx.GetProgramiv(program.handle(), GL_LINK_STATUS, &status); + + if(!status) { + return program.err_str(); + } + + return program; +} + +void program_t::bind(const buffer_t &buffer) { + ctx.UseProgram(handle()); + auto i = ctx.GetUniformBlockIndex(handle(), buffer.block()); + + ctx.BindBufferBase(GL_UNIFORM_BUFFER, i, buffer.handle()); +} + +std::optional program_t::uniform(const char *block, std::pair *members, std::size_t count) { + auto i = ctx.GetUniformBlockIndex(handle(), block); + if(i == GL_INVALID_INDEX) { + BOOST_LOG(error) << "Couldn't find index of ["sv << block << ']'; + return std::nullopt; + } + + int size; + ctx.GetActiveUniformBlockiv(handle(), i, GL_UNIFORM_BLOCK_DATA_SIZE, &size); + + bool error_flag = false; + + util::buffer_t offsets { count }; + auto indices = (std::uint32_t *)alloca(count * sizeof(std::uint32_t)); + auto names = (const char **)alloca(count * sizeof(const char *)); + auto names_p = names; + + std::for_each_n(members, count, [names_p](auto &member) mutable { + *names_p++ = std::get<0>(member); + }); + + std::fill_n(indices, count, GL_INVALID_INDEX); + ctx.GetUniformIndices(handle(), count, names, indices); + + for(int x = 0; x < count; ++x) { + if(indices[x] == GL_INVALID_INDEX) { + error_flag = true; + + BOOST_LOG(error) << "Couldn't find ["sv << block << '.' << members[x].first << ']'; + } + } + + if(error_flag) { + return std::nullopt; + } + + ctx.GetActiveUniformsiv(handle(), count, indices, GL_UNIFORM_OFFSET, offsets.begin()); + util::buffer_t buffer { (std::size_t)size }; + + for(int x = 0; x < count; ++x) { + auto val = std::get<1>(members[x]); + + std::copy_n((const std::uint8_t *)val.data(), val.size(), &buffer[offsets[x]]); + } + + return buffer_t::make(std::move(offsets), block, std::string_view { (char *)buffer.begin(), buffer.size() }); +} + +GLuint program_t::handle() const { + return _program.el; +} + +} // namespace gl + +namespace gbm { +device_destroy_fn device_destroy; +create_device_fn create_device; + +int init() { + static void *handle { nullptr }; + static bool funcs_loaded = false; + + if(funcs_loaded) return 0; + + if(!handle) { + handle = dyn::handle({ "libgbm.so.1", "libgbm.so" }); + if(!handle) { + return -1; + } + } + + std::vector> funcs { + { (GLADapiproc *)&device_destroy, "gbm_device_destroy" }, + { (GLADapiproc *)&create_device, "gbm_create_device" }, + }; + + if(dyn::load(handle, funcs)) { + return -1; + } + + funcs_loaded = true; + return 0; +} +} // namespace gbm + +namespace egl { +constexpr auto EGL_LINUX_DMA_BUF_EXT = 0x3270; +constexpr auto EGL_LINUX_DRM_FOURCC_EXT = 0x3271; +constexpr auto EGL_DMA_BUF_PLANE0_FD_EXT = 0x3272; +constexpr auto EGL_DMA_BUF_PLANE0_OFFSET_EXT = 0x3273; +constexpr auto EGL_DMA_BUF_PLANE0_PITCH_EXT = 0x3274; + +bool fail() { + return eglGetError() != EGL_SUCCESS; +} + +display_t make_display(gbm::gbm_t::pointer gbm) { + constexpr auto EGL_PLATFORM_GBM_MESA = 0x31D7; + + display_t display = eglGetPlatformDisplay(EGL_PLATFORM_GBM_MESA, gbm, nullptr); + + if(fail()) { + BOOST_LOG(error) << "Couldn't open EGL display: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return nullptr; + } + + int major, minor; + if(!eglInitialize(display.get(), &major, &minor)) { + BOOST_LOG(error) << "Couldn't initialize EGL display: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return nullptr; + } + + const char *extension_st = eglQueryString(display.get(), EGL_EXTENSIONS); + const char *version = eglQueryString(display.get(), EGL_VERSION); + const char *vendor = eglQueryString(display.get(), EGL_VENDOR); + const char *apis = eglQueryString(display.get(), EGL_CLIENT_APIS); + + BOOST_LOG(debug) << "EGL: ["sv << vendor << "]: version ["sv << version << ']'; + BOOST_LOG(debug) << "API's supported: ["sv << apis << ']'; + + const char *extensions[] { + "EGL_KHR_create_context", + "EGL_KHR_surfaceless_context", + "EGL_EXT_image_dma_buf_import", + "EGL_KHR_image_pixmap" + }; + + for(auto ext : extensions) { + if(!std::strstr(extension_st, ext)) { + BOOST_LOG(error) << "Missing extension: ["sv << ext << ']'; + return nullptr; + } + } + + return display; +} + +std::optional make_ctx(display_t::pointer display) { + constexpr int conf_attr[] { + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE + }; + + int count; + EGLConfig conf; + if(!eglChooseConfig(display, conf_attr, &conf, 1, &count)) { + BOOST_LOG(error) << "Couldn't set config attributes: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return std::nullopt; + } + + if(!eglBindAPI(EGL_OPENGL_API)) { + BOOST_LOG(error) << "Couldn't bind API: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return std::nullopt; + } + + constexpr int attr[] { + EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE + }; + + ctx_t ctx { display, eglCreateContext(display, conf, EGL_NO_CONTEXT, attr) }; + if(fail()) { + BOOST_LOG(error) << "Couldn't create EGL context: ["sv << util::hex(eglGetError()).to_string_view() << ']'; + return std::nullopt; + } + + TUPLE_EL_REF(ctx_p, 1, ctx.el); + if(!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx_p)) { + BOOST_LOG(error) << "Couldn't make current display"sv; + return std::nullopt; + } + + if(!gladLoadGLContext(&gl::ctx, eglGetProcAddress)) { + BOOST_LOG(error) << "Couldn't load OpenGL library"sv; + return std::nullopt; + } + + BOOST_LOG(debug) << "GL: vendor: "sv << gl::ctx.GetString(GL_VENDOR); + BOOST_LOG(debug) << "GL: renderer: "sv << gl::ctx.GetString(GL_RENDERER); + BOOST_LOG(debug) << "GL: version: "sv << gl::ctx.GetString(GL_VERSION); + BOOST_LOG(debug) << "GL: shader: "sv << gl::ctx.GetString(GL_SHADING_LANGUAGE_VERSION); + + gl::ctx.PixelStorei(GL_UNPACK_ALIGNMENT, 1); + + return ctx; +} +std::optional import_source(display_t::pointer egl_display, const surface_descriptor_t &xrgb) { + EGLAttrib img_attr_planes[13] { + EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_XRGB8888, + EGL_WIDTH, xrgb.width, + EGL_HEIGHT, xrgb.height, + EGL_DMA_BUF_PLANE0_FD_EXT, xrgb.fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, xrgb.offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, xrgb.pitch, + EGL_NONE + }; + + rgb_t rgb { + egl_display, + eglCreateImage(egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, img_attr_planes), + gl::tex_t::make(1) + }; + + if(!rgb->xrgb8) { + BOOST_LOG(error) << "Couldn't import RGB Image: "sv << util::hex(eglGetError()).to_string_view(); + + return std::nullopt; + } + + gl::ctx.BindTexture(GL_TEXTURE_2D, rgb->tex[0]); + gl::ctx.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, rgb->xrgb8); + + gl::ctx.BindTexture(GL_TEXTURE_2D, 0); + + gl_drain_errors; + + return rgb; +} + +std::optional import_target(display_t::pointer egl_display, std::array &&fds, const surface_descriptor_t &r8, const surface_descriptor_t &gr88) { + int img_attr_planes[2][13] { + { EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_R8, + EGL_WIDTH, r8.width, + EGL_HEIGHT, r8.height, + EGL_DMA_BUF_PLANE0_FD_EXT, r8.fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, r8.offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, r8.pitch, + EGL_NONE }, + + { EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_GR88, + EGL_WIDTH, gr88.width, + EGL_HEIGHT, gr88.height, + EGL_DMA_BUF_PLANE0_FD_EXT, r8.fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, gr88.offset, + EGL_DMA_BUF_PLANE0_PITCH_EXT, gr88.pitch, + EGL_NONE }, + }; + + nv12_t nv12 { + egl_display, + eglCreateImageKHR(egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, img_attr_planes[0]), + eglCreateImageKHR(egl_display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, img_attr_planes[1]), + gl::tex_t::make(2), + gl::frame_buf_t::make(2), + std::move(fds) + }; + + if(!nv12->r8 || !nv12->bg88) { + BOOST_LOG(error) << "Couldn't create KHR Image"sv; + + return std::nullopt; + } + + gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[0]); + gl::ctx.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, nv12->r8); + + gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[1]); + gl::ctx.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, nv12->bg88); + + nv12->buf.bind(std::begin(nv12->tex), std::end(nv12->tex)); + + gl_drain_errors; + + return nv12; +} + +void egl_t::set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) { + video::color_t *color_p; + switch(colorspace) { + case 5: // SWS_CS_SMPTE170M + color_p = &video::colors[0]; + break; + case 1: // SWS_CS_ITU709 + color_p = &video::colors[2]; + break; + case 9: // SWS_CS_BT2020 + default: + BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; + color_p = &video::colors[0]; + }; + + if(color_range > 1) { + // Full range + ++color_p; + } + + std::string_view members[] { + util::view(color_p->color_vec_y), + util::view(color_p->color_vec_u), + util::view(color_p->color_vec_v), + util::view(color_p->range_y), + util::view(color_p->range_uv), + }; + + color_matrix.update(members, sizeof(members) / sizeof(decltype(members[0]))); +} + +int egl_t::init(int in_width, int in_height, file_t &&fd) { + file = std::move(fd); + + if(!gbm::create_device) { + BOOST_LOG(warning) << "libgbm not initialized"sv; + return -1; + } + + 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; + } + + display = make_display(gbm.get()); + if(!display) { + return -1; + } + + auto ctx_opt = make_ctx(display.get()); + if(!ctx_opt) { + return -1; + } + + ctx = std::move(*ctx_opt); + + { + const char *sources[] { + SUNSHINE_SHADERS_DIR "/ConvertUV.frag", + SUNSHINE_SHADERS_DIR "/ConvertUV.vert", + SUNSHINE_SHADERS_DIR "/ConvertY.frag", + SUNSHINE_SHADERS_DIR "/Scene.vert", + SUNSHINE_SHADERS_DIR "/Scene.frag", + }; + + GLenum shader_type[2] { + GL_FRAGMENT_SHADER, + GL_VERTEX_SHADER, + }; + + constexpr auto count = sizeof(sources) / sizeof(const char *); + + util::Either compiled_sources[count]; + + bool error_flag = false; + for(int x = 0; x < count; ++x) { + auto &compiled_source = compiled_sources[x]; + + compiled_source = gl::shader_t::compile(read_file(sources[x]), shader_type[x % 2]); + gl_drain_errors; + + if(compiled_source.has_right()) { + BOOST_LOG(error) << sources[x] << ": "sv << compiled_source.right(); + error_flag = true; + } + } + + if(error_flag) { + return -1; + } + + 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; + } + + // UV - shader + this->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; + } + + // Y - shader + this->program[0] = std::move(program.left()); + } + + auto color_p = &video::colors[0]; + std::pair members[] { + std::make_pair("color_vec_y", util::view(color_p->color_vec_y)), + std::make_pair("color_vec_u", util::view(color_p->color_vec_u)), + std::make_pair("color_vec_v", util::view(color_p->color_vec_v)), + std::make_pair("range_y", util::view(color_p->range_y)), + 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]))); + if(!color_matrix) { + return -1; + } + + this->color_matrix = std::move(*color_matrix); + + tex_in = gl::tex_t::make(1); + + this->in_width = in_width; + this->in_height = in_height; + return 0; +} + +int egl_t::convert(platf::img_t &img) { + auto tex = tex_in[0]; + + 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); + + GLenum attachments[] { + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1 + }; + + for(int x = 0; x < sizeof(attachments) / sizeof(decltype(attachments[0])); ++x) { + gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]); + gl::ctx.DrawBuffers(1, &attachments[x]); + + auto status = gl::ctx.CheckFramebufferStatus(GL_FRAMEBUFFER); + if(status != GL_FRAMEBUFFER_COMPLETE) { + BOOST_LOG(error) << "Pass "sv << x << ": CheckFramebufferStatus() --> [0x"sv << util::hex(status).to_string_view() << ']'; + return -1; + } + + gl::ctx.BindTexture(GL_TEXTURE_2D, tex); + + 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.DrawArrays(GL_TRIANGLES, 0, 3); + } + + return 0; +} + +int egl_t::_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; + + return -1; + } + + // 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) { + av_frame_free(&frame); +} \ No newline at end of file diff --git a/sunshine/platform/linux/graphics.h b/sunshine/platform/linux/graphics.h new file mode 100644 index 00000000..f0f05883 --- /dev/null +++ b/sunshine/platform/linux/graphics.h @@ -0,0 +1,264 @@ +#ifndef SUNSHINE_PLATFORM_LINUX_OPENGL_H +#define SUNSHINE_PLATFORM_LINUX_OPENGL_H + +#include +#include + +#include +#include + +#include "misc.h" +#include "sunshine/main.h" +#include "sunshine/platform/common.h" +#include "sunshine/utility.h" + +extern "C" int close(int __fd); + +struct AVFrame; +void free_frame(AVFrame *frame); + +using frame_t = util::safe_ptr; + +namespace gl { +extern GladGLContext ctx; +void drain_errors(const std::string_view &prefix); + +class tex_t : public util::buffer_t { + using util::buffer_t::buffer_t; + +public: + tex_t(tex_t &&) = default; + tex_t &operator=(tex_t &&) = default; + + ~tex_t(); + + static tex_t make(std::size_t count); +}; + +class frame_buf_t : public util::buffer_t { + using util::buffer_t::buffer_t; + +public: + frame_buf_t(frame_buf_t &&) = default; + frame_buf_t &operator=(frame_buf_t &&) = default; + + ~frame_buf_t(); + + static frame_buf_t make(std::size_t count); + + template + void bind(It it_begin, It it_end) { + using namespace std::literals; + if(std::distance(it_begin, it_end) > size()) { + BOOST_LOG(warning) << "To many elements to bind"sv; + return; + } + + int x = 0; + std::for_each(it_begin, it_end, [&](auto tex) { + ctx.BindFramebuffer(GL_FRAMEBUFFER, (*this)[x]); + ctx.BindTexture(GL_TEXTURE_2D, tex); + + ctx.FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, tex, 0); + + ++x; + }); + } +}; + +class shader_t { + KITTY_USING_MOVE_T(shader_internal_t, GLuint, std::numeric_limits::max(), { + if(el != std::numeric_limits::max()) { + ctx.DeleteShader(el); + } + }); + +public: + std::string err_str(); + + static util::Either compile(const std::string_view &source, GLenum type); + + GLuint handle() const; + +private: + shader_internal_t _shader; +}; + +class buffer_t { + KITTY_USING_MOVE_T(buffer_internal_t, GLuint, std::numeric_limits::max(), { + if(el != std::numeric_limits::max()) { + ctx.DeleteBuffers(1, &el); + } + }); + +public: + static buffer_t make(util::buffer_t &&offsets, const char *block, const std::string_view &data); + + GLuint handle() const; + + const char *block() const; + + void update(const std::string_view &view, std::size_t offset = 0); + void update(std::string_view *members, std::size_t count, std::size_t offset = 0); + +private: + const char *_block; + + std::size_t _size; + + util::buffer_t _offsets; + + buffer_internal_t _buffer; +}; + +class program_t { + KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits::max(), { + if(el != std::numeric_limits::max()) { + ctx.DeleteProgram(el); + } + }); + +public: + std::string err_str(); + + static util::Either link(const shader_t &vert, const shader_t &frag); + + void bind(const buffer_t &buffer); + + std::optional uniform(const char *block, std::pair *members, std::size_t count); + + GLuint handle() const; + +private: + program_internal_t _program; +}; +} // namespace gl + +namespace gbm { +struct device; +typedef void (*device_destroy_fn)(device *gbm); +typedef device *(*create_device_fn)(int fd); + +extern device_destroy_fn device_destroy; +extern create_device_fn create_device; + +using gbm_t = util::dyn_safe_ptr; + +int init(); + +} // namespace gbm + +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; + + gl::tex_t tex; +}; + +struct nv12_img_t { + display_t::pointer display; + EGLImage r8; + EGLImage bg88; + + gl::tex_t tex; + gl::frame_buf_t buf; + + // sizeof(va::DRMPRIMESurfaceDescriptor::objects) / sizeof(va::DRMPRIMESurfaceDescriptor::objects[0]); + static constexpr std::size_t num_fds = 4; + + std::array fds; +}; + +KITTY_USING_MOVE_T(rgb_t, rgb_img_t, , { + if(el.xrgb8) { + eglDestroyImage(el.display, el.xrgb8); + } +}); + +KITTY_USING_MOVE_T(nv12_t, nv12_img_t, , { + if(el.r8) { + eglDestroyImageKHR(el.display, el.r8); + } + + if(el.bg88) { + eglDestroyImageKHR(el.display, el.bg88); + } +}); + +KITTY_USING_MOVE_T(ctx_t, (std::tuple), , { + TUPLE_2D_REF(disp, ctx, el); + if(ctx) { + if(ctx == eglGetCurrentContext()) { + eglMakeCurrent(disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + } + eglDestroyContext(disp, ctx); + } +}); + +struct surface_descriptor_t { + int fd; + + int width; + int height; + int offset; + int pitch; +}; + +display_t make_display(gbm::gbm_t::pointer gbm); +std::optional make_ctx(display_t::pointer display); + +std::optional import_source( + display_t::pointer egl_display, + const surface_descriptor_t &xrgb); + +std::optional import_target( + display_t::pointer egl_display, + std::array &&fds, + const surface_descriptor_t &r8, const surface_descriptor_t &gr88); + +class egl_t : public platf::hwdevice_t { +public: + void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override; + + int init(int in_width, int in_height, file_t &&fd); + + int convert(platf::img_t &img) override; + + /** + * Any specialization needs to populate nv12_t nv12 + * Then call this function + */ + int _set_frame(AVFrame *frame); + + ~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::program_t program[2]; + gl::buffer_t color_matrix; +}; + +bool fail(); +} // namespace egl + +#endif \ No newline at end of file diff --git a/sunshine/platform/linux/kmsgrab.cpp b/sunshine/platform/linux/kmsgrab.cpp new file mode 100644 index 00000000..3cd51e5c --- /dev/null +++ b/sunshine/platform/linux/kmsgrab.cpp @@ -0,0 +1,248 @@ +#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 \ No newline at end of file diff --git a/sunshine/platform/linux/vaapi.cpp b/sunshine/platform/linux/vaapi.cpp index c9d0426b..905c0bbe 100644 --- a/sunshine/platform/linux/vaapi.cpp +++ b/sunshine/platform/linux/vaapi.cpp @@ -4,42 +4,21 @@ #include #include -#include extern "C" { #include } +#include "graphics.h" #include "misc.h" #include "sunshine/config.h" #include "sunshine/main.h" #include "sunshine/platform/common.h" #include "sunshine/utility.h" -#include "sunshine/video.h" - -// I want to have as little build dependencies as possible -// There aren't that many DRM_FORMAT I need to use, so define them here -// -// They aren't likely to change any time soon. -#define fourcc_code(a, b, c, d) ((std::uint32_t)(a) | ((std::uint32_t)(b) << 8) | \ - ((std::uint32_t)(c) << 16) | ((std::uint32_t)(d) << 24)) -#define DRM_FORMAT_R8 fourcc_code('R', '8', ' ', ' ') /* [7:0] R */ -#define DRM_FORMAT_GR88 fourcc_code('G', 'R', '8', '8') /* [15:0] G:R 8:8 little endian */ - - -#define SUNSHINE_SHADERS_DIR SUNSHINE_ASSETS_DIR "/shaders/opengl" - -#define STRINGIFY(x) #x -#define gl_drain_errors_helper(x) gl::drain_errors("line " STRINGIFY(x)) -#define gl_drain_errors gl_drain_errors_helper(__LINE__) using namespace std::literals; -static void free_frame(AVFrame *frame) { - av_frame_free(&frame); -} - -using frame_t = util::safe_ptr; +extern "C" struct AVBufferRef; namespace va { constexpr auto SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 = 0x40000000; @@ -178,409 +157,35 @@ int init_drm() { funcs_loaded = true; return 0; } -} // namespace va - -namespace gbm { -struct device; -typedef void (*device_destroy_fn)(device *gbm); -typedef device *(*create_device_fn)(int fd); - -device_destroy_fn device_destroy; -create_device_fn create_device; - -int init() { - static void *handle { nullptr }; - static bool funcs_loaded = false; - - if(funcs_loaded) return 0; - - if(!handle) { - handle = dyn::handle({ "libgbm.so.1", "libgbm.so" }); - if(!handle) { - return -1; - } - } - - std::vector> funcs { - { (GLADapiproc *)&device_destroy, "gbm_device_destroy" }, - { (GLADapiproc *)&create_device, "gbm_create_device" }, - }; - - if(dyn::load(handle, funcs)) { - return -1; - } - - funcs_loaded = true; - return 0; -} - -} // namespace gbm - -namespace gl { -static GladGLContext ctx; - -void drain_errors(const std::string_view &prefix) { - GLenum err; - while((err = ctx.GetError()) != GL_NO_ERROR) { - BOOST_LOG(error) << "GL: "sv << prefix << ": ["sv << util::hex(err).to_string_view() << ']'; - } -} - -class tex_t : public util::buffer_t { - using util::buffer_t::buffer_t; - -public: - tex_t(tex_t &&) = default; - tex_t &operator=(tex_t &&) = default; - - ~tex_t() { - if(!size() == 0) { - ctx.DeleteTextures(size(), begin()); - } - } - - static tex_t make(std::size_t count) { - tex_t textures { count }; - - ctx.GenTextures(textures.size(), textures.begin()); - - float color[] = { 0.0f, 0.0f, 0.0f, 1.0f }; - - for(auto tex : textures) { - gl::ctx.BindTexture(GL_TEXTURE_2D, tex); - gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // x - gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // y - gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl::ctx.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - gl::ctx.TexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color); - } - - return textures; - } -}; - -class frame_buf_t : public util::buffer_t { - using util::buffer_t::buffer_t; - -public: - frame_buf_t(frame_buf_t &&) = default; - frame_buf_t &operator=(frame_buf_t &&) = default; - - ~frame_buf_t() { - if(begin()) { - ctx.DeleteFramebuffers(size(), begin()); - } - } - - static frame_buf_t make(std::size_t count) { - frame_buf_t frame_buf { count }; - - ctx.GenFramebuffers(frame_buf.size(), frame_buf.begin()); - - return frame_buf; - } - - template - void bind(It it_begin, It it_end) { - if(std::distance(it_begin, it_end) > size()) { - BOOST_LOG(warning) << "To many elements to bind"sv; - return; - } - - int x = 0; - std::for_each(it_begin, it_end, [&](auto tex) { - ctx.BindFramebuffer(GL_FRAMEBUFFER, (*this)[x]); - ctx.BindTexture(GL_TEXTURE_2D, tex); - - ctx.FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, tex, 0); - - ++x; - }); - } -}; - -class shader_t { - KITTY_USING_MOVE_T(shader_internal_t, GLuint, std::numeric_limits::max(), { - if(el != std::numeric_limits::max()) { - ctx.DeleteShader(el); - } - }); - -public: - std::string err_str() { - int length; - ctx.GetShaderiv(handle(), GL_INFO_LOG_LENGTH, &length); - - std::string string; - string.resize(length); - - ctx.GetShaderInfoLog(handle(), length, &length, string.data()); - - string.resize(length - 1); - - return string; - } - - static util::Either compile(const std::string_view &source, GLenum type) { - shader_t shader; - - auto data = source.data(); - GLint length = source.length(); - - shader._shader.el = ctx.CreateShader(type); - ctx.ShaderSource(shader.handle(), 1, &data, &length); - ctx.CompileShader(shader.handle()); - - int status = 0; - ctx.GetShaderiv(shader.handle(), GL_COMPILE_STATUS, &status); - - if(!status) { - return shader.err_str(); - } - - return shader; - } - - GLuint handle() const { - return _shader.el; - } - -private: - shader_internal_t _shader; -}; - -class buffer_t { - KITTY_USING_MOVE_T(buffer_internal_t, GLuint, std::numeric_limits::max(), { - if(el != std::numeric_limits::max()) { - ctx.DeleteBuffers(1, &el); - } - }); - -public: - static buffer_t make(util::buffer_t &&offsets, const char *block, const std::string_view &data) { - buffer_t buffer; - buffer._block = block; - buffer._size = data.size(); - buffer._offsets = std::move(offsets); - - ctx.GenBuffers(1, &buffer._buffer.el); - ctx.BindBuffer(GL_UNIFORM_BUFFER, buffer.handle()); - ctx.BufferData(GL_UNIFORM_BUFFER, data.size(), (const std::uint8_t *)data.data(), GL_DYNAMIC_DRAW); - - return buffer; - } - - GLuint handle() const { - return _buffer.el; - } - - const char *block() const { - return _block; - } - - void update(const std::string_view &view, std::size_t offset = 0) { - ctx.BindBuffer(GL_UNIFORM_BUFFER, handle()); - ctx.BufferSubData(GL_UNIFORM_BUFFER, offset, view.size(), (const void *)view.data()); - } - - void update(std::string_view *members, std::size_t count, std::size_t offset = 0) { - util::buffer_t buffer { _size }; - - for(int x = 0; x < count; ++x) { - auto val = members[x]; - - std::copy_n((const std::uint8_t *)val.data(), val.size(), &buffer[_offsets[x]]); - } - - update(util::view(buffer.begin(), buffer.end()), offset); - } - -private: - const char *_block; - - std::size_t _size; - - util::buffer_t _offsets; - - buffer_internal_t _buffer; -}; - -class program_t { - KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits::max(), { - if(el != std::numeric_limits::max()) { - ctx.DeleteProgram(el); - } - }); - -public: - std::string err_str() { - int length; - ctx.GetProgramiv(handle(), GL_INFO_LOG_LENGTH, &length); - - std::string string; - string.resize(length); - - ctx.GetShaderInfoLog(handle(), length, &length, string.data()); - - string.resize(length - 1); - - return string; - } - - static util::Either link(const shader_t &vert, const shader_t &frag) { - program_t program; - - program._program.el = ctx.CreateProgram(); - - ctx.AttachShader(program.handle(), vert.handle()); - ctx.AttachShader(program.handle(), frag.handle()); - - // p_handle stores a copy of the program handle, since program will be moved before - // the fail guard funcion is called. - auto fg = util::fail_guard([p_handle = program.handle(), &vert, &frag]() { - ctx.DetachShader(p_handle, vert.handle()); - ctx.DetachShader(p_handle, frag.handle()); - }); - - ctx.LinkProgram(program.handle()); - - int status = 0; - ctx.GetProgramiv(program.handle(), GL_LINK_STATUS, &status); - - if(!status) { - return program.err_str(); - } - - return program; - } - - void bind(const buffer_t &buffer) { - ctx.UseProgram(handle()); - auto i = ctx.GetUniformBlockIndex(handle(), buffer.block()); - - ctx.BindBufferBase(GL_UNIFORM_BUFFER, i, buffer.handle()); - } - - std::optional uniform(const char *block, std::pair *members, std::size_t count) { - auto i = ctx.GetUniformBlockIndex(handle(), block); - if(i == GL_INVALID_INDEX) { - BOOST_LOG(error) << "Couldn't find index of ["sv << block << ']'; - return std::nullopt; - } - - int size; - ctx.GetActiveUniformBlockiv(handle(), i, GL_UNIFORM_BLOCK_DATA_SIZE, &size); - - bool error_flag = false; - - util::buffer_t offsets { count }; - auto indices = (std::uint32_t *)alloca(count * sizeof(std::uint32_t)); - auto names = (const char **)alloca(count * sizeof(const char *)); - auto names_p = names; - - std::for_each_n(members, count, [names_p](auto &member) mutable { - *names_p++ = std::get<0>(member); - }); - - std::fill_n(indices, count, GL_INVALID_INDEX); - ctx.GetUniformIndices(handle(), count, names, indices); - - for(int x = 0; x < count; ++x) { - if(indices[x] == GL_INVALID_INDEX) { - error_flag = true; - - BOOST_LOG(error) << "Couldn't find ["sv << block << '.' << members[x].first << ']'; - } - } - - if(error_flag) { - return std::nullopt; - } - - ctx.GetActiveUniformsiv(handle(), count, indices, GL_UNIFORM_OFFSET, offsets.begin()); - util::buffer_t buffer { (std::size_t)size }; - - for(int x = 0; x < count; ++x) { - auto val = std::get<1>(members[x]); - - std::copy_n((const std::uint8_t *)val.data(), val.size(), &buffer[offsets[x]]); - } - - return buffer_t::make(std::move(offsets), block, std::string_view { (char *)buffer.begin(), buffer.size() }); - } - - GLuint handle() const { - return _program.el; - } - -private: - program_internal_t _program; -}; -} // namespace gl - -namespace platf { -namespace egl { - -constexpr auto EGL_LINUX_DMA_BUF_EXT = 0x3270; -constexpr auto EGL_LINUX_DRM_FOURCC_EXT = 0x3271; -constexpr auto EGL_DMA_BUF_PLANE0_FD_EXT = 0x3272; -constexpr auto EGL_DMA_BUF_PLANE0_OFFSET_EXT = 0x3273; -constexpr auto EGL_DMA_BUF_PLANE0_PITCH_EXT = 0x3274; - -using display_t = util::dyn_safe_ptr_v2; -using gbm_t = util::dyn_safe_ptr; int vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf); -KITTY_USING_MOVE_T(file_t, int, -1, { - if(el >= 0) { - close(el); - } -}); - -struct nv12_img_t { - display_t::pointer display; - EGLImage r8; - EGLImage bg88; - - gl::tex_t tex; - gl::frame_buf_t buf; - - static constexpr std::size_t num_fds = - sizeof(va::DRMPRIMESurfaceDescriptor::objects) / sizeof(va::DRMPRIMESurfaceDescriptor::objects[0]); - - std::array fds; -}; - -KITTY_USING_MOVE_T(nv12_t, nv12_img_t, , { - if(el.r8) { - eglDestroyImageKHR(el.display, el.r8); - } - - if(el.bg88) { - eglDestroyImageKHR(el.display, el.bg88); - } -}); - -KITTY_USING_MOVE_T(ctx_t, (std::tuple), , { - TUPLE_2D_REF(disp, ctx, el); - if(ctx) { - if(ctx == eglGetCurrentContext()) { - eglMakeCurrent(disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - eglDestroyContext(disp, ctx); - } -}); - -bool fail() { - return eglGetError() != EGL_SUCCESS; -} - -class egl_t : public platf::hwdevice_t { +class va_t : public egl::egl_t { public: - std::optional import(va::VASurfaceID surface) { + int init(int in_width, int in_height, const char *render_device) { + if(!va::initialize) { + BOOST_LOG(warning) << "libva not initialized"sv; + return -1; + } + + data = (void *)vaapi_make_hwdevice_ctx; + + egl::file_t fd = open(render_device, O_RDWR); + if(fd.el < 0) { + char string[1024]; + BOOST_LOG(error) << "Couldn't open "sv << render_device << ": " << strerror_r(errno, string, sizeof(string)); + + return -1; + } + + return egl::egl_t::init(in_width, in_height, std::move(fd)); + } + + int set_frame(AVFrame *frame) override { // No deallocation necessary + va::DRMPRIMESurfaceDescriptor prime; + va::VASurfaceID surface = (std::uintptr_t)frame->data[3]; auto status = va::exportSurfaceHandle( va_display, @@ -592,382 +197,43 @@ public: BOOST_LOG(error) << "Couldn't export va surface handle: ["sv << (int)surface << "]: "sv << va::errorStr(status); - return std::nullopt; + return -1; } // 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; } - int img_attr_planes[2][13] { - { EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_R8, - EGL_WIDTH, (int)prime.width, - EGL_HEIGHT, (int)prime.height, - EGL_DMA_BUF_PLANE0_FD_EXT, prime.objects[prime.layers[0].object_index[0]].fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, (int)prime.layers[0].offset[0], - EGL_DMA_BUF_PLANE0_PITCH_EXT, (int)prime.layers[0].pitch[0], - EGL_NONE }, - - { EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_GR88, - EGL_WIDTH, (int)prime.width / 2, - EGL_HEIGHT, (int)prime.height / 2, - EGL_DMA_BUF_PLANE0_FD_EXT, prime.objects[prime.layers[0].object_index[1]].fd, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, (int)prime.layers[0].offset[1], - EGL_DMA_BUF_PLANE0_PITCH_EXT, (int)prime.layers[0].pitch[1], - EGL_NONE }, - }; - - nv12_t nv12 { + auto nv12_opt = egl::import_target( display.get(), - eglCreateImageKHR(display.get(), EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, img_attr_planes[0]), - eglCreateImageKHR(display.get(), EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, img_attr_planes[1]), - gl::tex_t::make(2), - gl::frame_buf_t::make(2), - std::move(fds) - }; + std::move(fds), + { + prime.objects[prime.layers[0].object_index[0]].fd, + (int)prime.width, + (int)prime.height, + (int)prime.layers[0].offset[0], + (int)prime.layers[0].pitch[0], + }, + { + prime.objects[prime.layers[0].object_index[1]].fd, + (int)prime.width / 2, + (int)prime.height / 2, + (int)prime.layers[0].offset[1], + (int)prime.layers[0].pitch[1], + }); - if(!nv12->r8 || !nv12->bg88) { - BOOST_LOG(error) << "Couldn't create KHR Image"sv; - - return std::nullopt; - } - - gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[0]); - gl::ctx.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, nv12->r8); - - gl::ctx.BindTexture(GL_TEXTURE_2D, nv12->tex[1]); - gl::ctx.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, nv12->bg88); - - nv12->buf.bind(std::begin(nv12->tex), std::end(nv12->tex)); - - gl_drain_errors; - - return nv12; - } - - void set_colorspace(std::uint32_t colorspace, std::uint32_t color_range) override { - video::color_t *color_p; - switch(colorspace) { - case 5: // SWS_CS_SMPTE170M - color_p = &video::colors[0]; - break; - case 1: // SWS_CS_ITU709 - color_p = &video::colors[2]; - break; - case 9: // SWS_CS_BT2020 - default: - BOOST_LOG(warning) << "Colorspace: ["sv << colorspace << "] not yet supported: switching to default"sv; - color_p = &video::colors[0]; - }; - - if(color_range > 1) { - // Full range - ++color_p; - } - - std::string_view members[] { - util::view(color_p->color_vec_y), - util::view(color_p->color_vec_u), - util::view(color_p->color_vec_v), - util::view(color_p->range_y), - util::view(color_p->range_uv), - }; - - color_matrix.update(members, sizeof(members) / sizeof(decltype(members[0]))); - } - - int init(int in_width, int in_height, const char *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; - } - - file.el = open(render_device, O_RDWR); - - if(file.el < 0) { - char error_buf[1024]; - BOOST_LOG(error) << "Couldn't open ["sv << render_device << "]: "sv << strerror_r(errno, error_buf, sizeof(error_buf)); - return -1; - } - - 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; - } - - constexpr auto EGL_PLATFORM_GBM_MESA = 0x31D7; - - display.reset(eglGetPlatformDisplay(EGL_PLATFORM_GBM_MESA, gbm.get(), nullptr)); - if(fail()) { - BOOST_LOG(error) << "Couldn't open EGL display: ["sv << util::hex(eglGetError()).to_string_view() << ']'; - return -1; - } - - int major, minor; - if(!eglInitialize(display.get(), &major, &minor)) { - BOOST_LOG(error) << "Couldn't initialize EGL display: ["sv << util::hex(eglGetError()).to_string_view() << ']'; - return -1; - } - - const char *extension_st = eglQueryString(display.get(), EGL_EXTENSIONS); - const char *version = eglQueryString(display.get(), EGL_VERSION); - const char *vendor = eglQueryString(display.get(), EGL_VENDOR); - const char *apis = eglQueryString(display.get(), EGL_CLIENT_APIS); - - BOOST_LOG(debug) << "EGL: ["sv << vendor << "]: version ["sv << version << ']'; - BOOST_LOG(debug) << "API's supported: ["sv << apis << ']'; - - const char *extensions[] { - "EGL_KHR_create_context", - "EGL_KHR_surfaceless_context", - "EGL_EXT_image_dma_buf_import", - "EGL_KHR_image_pixmap" - }; - - for(auto ext : extensions) { - if(!std::strstr(extension_st, ext)) { - BOOST_LOG(error) << "Missing extension: ["sv << ext << ']'; - return -1; - } - } - - constexpr int conf_attr[] { - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE - }; - - int count; - EGLConfig conf; - if(!eglChooseConfig(display.get(), conf_attr, &conf, 1, &count)) { - BOOST_LOG(error) << "Couldn't set config attributes: ["sv << util::hex(eglGetError()).to_string_view() << ']'; - return -1; - } - - if(!eglBindAPI(EGL_OPENGL_API)) { - BOOST_LOG(error) << "Couldn't bind API: ["sv << util::hex(eglGetError()).to_string_view() << ']'; - return -1; - } - - constexpr int attr[] { - EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE - }; - - ctx.el = { display.get(), eglCreateContext(display.get(), conf, EGL_NO_CONTEXT, attr) }; - if(fail()) { - BOOST_LOG(error) << "Couldn't create EGL context: ["sv << util::hex(eglGetError()).to_string_view() << ']'; - return -1; - } - - TUPLE_EL_REF(ctx_p, 1, ctx.el); - if(!eglMakeCurrent(display.get(), EGL_NO_SURFACE, EGL_NO_SURFACE, ctx_p)) { - BOOST_LOG(error) << "Couldn't make current display"sv; - return -1; - } - - if(!gladLoadGLContext(&gl::ctx, eglGetProcAddress)) { - BOOST_LOG(error) << "Couldn't load OpenGL library"sv; - return -1; - } - - BOOST_LOG(debug) << "GL: vendor: "sv << gl::ctx.GetString(GL_VENDOR); - BOOST_LOG(debug) << "GL: renderer: "sv << gl::ctx.GetString(GL_RENDERER); - BOOST_LOG(debug) << "GL: version: "sv << gl::ctx.GetString(GL_VERSION); - BOOST_LOG(debug) << "GL: shader: "sv << gl::ctx.GetString(GL_SHADING_LANGUAGE_VERSION); - - gl::ctx.PixelStorei(GL_UNPACK_ALIGNMENT, 1); - - { - const char *sources[] { - SUNSHINE_SHADERS_DIR "/ConvertUV.frag", - SUNSHINE_SHADERS_DIR "/ConvertUV.vert", - SUNSHINE_SHADERS_DIR "/ConvertY.frag", - SUNSHINE_SHADERS_DIR "/Scene.vert", - SUNSHINE_SHADERS_DIR "/Scene.frag", - }; - - GLenum shader_type[2] { - GL_FRAGMENT_SHADER, - GL_VERTEX_SHADER, - }; - - constexpr auto count = sizeof(sources) / sizeof(const char *); - - util::Either compiled_sources[count]; - - bool error_flag = false; - for(int x = 0; x < count; ++x) { - auto &compiled_source = compiled_sources[x]; - - compiled_source = gl::shader_t::compile(read_file(sources[x]), shader_type[x % 2]); - gl_drain_errors; - - if(compiled_source.has_right()) { - BOOST_LOG(error) << sources[x] << ": "sv << compiled_source.right(); - error_flag = true; - } - } - - if(error_flag) { - return -1; - } - - 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; - } - - // UV - shader - this->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; - } - - // Y - shader - this->program[0] = std::move(program.left()); - } - - auto color_p = &video::colors[0]; - std::pair members[] { - std::make_pair("color_vec_y", util::view(color_p->color_vec_y)), - std::make_pair("color_vec_u", util::view(color_p->color_vec_u)), - std::make_pair("color_vec_v", util::view(color_p->color_vec_v)), - std::make_pair("range_y", util::view(color_p->range_y)), - 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]))); - if(!color_matrix) { - return -1; - } - - this->color_matrix = std::move(*color_matrix); - - tex_in = gl::tex_t::make(1); - - this->in_width = in_width; - this->in_height = in_height; - - data = (void *)vaapi_make_hwdevice_ctx; - gl_drain_errors; - return 0; - } - - int convert(platf::img_t &img) override { - auto tex = tex_in[0]; - - 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); - - GLenum attachments[] { - GL_COLOR_ATTACHMENT0, - GL_COLOR_ATTACHMENT1 - }; - - for(int x = 0; x < sizeof(attachments) / sizeof(decltype(attachments[0])); ++x) { - gl::ctx.BindFramebuffer(GL_FRAMEBUFFER, nv12->buf[x]); - gl::ctx.DrawBuffers(1, &attachments[x]); - - auto status = gl::ctx.CheckFramebufferStatus(GL_FRAMEBUFFER); - if(status != GL_FRAMEBUFFER_COMPLETE) { - BOOST_LOG(error) << "Pass "sv << x << ": CheckFramebufferStatus() --> [0x"sv << util::hex(status).to_string_view() << ']'; - return -1; - } - - gl::ctx.BindTexture(GL_TEXTURE_2D, tex); - - 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.DrawArrays(GL_TRIANGLES, 0, 3); - } - - return 0; - } - - int set_frame(AVFrame *frame) override { - 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; - - return -1; - } - - va::VASurfaceID surface = (std::uintptr_t)frame->data[3]; - - auto nv12_opt = import(surface); if(!nv12_opt) { return -1; } nv12 = std::move(*nv12_opt); - // // 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; + return egl::egl_t::_set_frame(frame); } - ~egl_t() override { - if(gl::ctx.GetError) { - gl_drain_errors; - } - } - - int in_width, in_height; - int out_width, out_height; - int offsetX, offsetY; - - frame_t hwframe; - va::display_t::pointer va_display; - - file_t file; - gbm_t gbm; - display_t display; - ctx_t ctx; - - gl::tex_t tex_in; - nv12_t nv12; - gl::program_t program[2]; - gl::buffer_t color_matrix; }; /** @@ -1019,8 +285,8 @@ int vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf return -1; } - auto egl = (platf::egl::egl_t *)base; - auto fd = dup(egl->file.el); + auto va = (va::va_t *)base; + auto fd = dup(va->file.el); auto *priv = (VAAPIDevicePriv *)av_mallocz(sizeof(VAAPIDevicePriv)); priv->drm_fd = fd; @@ -1039,7 +305,7 @@ int vaapi_make_hwdevice_ctx(platf::hwdevice_t *base, AVBufferRef **hw_device_buf return -1; } - egl->va_display = display.get(); + va->va_display = display.get(); va::setErrorCallback(display.get(), __log, &error); va::setErrorCallback(display.get(), __log, &info); @@ -1071,7 +337,7 @@ 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 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)) { @@ -1080,8 +346,9 @@ std::shared_ptr make_hwdevice(int width, int height) { return egl; } -} // namespace egl +} // namespace va +namespace platf { std::unique_ptr init() { gbm::init(); va::init_drm(); diff --git a/sunshine/platform/linux/vaapi.h b/sunshine/platform/linux/vaapi.h index 10b1b3f1..f6f18389 100644 --- a/sunshine/platform/linux/vaapi.h +++ b/sunshine/platform/linux/vaapi.h @@ -2,7 +2,7 @@ #define SUNSHINE_DISPLAY_H #include "sunshine/platform/common.h" -namespace platf::egl { -std::shared_ptr make_hwdevice(int width, int height); -} // namespace platf::egl +namespace va { +std::shared_ptr make_hwdevice(int width, int height); +} // namespace va #endif \ No newline at end of file diff --git a/sunshine/platform/linux/display.cpp b/sunshine/platform/linux/x11grab.cpp similarity index 100% rename from sunshine/platform/linux/display.cpp rename to sunshine/platform/linux/x11grab.cpp