| /* |
| * 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/egl_dmabuf.h" |
| |
| #include <asm/ioctl.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/strings/str_split.h" |
| #include "absl/types/optional.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/sanitizer.h" |
| |
| namespace webrtc { |
| |
| 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); |
| eglQueryDmaBufFormatsEXT_func EglQueryDmaBufFormatsEXT = nullptr; |
| eglQueryDmaBufModifiersEXT_func EglQueryDmaBufModifiersEXT = 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 std::string("0x") + 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 absl::optional<std::string> GetRenderNode() { |
| int ret, max_devices; |
| std::string render_node; |
| |
| 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); |
| ret = drmGetDevices2(0, devices.data(), max_devices); |
| if (ret < 0) { |
| RTC_LOG(LS_ERROR) << "drmGetDevices2() returned an error " << ret; |
| return absl::nullopt; |
| } |
| |
| 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; |
| } |
| |
| RTC_NO_SANITIZE("cfi-icall") |
| EglDmaBuf::EglDmaBuf() { |
| 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); |
| return; |
| } |
| |
| // Get the list of client extensions |
| const char* client_extensions_cstring_no_display = |
| eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); |
| std::string client_extensions_string = client_extensions_cstring_no_display; |
| if (!client_extensions_cstring_no_display) { |
| // 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! " |
| << FormatGLError(eglGetError()); |
| return; |
| } |
| |
| for (const auto& extension : |
| absl::StrSplit(client_extensions_cstring_no_display, " ")) { |
| egl_.extensions.push_back(std::string(extension)); |
| } |
| |
| bool has_platform_base_ext = false; |
| bool has_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; |
| } |
| } |
| |
| if (!has_platform_base_ext || !has_platform_gbm_ext) { |
| RTC_LOG(LS_ERROR) << "One of required EGL extensions is missing"; |
| return; |
| } |
| |
| // Use eglGetPlatformDisplayEXT() to get the display pointer |
| // if the implementation supports it. |
| egl_.display = |
| eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, gbm_device_, nullptr); |
| |
| if (egl_.display == EGL_NO_DISPLAY) { |
| RTC_LOG(LS_ERROR) << "Error during obtaining EGL display: " |
| << FormatGLError(eglGetError()); |
| return; |
| } |
| |
| EGLint major, minor; |
| if (eglInitialize(egl_.display, &major, &minor) == EGL_FALSE) { |
| RTC_LOG(LS_ERROR) << "Error during eglInitialize: " |
| << FormatGLError(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; |
| } |
| |
| const char* client_extensions_cstring_display = |
| eglQueryString(egl_.display, EGL_EXTENSIONS); |
| client_extensions_string = client_extensions_cstring_display; |
| |
| for (const auto& extension : absl::StrSplit(client_extensions_string, " ")) { |
| egl_.extensions.push_back(std::string(extension)); |
| } |
| |
| bool has_image_dma_buf_import_ext = false; |
| 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; |
| } |
| |
| EglDmaBuf::~EglDmaBuf() { |
| if (gbm_device_) { |
| gbm_device_destroy(gbm_device_); |
| } |
| } |
| |
| RTC_NO_SANITIZE("cfi-icall") |
| std::unique_ptr<uint8_t[]> EglDmaBuf::ImageFromDmaBuf(const DesktopSize& size, |
| uint32_t format, |
| uint32_t n_planes, |
| const int32_t* fds, |
| const uint32_t* strides, |
| const uint32_t* offsets, |
| uint64_t modifier) { |
| std::unique_ptr<uint8_t[]> src; |
| |
| if (!egl_initialized_) { |
| return src; |
| } |
| |
| if (n_planes <= 0) { |
| RTC_LOG(LS_ERROR) << "Failed to process buffer: invalid number of planes"; |
| return src; |
| } |
| |
| gbm_bo* imported; |
| if (modifier == DRM_FORMAT_MOD_INVALID) { |
| gbm_import_fd_data import_info = {fds[0], |
| static_cast<uint32_t>(size.width()), |
| static_cast<uint32_t>(size.height()), |
| strides[0], GBM_BO_FORMAT_ARGB8888}; |
| |
| imported = gbm_bo_import(gbm_device_, GBM_BO_IMPORT_FD, &import_info, 0); |
| } else { |
| gbm_import_fd_modifier_data import_info = {}; |
| import_info.format = GBM_BO_FORMAT_ARGB8888; |
| import_info.width = static_cast<uint32_t>(size.width()); |
| import_info.height = static_cast<uint32_t>(size.height()); |
| import_info.num_fds = n_planes; |
| import_info.modifier = modifier; |
| for (uint32_t i = 0; i < n_planes; i++) { |
| import_info.fds[i] = fds[i]; |
| import_info.offsets[i] = offsets[i]; |
| import_info.strides[i] = strides[i]; |
| } |
| |
| imported = |
| gbm_bo_import(gbm_device_, GBM_BO_IMPORT_FD_MODIFIER, &import_info, 0); |
| } |
| |
| if (!imported) { |
| RTC_LOG(LS_ERROR) |
| << "Failed to process buffer: Cannot import passed GBM fd - " |
| << strerror(errno); |
| return src; |
| } |
| |
| // bind context to render thread |
| eglMakeCurrent(egl_.display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_.context); |
| |
| // create EGL image from imported BO |
| EGLImageKHR image = eglCreateImageKHR( |
| egl_.display, nullptr, EGL_NATIVE_PIXMAP_KHR, imported, nullptr); |
| |
| if (image == EGL_NO_IMAGE_KHR) { |
| RTC_LOG(LS_ERROR) << "Failed to record frame: Error creating EGLImageKHR - " |
| << FormatGLError(glGetError()); |
| gbm_bo_destroy(imported); |
| 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[]>(strides[0] * 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; |
| case SPA_VIDEO_FORMAT_RGB: |
| gl_format = GL_RGB; |
| break; |
| case SPA_VIDEO_FORMAT_BGR: |
| gl_format = GL_BGR; |
| 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."; |
| gbm_bo_destroy(imported); |
| return src; |
| } |
| |
| glDeleteTextures(1, &texture); |
| eglDestroyImageKHR(egl_.display, image); |
| |
| gbm_bo_destroy(imported); |
| |
| return src; |
| } |
| |
| RTC_NO_SANITIZE("cfi-icall") |
| std::vector<uint64_t> EglDmaBuf::QueryDmaBufModifiers(uint32_t format) { |
| if (!egl_initialized_) { |
| return {}; |
| } |
| |
| // Modifiers not supported, return just DRM_FORMAT_MOD_INVALID as we can still |
| // use modifier-less DMA-BUFs |
| if (EglQueryDmaBufFormatsEXT == nullptr || |
| EglQueryDmaBufModifiersEXT == nullptr) { |
| return {DRM_FORMAT_MOD_INVALID}; |
| } |
| |
| uint32_t drm_format = SpaPixelFormatToDrmFormat(format); |
| if (drm_format == DRM_FORMAT_INVALID) { |
| RTC_LOG(LS_ERROR) << "Failed to find matching DRM format."; |
| return {DRM_FORMAT_MOD_INVALID}; |
| } |
| |
| EGLint count = 0; |
| EGLBoolean success = |
| EglQueryDmaBufFormatsEXT(egl_.display, 0, nullptr, &count); |
| |
| if (!success || !count) { |
| RTC_LOG(LS_ERROR) << "Failed to query DMA-BUF 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_ERROR) << "Failed to query DMA-BUF formats."; |
| return {DRM_FORMAT_MOD_INVALID}; |
| } |
| |
| if (std::find(formats.begin(), formats.end(), drm_format) == formats.end()) { |
| RTC_LOG(LS_ERROR) << "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_ERROR) << "Failed to query DMA-BUF 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_ERROR) << "Failed to query DMA-BUF modifiers."; |
| } |
| |
| // Support modifier-less buffers |
| modifiers.push_back(DRM_FORMAT_MOD_INVALID); |
| |
| return modifiers; |
| } |
| |
| } // namespace webrtc |