mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-15 06:40:43 +01:00

It's not a problem if we fail to query DMA-BUF modifiers as we can still continue with modifier-less buffers. Bug: webrtc:13429 Change-Id: Ia718362bdc9eef1ebc54c06b24a2b65206aa873e Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/267003 Reviewed-by: Alexander Cooper <alcooper@chromium.org> Commit-Queue: Jan Grulich <grulja@gmail.com> Cr-Commit-Position: refs/heads/main@{#37342}
703 lines
24 KiB
C++
703 lines
24 KiB
C++
/*
|
|
* Copyright 2021 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "modules/desktop_capture/linux/wayland/egl_dmabuf.h"
|
|
|
|
#include <asm/ioctl.h>
|
|
#include <dlfcn.h>
|
|
#include <fcntl.h>
|
|
#include <libdrm/drm_fourcc.h>
|
|
#include <linux/types.h>
|
|
#include <spa/param/video/format-utils.h>
|
|
#include <unistd.h>
|
|
#include <xf86drm.h>
|
|
|
|
#include "absl/memory/memory.h"
|
|
#include "absl/types/optional.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "rtc_base/sanitizer.h"
|
|
#include "rtc_base/string_encode.h"
|
|
|
|
namespace webrtc {
|
|
|
|
// EGL
|
|
typedef EGLBoolean (*eglBindAPI_func)(EGLenum api);
|
|
typedef EGLContext (*eglCreateContext_func)(EGLDisplay dpy,
|
|
EGLConfig config,
|
|
EGLContext share_context,
|
|
const EGLint* attrib_list);
|
|
typedef EGLBoolean (*eglDestroyContext_func)(EGLDisplay display,
|
|
EGLContext context);
|
|
typedef EGLBoolean (*eglTerminate_func)(EGLDisplay display);
|
|
typedef EGLImageKHR (*eglCreateImageKHR_func)(EGLDisplay dpy,
|
|
EGLContext ctx,
|
|
EGLenum target,
|
|
EGLClientBuffer buffer,
|
|
const EGLint* attrib_list);
|
|
typedef EGLBoolean (*eglDestroyImageKHR_func)(EGLDisplay dpy,
|
|
EGLImageKHR image);
|
|
typedef EGLint (*eglGetError_func)(void);
|
|
typedef void* (*eglGetProcAddress_func)(const char*);
|
|
typedef EGLDisplay (*eglGetPlatformDisplayEXT_func)(EGLenum platform,
|
|
void* native_display,
|
|
const EGLint* attrib_list);
|
|
typedef EGLDisplay (*eglGetPlatformDisplay_func)(EGLenum platform,
|
|
void* native_display,
|
|
const EGLAttrib* attrib_list);
|
|
|
|
typedef EGLBoolean (*eglInitialize_func)(EGLDisplay dpy,
|
|
EGLint* major,
|
|
EGLint* minor);
|
|
typedef EGLBoolean (*eglMakeCurrent_func)(EGLDisplay dpy,
|
|
EGLSurface draw,
|
|
EGLSurface read,
|
|
EGLContext ctx);
|
|
typedef EGLBoolean (*eglQueryDmaBufFormatsEXT_func)(EGLDisplay dpy,
|
|
EGLint max_formats,
|
|
EGLint* formats,
|
|
EGLint* num_formats);
|
|
typedef EGLBoolean (*eglQueryDmaBufModifiersEXT_func)(EGLDisplay dpy,
|
|
EGLint format,
|
|
EGLint max_modifiers,
|
|
EGLuint64KHR* modifiers,
|
|
EGLBoolean* external_only,
|
|
EGLint* num_modifiers);
|
|
typedef const char* (*eglQueryString_func)(EGLDisplay dpy, EGLint name);
|
|
typedef void (*glEGLImageTargetTexture2DOES_func)(GLenum target,
|
|
GLeglImageOES image);
|
|
|
|
// This doesn't follow naming conventions in WebRTC, where the naming
|
|
// should look like e.g. egl_bind_api instead of EglBindAPI, however
|
|
// we named them according to the exported functions they map to for
|
|
// consistency.
|
|
eglBindAPI_func EglBindAPI = nullptr;
|
|
eglCreateContext_func EglCreateContext = nullptr;
|
|
eglDestroyContext_func EglDestroyContext = nullptr;
|
|
eglTerminate_func EglTerminate = nullptr;
|
|
eglCreateImageKHR_func EglCreateImageKHR = nullptr;
|
|
eglDestroyImageKHR_func EglDestroyImageKHR = nullptr;
|
|
eglGetError_func EglGetError = nullptr;
|
|
eglGetProcAddress_func EglGetProcAddress = nullptr;
|
|
eglGetPlatformDisplayEXT_func EglGetPlatformDisplayEXT = nullptr;
|
|
eglGetPlatformDisplay_func EglGetPlatformDisplay = nullptr;
|
|
eglInitialize_func EglInitialize = nullptr;
|
|
eglMakeCurrent_func EglMakeCurrent = nullptr;
|
|
eglQueryDmaBufFormatsEXT_func EglQueryDmaBufFormatsEXT = nullptr;
|
|
eglQueryDmaBufModifiersEXT_func EglQueryDmaBufModifiersEXT = nullptr;
|
|
eglQueryString_func EglQueryString = nullptr;
|
|
glEGLImageTargetTexture2DOES_func GlEGLImageTargetTexture2DOES = nullptr;
|
|
|
|
// GL
|
|
typedef void (*glBindTexture_func)(GLenum target, GLuint texture);
|
|
typedef void (*glDeleteTextures_func)(GLsizei n, const GLuint* textures);
|
|
typedef void (*glGenTextures_func)(GLsizei n, GLuint* textures);
|
|
typedef GLenum (*glGetError_func)(void);
|
|
typedef const GLubyte* (*glGetString_func)(GLenum name);
|
|
typedef void (*glGetTexImage_func)(GLenum target,
|
|
GLint level,
|
|
GLenum format,
|
|
GLenum type,
|
|
void* pixels);
|
|
typedef void (*glTexParameteri_func)(GLenum target, GLenum pname, GLint param);
|
|
typedef void* (*glXGetProcAddressARB_func)(const char*);
|
|
|
|
// This doesn't follow naming conventions in WebRTC, where the naming
|
|
// should look like e.g. egl_bind_api instead of EglBindAPI, however
|
|
// we named them according to the exported functions they map to for
|
|
// consistency.
|
|
glBindTexture_func GlBindTexture = nullptr;
|
|
glDeleteTextures_func GlDeleteTextures = nullptr;
|
|
glGenTextures_func GlGenTextures = nullptr;
|
|
glGetError_func GlGetError = nullptr;
|
|
glGetString_func GlGetString = nullptr;
|
|
glGetTexImage_func GlGetTexImage = nullptr;
|
|
glTexParameteri_func GlTexParameteri = nullptr;
|
|
glXGetProcAddressARB_func GlXGetProcAddressARB = nullptr;
|
|
|
|
static const std::string FormatGLError(GLenum err) {
|
|
switch (err) {
|
|
case GL_NO_ERROR:
|
|
return "GL_NO_ERROR";
|
|
case GL_INVALID_ENUM:
|
|
return "GL_INVALID_ENUM";
|
|
case GL_INVALID_VALUE:
|
|
return "GL_INVALID_VALUE";
|
|
case GL_INVALID_OPERATION:
|
|
return "GL_INVALID_OPERATION";
|
|
case GL_STACK_OVERFLOW:
|
|
return "GL_STACK_OVERFLOW";
|
|
case GL_STACK_UNDERFLOW:
|
|
return "GL_STACK_UNDERFLOW";
|
|
case GL_OUT_OF_MEMORY:
|
|
return "GL_OUT_OF_MEMORY";
|
|
default:
|
|
return "GL error code: " + std::to_string(err);
|
|
}
|
|
}
|
|
|
|
static const std::string FormatEGLError(EGLint err) {
|
|
switch (err) {
|
|
case EGL_NOT_INITIALIZED:
|
|
return "EGL_NOT_INITIALIZED";
|
|
case EGL_BAD_ACCESS:
|
|
return "EGL_BAD_ACCESS";
|
|
case EGL_BAD_ALLOC:
|
|
return "EGL_BAD_ALLOC";
|
|
case EGL_BAD_ATTRIBUTE:
|
|
return "EGL_BAD_ATTRIBUTE";
|
|
case EGL_BAD_CONTEXT:
|
|
return "EGL_BAD_CONTEXT";
|
|
case EGL_BAD_CONFIG:
|
|
return "EGL_BAD_CONFIG";
|
|
case EGL_BAD_CURRENT_SURFACE:
|
|
return "EGL_BAD_CURRENT_SURFACE";
|
|
case EGL_BAD_DISPLAY:
|
|
return "EGL_BAD_DISPLAY";
|
|
case EGL_BAD_SURFACE:
|
|
return "EGL_BAD_SURFACE";
|
|
case EGL_BAD_MATCH:
|
|
return "EGL_BAD_MATCH";
|
|
case EGL_BAD_PARAMETER:
|
|
return "EGL_BAD_PARAMETER";
|
|
case EGL_BAD_NATIVE_PIXMAP:
|
|
return "EGL_BAD_NATIVE_PIXMAP";
|
|
case EGL_BAD_NATIVE_WINDOW:
|
|
return "EGL_BAD_NATIVE_WINDOW";
|
|
case EGL_CONTEXT_LOST:
|
|
return "EGL_CONTEXT_LOST";
|
|
default:
|
|
return "EGL error code: " + std::to_string(err);
|
|
}
|
|
}
|
|
|
|
static uint32_t SpaPixelFormatToDrmFormat(uint32_t spa_format) {
|
|
switch (spa_format) {
|
|
case SPA_VIDEO_FORMAT_RGBA:
|
|
return DRM_FORMAT_ABGR8888;
|
|
case SPA_VIDEO_FORMAT_RGBx:
|
|
return DRM_FORMAT_XBGR8888;
|
|
case SPA_VIDEO_FORMAT_BGRA:
|
|
return DRM_FORMAT_ARGB8888;
|
|
case SPA_VIDEO_FORMAT_BGRx:
|
|
return DRM_FORMAT_XRGB8888;
|
|
default:
|
|
return DRM_FORMAT_INVALID;
|
|
}
|
|
}
|
|
|
|
static void CloseLibrary(void* library) {
|
|
if (library) {
|
|
dlclose(library);
|
|
library = nullptr;
|
|
}
|
|
}
|
|
|
|
static void* g_lib_egl = nullptr;
|
|
|
|
RTC_NO_SANITIZE("cfi-icall")
|
|
static bool OpenEGL() {
|
|
g_lib_egl = dlopen("libEGL.so.1", RTLD_NOW | RTLD_GLOBAL);
|
|
if (g_lib_egl) {
|
|
EglGetProcAddress =
|
|
(eglGetProcAddress_func)dlsym(g_lib_egl, "eglGetProcAddress");
|
|
return EglGetProcAddress;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
RTC_NO_SANITIZE("cfi-icall")
|
|
static bool LoadEGL() {
|
|
if (OpenEGL()) {
|
|
EglBindAPI = (eglBindAPI_func)EglGetProcAddress("eglBindAPI");
|
|
EglCreateContext =
|
|
(eglCreateContext_func)EglGetProcAddress("eglCreateContext");
|
|
EglDestroyContext =
|
|
(eglDestroyContext_func)EglGetProcAddress("eglDestroyContext");
|
|
EglTerminate = (eglTerminate_func)EglGetProcAddress("eglTerminate");
|
|
EglCreateImageKHR =
|
|
(eglCreateImageKHR_func)EglGetProcAddress("eglCreateImageKHR");
|
|
EglDestroyImageKHR =
|
|
(eglDestroyImageKHR_func)EglGetProcAddress("eglDestroyImageKHR");
|
|
EglGetError = (eglGetError_func)EglGetProcAddress("eglGetError");
|
|
EglGetPlatformDisplayEXT = (eglGetPlatformDisplayEXT_func)EglGetProcAddress(
|
|
"eglGetPlatformDisplayEXT");
|
|
EglGetPlatformDisplay =
|
|
(eglGetPlatformDisplay_func)EglGetProcAddress("eglGetPlatformDisplay");
|
|
EglInitialize = (eglInitialize_func)EglGetProcAddress("eglInitialize");
|
|
EglMakeCurrent = (eglMakeCurrent_func)EglGetProcAddress("eglMakeCurrent");
|
|
EglQueryString = (eglQueryString_func)EglGetProcAddress("eglQueryString");
|
|
GlEGLImageTargetTexture2DOES =
|
|
(glEGLImageTargetTexture2DOES_func)EglGetProcAddress(
|
|
"glEGLImageTargetTexture2DOES");
|
|
|
|
return EglBindAPI && EglCreateContext && EglCreateImageKHR &&
|
|
EglTerminate && EglDestroyContext && EglDestroyImageKHR &&
|
|
EglGetError && EglGetPlatformDisplayEXT && EglGetPlatformDisplay &&
|
|
EglInitialize && EglMakeCurrent && EglQueryString &&
|
|
GlEGLImageTargetTexture2DOES;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void* g_lib_gl = nullptr;
|
|
|
|
RTC_NO_SANITIZE("cfi-icall")
|
|
static bool OpenGL() {
|
|
std::vector<std::string> names = {"libGL.so.1", "libGL.so"};
|
|
for (const std::string& name : names) {
|
|
g_lib_gl = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
|
if (g_lib_gl) {
|
|
GlXGetProcAddressARB =
|
|
(glXGetProcAddressARB_func)dlsym(g_lib_gl, "glXGetProcAddressARB");
|
|
return GlXGetProcAddressARB;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
RTC_NO_SANITIZE("cfi-icall")
|
|
static bool LoadGL() {
|
|
if (OpenGL()) {
|
|
GlGetString = (glGetString_func)GlXGetProcAddressARB("glGetString");
|
|
if (!GlGetString) {
|
|
return false;
|
|
}
|
|
|
|
GlBindTexture = (glBindTexture_func)GlXGetProcAddressARB("glBindTexture");
|
|
GlDeleteTextures =
|
|
(glDeleteTextures_func)GlXGetProcAddressARB("glDeleteTextures");
|
|
GlGenTextures = (glGenTextures_func)GlXGetProcAddressARB("glGenTextures");
|
|
GlGetError = (glGetError_func)GlXGetProcAddressARB("glGetError");
|
|
GlGetTexImage = (glGetTexImage_func)GlXGetProcAddressARB("glGetTexImage");
|
|
GlTexParameteri =
|
|
(glTexParameteri_func)GlXGetProcAddressARB("glTexParameteri");
|
|
|
|
return GlBindTexture && GlDeleteTextures && GlGenTextures && GlGetError &&
|
|
GlGetTexImage && GlTexParameteri;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
RTC_NO_SANITIZE("cfi-icall")
|
|
EglDmaBuf::EglDmaBuf() {
|
|
if (!LoadEGL()) {
|
|
RTC_LOG(LS_ERROR) << "Unable to load EGL entry functions.";
|
|
CloseLibrary(g_lib_egl);
|
|
return;
|
|
}
|
|
|
|
if (!LoadGL()) {
|
|
RTC_LOG(LS_ERROR) << "Failed to load OpenGL entry functions.";
|
|
CloseLibrary(g_lib_gl);
|
|
return;
|
|
}
|
|
|
|
if (!GetClientExtensions(EGL_NO_DISPLAY, EGL_EXTENSIONS)) {
|
|
return;
|
|
}
|
|
|
|
bool has_platform_base_ext = false;
|
|
bool has_platform_gbm_ext = false;
|
|
bool has_khr_platform_gbm_ext = false;
|
|
|
|
for (const auto& extension : egl_.extensions) {
|
|
if (extension == "EGL_EXT_platform_base") {
|
|
has_platform_base_ext = true;
|
|
continue;
|
|
} else if (extension == "EGL_MESA_platform_gbm") {
|
|
has_platform_gbm_ext = true;
|
|
continue;
|
|
} else if (extension == "EGL_KHR_platform_gbm") {
|
|
has_khr_platform_gbm_ext = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!has_platform_base_ext || !has_platform_gbm_ext ||
|
|
!has_khr_platform_gbm_ext) {
|
|
RTC_LOG(LS_ERROR) << "One of required EGL extensions is missing";
|
|
return;
|
|
}
|
|
|
|
egl_.display = EglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR,
|
|
(void*)EGL_DEFAULT_DISPLAY, nullptr);
|
|
|
|
if (egl_.display == EGL_NO_DISPLAY) {
|
|
RTC_LOG(LS_ERROR) << "Failed to obtain default EGL display: "
|
|
<< FormatEGLError(EglGetError()) << "\n"
|
|
<< "Defaulting to using first available render node";
|
|
absl::optional<std::string> render_node = GetRenderNode();
|
|
if (!render_node) {
|
|
return;
|
|
}
|
|
|
|
drm_fd_ = open(render_node->c_str(), O_RDWR);
|
|
|
|
if (drm_fd_ < 0) {
|
|
RTC_LOG(LS_ERROR) << "Failed to open drm render node: "
|
|
<< strerror(errno);
|
|
return;
|
|
}
|
|
|
|
gbm_device_ = gbm_create_device(drm_fd_);
|
|
|
|
if (!gbm_device_) {
|
|
RTC_LOG(LS_ERROR) << "Cannot create GBM device: " << strerror(errno);
|
|
close(drm_fd_);
|
|
return;
|
|
}
|
|
|
|
// Use eglGetPlatformDisplayEXT() to get the display pointer
|
|
// if the implementation supports it.
|
|
egl_.display =
|
|
EglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm_device_, nullptr);
|
|
}
|
|
|
|
if (egl_.display == EGL_NO_DISPLAY) {
|
|
RTC_LOG(LS_ERROR) << "Error during obtaining EGL display: "
|
|
<< FormatEGLError(EglGetError());
|
|
return;
|
|
}
|
|
|
|
EGLint major, minor;
|
|
if (EglInitialize(egl_.display, &major, &minor) == EGL_FALSE) {
|
|
RTC_LOG(LS_ERROR) << "Error during eglInitialize: "
|
|
<< FormatEGLError(EglGetError());
|
|
return;
|
|
}
|
|
|
|
if (EglBindAPI(EGL_OPENGL_API) == EGL_FALSE) {
|
|
RTC_LOG(LS_ERROR) << "bind OpenGL API failed";
|
|
return;
|
|
}
|
|
|
|
egl_.context =
|
|
EglCreateContext(egl_.display, nullptr, EGL_NO_CONTEXT, nullptr);
|
|
|
|
if (egl_.context == EGL_NO_CONTEXT) {
|
|
RTC_LOG(LS_ERROR) << "Couldn't create EGL context: "
|
|
<< FormatGLError(EglGetError());
|
|
return;
|
|
}
|
|
|
|
if (!GetClientExtensions(egl_.display, EGL_EXTENSIONS)) {
|
|
return;
|
|
}
|
|
|
|
bool has_image_dma_buf_import_modifiers_ext = false;
|
|
|
|
for (const auto& extension : egl_.extensions) {
|
|
if (extension == "EGL_EXT_image_dma_buf_import") {
|
|
has_image_dma_buf_import_ext_ = true;
|
|
continue;
|
|
} else if (extension == "EGL_EXT_image_dma_buf_import_modifiers") {
|
|
has_image_dma_buf_import_modifiers_ext = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (has_image_dma_buf_import_ext_ && has_image_dma_buf_import_modifiers_ext) {
|
|
EglQueryDmaBufFormatsEXT = (eglQueryDmaBufFormatsEXT_func)EglGetProcAddress(
|
|
"eglQueryDmaBufFormatsEXT");
|
|
EglQueryDmaBufModifiersEXT =
|
|
(eglQueryDmaBufModifiersEXT_func)EglGetProcAddress(
|
|
"eglQueryDmaBufModifiersEXT");
|
|
}
|
|
|
|
RTC_LOG(LS_INFO) << "Egl initialization succeeded";
|
|
egl_initialized_ = true;
|
|
}
|
|
|
|
RTC_NO_SANITIZE("cfi-icall")
|
|
EglDmaBuf::~EglDmaBuf() {
|
|
if (gbm_device_) {
|
|
gbm_device_destroy(gbm_device_);
|
|
close(drm_fd_);
|
|
}
|
|
|
|
if (egl_.context != EGL_NO_CONTEXT) {
|
|
EglDestroyContext(egl_.display, egl_.context);
|
|
}
|
|
|
|
if (egl_.display != EGL_NO_DISPLAY) {
|
|
EglTerminate(egl_.display);
|
|
}
|
|
|
|
// BUG: crbug.com/1290566
|
|
// Closing libEGL.so.1 when using NVidia drivers causes a crash
|
|
// when EglGetPlatformDisplayEXT() is used, at least this one is enough
|
|
// to be called to make it crash.
|
|
// It also looks that libepoxy and glad don't dlclose it either
|
|
// CloseLibrary(g_lib_egl);
|
|
// CloseLibrary(g_lib_gl);
|
|
}
|
|
|
|
RTC_NO_SANITIZE("cfi-icall")
|
|
bool EglDmaBuf::GetClientExtensions(EGLDisplay dpy, EGLint name) {
|
|
// Get the list of client extensions
|
|
const char* client_extensions_cstring = EglQueryString(dpy, name);
|
|
if (!client_extensions_cstring) {
|
|
// If eglQueryString() returned NULL, the implementation doesn't support
|
|
// EGL_EXT_client_extensions. Expect an EGL_BAD_DISPLAY error.
|
|
RTC_LOG(LS_ERROR) << "No client extensions defined! "
|
|
<< FormatEGLError(EglGetError());
|
|
return false;
|
|
}
|
|
|
|
std::vector<absl::string_view> client_extensions =
|
|
rtc::split(client_extensions_cstring, ' ');
|
|
for (const auto& extension : client_extensions) {
|
|
egl_.extensions.push_back(std::string(extension));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
RTC_NO_SANITIZE("cfi-icall")
|
|
std::unique_ptr<uint8_t[]> EglDmaBuf::ImageFromDmaBuf(
|
|
const DesktopSize& size,
|
|
uint32_t format,
|
|
const std::vector<PlaneData>& plane_datas,
|
|
uint64_t modifier) {
|
|
std::unique_ptr<uint8_t[]> src;
|
|
|
|
if (!egl_initialized_) {
|
|
return src;
|
|
}
|
|
|
|
if (plane_datas.size() <= 0) {
|
|
RTC_LOG(LS_ERROR) << "Failed to process buffer: invalid number of planes";
|
|
return src;
|
|
}
|
|
|
|
EGLint attribs[47];
|
|
int atti = 0;
|
|
|
|
attribs[atti++] = EGL_WIDTH;
|
|
attribs[atti++] = static_cast<EGLint>(size.width());
|
|
attribs[atti++] = EGL_HEIGHT;
|
|
attribs[atti++] = static_cast<EGLint>(size.height());
|
|
attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT;
|
|
attribs[atti++] = SpaPixelFormatToDrmFormat(format);
|
|
|
|
if (plane_datas.size() > 0) {
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE0_FD_EXT;
|
|
attribs[atti++] = plane_datas[0].fd;
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
|
|
attribs[atti++] = plane_datas[0].offset;
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
|
|
attribs[atti++] = plane_datas[0].stride;
|
|
|
|
if (modifier != DRM_FORMAT_MOD_INVALID) {
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
|
|
attribs[atti++] = modifier & 0xFFFFFFFF;
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
|
|
attribs[atti++] = modifier >> 32;
|
|
}
|
|
}
|
|
|
|
if (plane_datas.size() > 1) {
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE1_FD_EXT;
|
|
attribs[atti++] = plane_datas[1].fd;
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE1_OFFSET_EXT;
|
|
attribs[atti++] = plane_datas[1].offset;
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE1_PITCH_EXT;
|
|
attribs[atti++] = plane_datas[1].stride;
|
|
|
|
if (modifier != DRM_FORMAT_MOD_INVALID) {
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT;
|
|
attribs[atti++] = modifier & 0xFFFFFFFF;
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT;
|
|
attribs[atti++] = modifier >> 32;
|
|
}
|
|
}
|
|
|
|
if (plane_datas.size() > 2) {
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE2_FD_EXT;
|
|
attribs[atti++] = plane_datas[2].fd;
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE2_OFFSET_EXT;
|
|
attribs[atti++] = plane_datas[2].offset;
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE2_PITCH_EXT;
|
|
attribs[atti++] = plane_datas[2].stride;
|
|
|
|
if (modifier != DRM_FORMAT_MOD_INVALID) {
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT;
|
|
attribs[atti++] = modifier & 0xFFFFFFFF;
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT;
|
|
attribs[atti++] = modifier >> 32;
|
|
}
|
|
}
|
|
|
|
if (plane_datas.size() > 3) {
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE3_FD_EXT;
|
|
attribs[atti++] = plane_datas[3].fd;
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE3_OFFSET_EXT;
|
|
attribs[atti++] = plane_datas[3].offset;
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE3_PITCH_EXT;
|
|
attribs[atti++] = plane_datas[3].stride;
|
|
|
|
if (modifier != DRM_FORMAT_MOD_INVALID) {
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT;
|
|
attribs[atti++] = modifier & 0xFFFFFFFF;
|
|
attribs[atti++] = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT;
|
|
attribs[atti++] = modifier >> 32;
|
|
}
|
|
}
|
|
|
|
attribs[atti++] = EGL_NONE;
|
|
|
|
// bind context to render thread
|
|
EglMakeCurrent(egl_.display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_.context);
|
|
|
|
// create EGL image from attribute list
|
|
EGLImageKHR image = EglCreateImageKHR(
|
|
egl_.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs);
|
|
|
|
if (image == EGL_NO_IMAGE) {
|
|
RTC_LOG(LS_ERROR) << "Failed to record frame: Error creating EGLImage - "
|
|
<< FormatEGLError(EglGetError());
|
|
return src;
|
|
}
|
|
|
|
// create GL 2D texture for framebuffer
|
|
GLuint texture;
|
|
GlGenTextures(1, &texture);
|
|
GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
GlTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
GlBindTexture(GL_TEXTURE_2D, texture);
|
|
GlEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
|
|
|
|
src = std::make_unique<uint8_t[]>(plane_datas[0].stride * size.height());
|
|
|
|
GLenum gl_format = GL_BGRA;
|
|
switch (format) {
|
|
case SPA_VIDEO_FORMAT_RGBx:
|
|
gl_format = GL_RGBA;
|
|
break;
|
|
case SPA_VIDEO_FORMAT_RGBA:
|
|
gl_format = GL_RGBA;
|
|
break;
|
|
case SPA_VIDEO_FORMAT_BGRx:
|
|
gl_format = GL_BGRA;
|
|
break;
|
|
default:
|
|
gl_format = GL_BGRA;
|
|
break;
|
|
}
|
|
GlGetTexImage(GL_TEXTURE_2D, 0, gl_format, GL_UNSIGNED_BYTE, src.get());
|
|
|
|
if (GlGetError()) {
|
|
RTC_LOG(LS_ERROR) << "Failed to get image from DMA buffer.";
|
|
return src;
|
|
}
|
|
|
|
GlDeleteTextures(1, &texture);
|
|
EglDestroyImageKHR(egl_.display, image);
|
|
|
|
return src;
|
|
}
|
|
|
|
RTC_NO_SANITIZE("cfi-icall")
|
|
std::vector<uint64_t> EglDmaBuf::QueryDmaBufModifiers(uint32_t format) {
|
|
if (!egl_initialized_) {
|
|
return {};
|
|
}
|
|
|
|
// Explicit modifiers not supported, return just DRM_FORMAT_MOD_INVALID as we
|
|
// can still use modifier-less DMA-BUFs if we have required extension
|
|
if (EglQueryDmaBufFormatsEXT == nullptr ||
|
|
EglQueryDmaBufModifiersEXT == nullptr) {
|
|
return has_image_dma_buf_import_ext_
|
|
? std::vector<uint64_t>{DRM_FORMAT_MOD_INVALID}
|
|
: std::vector<uint64_t>{};
|
|
}
|
|
|
|
uint32_t drm_format = SpaPixelFormatToDrmFormat(format);
|
|
// Should never happen as it's us who controls the list of supported formats
|
|
RTC_DCHECK(drm_format != DRM_FORMAT_INVALID);
|
|
|
|
EGLint count = 0;
|
|
EGLBoolean success =
|
|
EglQueryDmaBufFormatsEXT(egl_.display, 0, nullptr, &count);
|
|
|
|
if (!success || !count) {
|
|
RTC_LOG(LS_WARNING) << "Cannot query the number of formats.";
|
|
return {DRM_FORMAT_MOD_INVALID};
|
|
}
|
|
|
|
std::vector<uint32_t> formats(count);
|
|
if (!EglQueryDmaBufFormatsEXT(egl_.display, count,
|
|
reinterpret_cast<EGLint*>(formats.data()),
|
|
&count)) {
|
|
RTC_LOG(LS_WARNING) << "Cannot query a list of formats.";
|
|
return {DRM_FORMAT_MOD_INVALID};
|
|
}
|
|
|
|
if (std::find(formats.begin(), formats.end(), drm_format) == formats.end()) {
|
|
RTC_LOG(LS_WARNING) << "Format " << drm_format
|
|
<< " not supported for modifiers.";
|
|
return {DRM_FORMAT_MOD_INVALID};
|
|
}
|
|
|
|
success = EglQueryDmaBufModifiersEXT(egl_.display, drm_format, 0, nullptr,
|
|
nullptr, &count);
|
|
|
|
if (!success || !count) {
|
|
RTC_LOG(LS_WARNING) << "Cannot query the number of modifiers.";
|
|
return {DRM_FORMAT_MOD_INVALID};
|
|
}
|
|
|
|
std::vector<uint64_t> modifiers(count);
|
|
if (!EglQueryDmaBufModifiersEXT(egl_.display, drm_format, count,
|
|
modifiers.data(), nullptr, &count)) {
|
|
RTC_LOG(LS_WARNING) << "Cannot query a list of modifiers.";
|
|
}
|
|
|
|
// Support modifier-less buffers
|
|
modifiers.push_back(DRM_FORMAT_MOD_INVALID);
|
|
return modifiers;
|
|
}
|
|
|
|
absl::optional<std::string> EglDmaBuf::GetRenderNode() {
|
|
int max_devices = drmGetDevices2(0, nullptr, 0);
|
|
if (max_devices <= 0) {
|
|
RTC_LOG(LS_ERROR) << "drmGetDevices2() has not found any devices (errno="
|
|
<< -max_devices << ")";
|
|
return absl::nullopt;
|
|
}
|
|
|
|
std::vector<drmDevicePtr> devices(max_devices);
|
|
int ret = drmGetDevices2(0, devices.data(), max_devices);
|
|
if (ret < 0) {
|
|
RTC_LOG(LS_ERROR) << "drmGetDevices2() returned an error " << ret;
|
|
return absl::nullopt;
|
|
}
|
|
|
|
std::string render_node;
|
|
|
|
for (const drmDevicePtr& device : devices) {
|
|
if (device->available_nodes & (1 << DRM_NODE_RENDER)) {
|
|
render_node = device->nodes[DRM_NODE_RENDER];
|
|
break;
|
|
}
|
|
}
|
|
|
|
drmFreeDevices(devices.data(), ret);
|
|
return render_node;
|
|
}
|
|
|
|
} // namespace webrtc
|