mirror of
https://github.com/LizardByte/Sunshine.git
synced 2024-12-28 18:16:43 +00:00
Skeleton of grabbing image with kms
This commit is contained in:
parent
793e329fa5
commit
ac5f439839
@ -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
|
||||
|
21
cmake/FindLIBDRM.cmake
Normal file
21
cmake/FindLIBDRM.cmake
Normal file
@ -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)
|
687
sunshine/platform/linux/graphics.cpp
Normal file
687
sunshine/platform/linux/graphics.cpp
Normal file
@ -0,0 +1,687 @@
|
||||
#include "graphics.h"
|
||||
#include "sunshine/video.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
}
|
||||
|
||||
#include <fcntl.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 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, std::string> 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<GLint> &&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<std::uint8_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, std::string> 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<buffer_t> program_t::uniform(const char *block, std::pair<const char *, std::string_view> *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<GLint> 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<std::uint8_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<std::tuple<GLADapiproc *, const char *>> 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<ctx_t> 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<rgb_t> 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<nv12_t> import_target(display_t::pointer egl_display, std::array<file_t, nv12_img_t::num_fds> &&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<gl::shader_t, std::string> 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<const char *, std::string_view> 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);
|
||||
}
|
264
sunshine/platform/linux/graphics.h
Normal file
264
sunshine/platform/linux/graphics.h
Normal file
@ -0,0 +1,264 @@
|
||||
#ifndef SUNSHINE_PLATFORM_LINUX_OPENGL_H
|
||||
#define SUNSHINE_PLATFORM_LINUX_OPENGL_H
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include <glad/egl.h>
|
||||
#include <glad/gl.h>
|
||||
|
||||
#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<AVFrame, free_frame>;
|
||||
|
||||
namespace gl {
|
||||
extern GladGLContext ctx;
|
||||
void drain_errors(const std::string_view &prefix);
|
||||
|
||||
class tex_t : public util::buffer_t<GLuint> {
|
||||
using util::buffer_t<GLuint>::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<GLuint> {
|
||||
using util::buffer_t<GLuint>::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<class It>
|
||||
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<GLuint>::max(), {
|
||||
if(el != std::numeric_limits<GLuint>::max()) {
|
||||
ctx.DeleteShader(el);
|
||||
}
|
||||
});
|
||||
|
||||
public:
|
||||
std::string err_str();
|
||||
|
||||
static util::Either<shader_t, std::string> 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<GLuint>::max(), {
|
||||
if(el != std::numeric_limits<GLuint>::max()) {
|
||||
ctx.DeleteBuffers(1, &el);
|
||||
}
|
||||
});
|
||||
|
||||
public:
|
||||
static buffer_t make(util::buffer_t<GLint> &&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<GLint> _offsets;
|
||||
|
||||
buffer_internal_t _buffer;
|
||||
};
|
||||
|
||||
class program_t {
|
||||
KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
|
||||
if(el != std::numeric_limits<GLuint>::max()) {
|
||||
ctx.DeleteProgram(el);
|
||||
}
|
||||
});
|
||||
|
||||
public:
|
||||
std::string err_str();
|
||||
|
||||
static util::Either<program_t, std::string> link(const shader_t &vert, const shader_t &frag);
|
||||
|
||||
void bind(const buffer_t &buffer);
|
||||
|
||||
std::optional<buffer_t> uniform(const char *block, std::pair<const char *, std::string_view> *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<device, &device_destroy>;
|
||||
|
||||
int init();
|
||||
|
||||
} // namespace gbm
|
||||
|
||||
namespace egl {
|
||||
using display_t = util::dyn_safe_ptr_v2<void, EGLBoolean, &eglTerminate>;
|
||||
|
||||
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<file_t, num_fds> 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<display_t::pointer, EGLContext>), , {
|
||||
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<ctx_t> make_ctx(display_t::pointer display);
|
||||
|
||||
std::optional<rgb_t> import_source(
|
||||
display_t::pointer egl_display,
|
||||
const surface_descriptor_t &xrgb);
|
||||
|
||||
std::optional<nv12_t> import_target(
|
||||
display_t::pointer egl_display,
|
||||
std::array<file_t, nv12_img_t::num_fds> &&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
|
248
sunshine/platform/linux/kmsgrab.cpp
Normal file
248
sunshine/platform/linux/kmsgrab.cpp
Normal file
@ -0,0 +1,248 @@
|
||||
#include <drm_fourcc.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <xf86drm.h>
|
||||
#include <xf86drmMode.h>
|
||||
|
||||
#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<drmModePlaneRes, drmModeFreePlaneResources>;
|
||||
using plane_t = util::safe_ptr<drmModePlane, drmModeFreePlane>;
|
||||
using fb_t = util::safe_ptr<drmModeFB, drmModeFreeFB>;
|
||||
using fb2_t = util::safe_ptr<drmModeFB2, drmModeFreeFB2>;
|
||||
|
||||
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_t> 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<img_t> alloc_img() override {
|
||||
auto img = std::make_shared<kms_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::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_t> display(mem_type_e hwdevice_type, const std::string &display_name, int framerate) {
|
||||
auto disp = std::make_shared<kms::display_t>();
|
||||
|
||||
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<std::string> display_names() {
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace platf
|
@ -4,42 +4,21 @@
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <glad/egl.h>
|
||||
#include <glad/gl.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
}
|
||||
|
||||
#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<AVFrame, free_frame>;
|
||||
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<std::tuple<GLADapiproc *, const char *>> 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<GLuint> {
|
||||
using util::buffer_t<GLuint>::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<GLuint> {
|
||||
using util::buffer_t<GLuint>::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<class It>
|
||||
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<GLuint>::max(), {
|
||||
if(el != std::numeric_limits<GLuint>::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<shader_t, std::string> 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<GLuint>::max(), {
|
||||
if(el != std::numeric_limits<GLuint>::max()) {
|
||||
ctx.DeleteBuffers(1, &el);
|
||||
}
|
||||
});
|
||||
|
||||
public:
|
||||
static buffer_t make(util::buffer_t<GLint> &&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<std::uint8_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<GLint> _offsets;
|
||||
|
||||
buffer_internal_t _buffer;
|
||||
};
|
||||
|
||||
class program_t {
|
||||
KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
|
||||
if(el != std::numeric_limits<GLuint>::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<program_t, std::string> 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<buffer_t> uniform(const char *block, std::pair<const char *, std::string_view> *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<GLint> 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<std::uint8_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<void, EGLBoolean, &eglTerminate>;
|
||||
using gbm_t = util::dyn_safe_ptr<gbm::device, &gbm::device_destroy>;
|
||||
|
||||
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<file_t, num_fds> 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<display_t::pointer, EGLContext>), , {
|
||||
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<nv12_t> 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<file_t, nv12_img_t::num_fds> fds;
|
||||
std::array<egl::file_t, egl::nv12_img_t::num_fds> 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<gl::shader_t, std::string> 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<const char *, std::string_view> 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<platf::hwdevice_t> make_hwdevice(int width, int height) {
|
||||
auto egl = std::make_shared<egl_t>();
|
||||
auto egl = std::make_shared<va::va_t>();
|
||||
|
||||
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<platf::hwdevice_t> make_hwdevice(int width, int height) {
|
||||
|
||||
return egl;
|
||||
}
|
||||
} // namespace egl
|
||||
} // namespace va
|
||||
|
||||
namespace platf {
|
||||
std::unique_ptr<deinit_t> init() {
|
||||
gbm::init();
|
||||
va::init_drm();
|
||||
|
@ -2,7 +2,7 @@
|
||||
#define SUNSHINE_DISPLAY_H
|
||||
|
||||
#include "sunshine/platform/common.h"
|
||||
namespace platf::egl {
|
||||
std::shared_ptr<hwdevice_t> make_hwdevice(int width, int height);
|
||||
} // namespace platf::egl
|
||||
namespace va {
|
||||
std::shared_ptr<platf::hwdevice_t> make_hwdevice(int width, int height);
|
||||
} // namespace va
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user