| /* |
| * 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 <EGL/egl.h> |
| #include <EGL/eglext.h> |
| #include <EGL/eglplatform.h> |
| #include <GL/gl.h> |
| #include <GL/glext.h> |
| #include <asm/ioctl.h> |
| #include <dlfcn.h> |
| #include <fcntl.h> |
| #include <gbm.h> |
| #include <libdrm/drm_fourcc.h> |
| #include <spa/param/video/raw.h> |
| #include <sys/stat.h> |
| #include <sys/sysmacros.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <xf86drm.h> |
| |
| #include <algorithm> |
| #include <cerrno> |
| #include <cstdint> |
| #include <cstring> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/memory/memory.h" |
| #include "absl/strings/string_view.h" |
| #include "modules/desktop_capture/desktop_geometry.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 display, |
| 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 display, |
| EGLContext ctx, |
| EGLenum target, |
| EGLClientBuffer buffer, |
| const EGLint* attrib_list); |
| typedef EGLBoolean (*eglDestroyImageKHR_func)(EGLDisplay display, |
| 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 display, |
| EGLint* major, |
| EGLint* minor); |
| typedef EGLBoolean (*eglMakeCurrent_func)(EGLDisplay display, |
| EGLSurface draw, |
| EGLSurface read, |
| EGLContext ctx); |
| typedef EGLBoolean (*eglQueryDmaBufFormatsEXT_func)(EGLDisplay display, |
| EGLint max_formats, |
| EGLint* formats, |
| EGLint* num_formats); |
| typedef EGLBoolean (*eglQueryDmaBufModifiersEXT_func)(EGLDisplay display, |
| EGLint format, |
| EGLint max_modifiers, |
| EGLuint64KHR* modifiers, |
| EGLBoolean* external_only, |
| EGLint* num_modifiers); |
| typedef const char* (*eglQueryString_func)(EGLDisplay display, EGLint name); |
| typedef const char* (*eglQueryDeviceStringEXT_func)(EGLDeviceEXT device, |
| EGLint name); |
| typedef EGLBoolean (*eglQueryDevicesEXT_func)(EGLint max_devices, |
| EGLDeviceEXT* devices, |
| EGLint* num_devices); |
| 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; |
| eglQueryDeviceStringEXT_func EglQueryDeviceStringEXT = nullptr; |
| eglQueryDevicesEXT_func EglQueryDevicesEXT = 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 void (*glReadPixels_func)(GLint x, |
| GLint y, |
| GLsizei width, |
| GLsizei height, |
| GLenum format, |
| GLenum type, |
| void* data); |
| typedef void (*glGenFramebuffers_func)(GLsizei n, GLuint* ids); |
| typedef void (*glDeleteFramebuffers_func)(GLsizei n, |
| const GLuint* framebuffers); |
| typedef void (*glBindFramebuffer_func)(GLenum target, GLuint framebuffer); |
| typedef void (*glFramebufferTexture2D_func)(GLenum target, |
| GLenum attachment, |
| GLenum textarget, |
| GLuint texture, |
| GLint level); |
| typedef GLenum (*glCheckFramebufferStatus_func)(GLenum target); |
| 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; |
| glReadPixels_func GlReadPixels = nullptr; |
| glGenFramebuffers_func GlGenFramebuffers = nullptr; |
| glDeleteFramebuffers_func GlDeleteFramebuffers = nullptr; |
| glBindFramebuffer_func GlBindFramebuffer = nullptr; |
| glFramebufferTexture2D_func GlFramebufferTexture2D = nullptr; |
| glCheckFramebufferStatus_func GlCheckFramebufferStatus = 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; |
| } |
| } |
| |
| RTC_NO_SANITIZE("cfi-icall") |
| static std::vector<std::string> GetClientExtensions(EGLDisplay display, |
| EGLint name) { |
| // Get the list of client extensions |
| const char* client_extensions_cstring = EglQueryString(display, 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 {}; |
| } |
| |
| std::vector<std::string> extensions; |
| std::vector<absl::string_view> client_extensions = |
| split(client_extensions_cstring, ' '); |
| for (const auto& extension : client_extensions) { |
| extensions.push_back(std::string(extension)); |
| } |
| |
| return extensions; |
| } |
| |
| 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"); |
| EglQueryDeviceStringEXT = (eglQueryDeviceStringEXT_func)EglGetProcAddress( |
| "eglQueryDeviceStringEXT"); |
| EglQueryDevicesEXT = |
| (eglQueryDevicesEXT_func)EglGetProcAddress("eglQueryDevicesEXT"); |
| GlEGLImageTargetTexture2DOES = |
| (glEGLImageTargetTexture2DOES_func)EglGetProcAddress( |
| "glEGLImageTargetTexture2DOES"); |
| |
| return EglBindAPI && EglCreateContext && EglCreateImageKHR && |
| EglTerminate && EglDestroyContext && EglDestroyImageKHR && |
| EglGetError && EglGetPlatformDisplayEXT && EglGetPlatformDisplay && |
| EglInitialize && EglMakeCurrent && EglQueryString && |
| EglQueryDeviceStringEXT && EglQueryDevicesEXT && |
| 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()) { |
| GlBindTexture = (glBindTexture_func)GlXGetProcAddressARB("glBindTexture"); |
| GlDeleteTextures = |
| (glDeleteTextures_func)GlXGetProcAddressARB("glDeleteTextures"); |
| GlGenTextures = (glGenTextures_func)GlXGetProcAddressARB("glGenTextures"); |
| GlGetError = (glGetError_func)GlXGetProcAddressARB("glGetError"); |
| GlReadPixels = (glReadPixels_func)GlXGetProcAddressARB("glReadPixels"); |
| GlGenFramebuffers = |
| (glGenFramebuffers_func)GlXGetProcAddressARB("glGenFramebuffers"); |
| GlDeleteFramebuffers = |
| (glDeleteFramebuffers_func)GlXGetProcAddressARB("glDeleteFramebuffers"); |
| GlBindFramebuffer = |
| (glBindFramebuffer_func)GlXGetProcAddressARB("glBindFramebuffer"); |
| GlFramebufferTexture2D = (glFramebufferTexture2D_func)GlXGetProcAddressARB( |
| "glFramebufferTexture2D"); |
| GlCheckFramebufferStatus = |
| (glCheckFramebufferStatus_func)GlXGetProcAddressARB( |
| "glCheckFramebufferStatus"); |
| |
| GlTexParameteri = |
| (glTexParameteri_func)GlXGetProcAddressARB("glTexParameteri"); |
| |
| return GlBindTexture && GlDeleteTextures && GlGenTextures && GlGetError && |
| GlReadPixels && GlGenFramebuffers && GlDeleteFramebuffers && |
| GlBindFramebuffer && GlFramebufferTexture2D && |
| GlCheckFramebufferStatus && GlTexParameteri; |
| } |
| |
| return false; |
| } |
| |
| RTC_NO_SANITIZE("cfi-icall") |
| EglDrmDevice::EglDrmDevice(EGLDisplay display, dev_t device_id) |
| : device_id_(device_id) { |
| egl_.display = display; |
| } |
| |
| EglDrmDevice::EglDrmDevice(std::string render_node, dev_t device_id) |
| : device_id_(device_id), render_node_(render_node) {} |
| |
| RTC_NO_SANITIZE("cfi-icall") |
| EglDrmDevice::~EglDrmDevice() { |
| if (drm_fd_ >= 0) { |
| close(drm_fd_); |
| } |
| |
| if (fbo_) { |
| GlDeleteFramebuffers(1, &fbo_); |
| } |
| |
| if (texture_) { |
| GlDeleteTextures(1, &texture_); |
| } |
| |
| if (egl_.display != EGL_NO_DISPLAY) { |
| EglMakeCurrent(egl_.display, EGL_NO_SURFACE, EGL_NO_SURFACE, |
| EGL_NO_CONTEXT); |
| } |
| |
| if (egl_.context != EGL_NO_CONTEXT) { |
| EglDestroyContext(egl_.display, egl_.context); |
| } |
| } |
| |
| RTC_NO_SANITIZE("cfi-icall") |
| bool EglDrmDevice::EnsureInitialized() { |
| if (initialized_) { |
| return true; |
| } |
| |
| // Initialize EGLDisplay using GBM |
| if (egl_.display == EGL_NO_DISPLAY && !render_node_.empty()) { |
| 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 false; |
| } |
| |
| gbm_device_.reset(gbm_create_device(drm_fd_)); |
| if (!gbm_device_) { |
| RTC_LOG(LS_ERROR) << "Cannot create GBM device: " << strerror(errno); |
| close(drm_fd_); |
| drm_fd_ = -1; |
| return false; |
| } |
| |
| // Use eglGetPlatformDisplayEXT() to get the display pointer |
| // if the implementation supports it. |
| egl_.display = EglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, |
| gbm_device_.get(), nullptr); |
| if (egl_.display == EGL_NO_DISPLAY) { |
| RTC_LOG(LS_ERROR) << "Failed to get EGL display from GBM device: " |
| << FormatEGLError(EglGetError()); |
| gbm_device_.reset(); |
| close(drm_fd_); |
| drm_fd_ = -1; |
| return false; |
| } |
| } |
| |
| if (egl_.display == EGL_NO_DISPLAY) { |
| RTC_LOG(LS_ERROR) << "No valid EGL display available"; |
| return false; |
| } |
| |
| EGLint major, minor; |
| if (EglInitialize(egl_.display, &major, &minor) == EGL_FALSE) { |
| RTC_LOG(LS_ERROR) << "Failed to initialize EGL display: " |
| << FormatEGLError(EglGetError()); |
| return false; |
| } |
| |
| if (EglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { |
| RTC_LOG(LS_ERROR) << "Failed to bind OpenGL API"; |
| return false; |
| } |
| |
| 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 false; |
| } |
| |
| egl_.extensions = GetClientExtensions(egl_.display, EGL_EXTENSIONS); |
| if (egl_.extensions.empty()) { |
| return 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"); |
| } |
| |
| initialized_ = true; |
| |
| return true; |
| } |
| |
| RTC_NO_SANITIZE("cfi-icall") |
| std::vector<uint64_t> EglDrmDevice::QueryDmaBufModifiers(uint32_t format) { |
| if (!EnsureInitialized()) { |
| 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); |
| |
| // Filter out failed modifiers |
| MutexLock lock(&failed_modifiers_lock_); |
| auto it = failed_modifiers_.find(format); |
| if (it == failed_modifiers_.end()) { |
| return modifiers; |
| } |
| |
| const auto& failed_set = it->second; |
| |
| // Special case: if DRM_FORMAT_MOD_INVALID is in the failed set, |
| // it means all modifiers failed for this format (older PipeWire) |
| if (failed_set.count(DRM_FORMAT_MOD_INVALID) > 0) { |
| return {}; |
| } |
| |
| std::vector<uint64_t> filtered; |
| for (uint64_t modifier : modifiers) { |
| if (failed_set.count(modifier) == 0) { |
| filtered.push_back(modifier); |
| } |
| } |
| |
| return filtered; |
| } |
| |
| void EglDrmDevice::MarkModifierFailed(uint32_t format, uint64_t modifier) { |
| MutexLock lock(&failed_modifiers_lock_); |
| failed_modifiers_[format].insert(modifier); |
| } |
| |
| void EglDrmDevice::MarkModifierFailed(uint64_t modifier) { |
| for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, |
| SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) { |
| MarkModifierFailed(format, modifier); |
| } |
| } |
| |
| RTC_NO_SANITIZE("cfi-icall") |
| bool EglDrmDevice::ImageFromDmaBuf(const DesktopSize& size, |
| uint32_t format, |
| const std::vector<PlaneData>& plane_datas, |
| uint64_t modifier, |
| const DesktopVector& offset, |
| const DesktopSize& buffer_size, |
| uint8_t* data) { |
| if (!EnsureInitialized()) { |
| return false; |
| } |
| |
| if (plane_datas.empty()) { |
| RTC_LOG(LS_ERROR) << "Failed to process buffer: invalid number of planes"; |
| return false; |
| } |
| |
| 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); |
| |
| 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 false; |
| } |
| |
| // create GL 2D texture for framebuffer |
| if (!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); |
| |
| if (!fbo_) { |
| GlGenFramebuffers(1, &fbo_); |
| } |
| |
| GlBindFramebuffer(GL_FRAMEBUFFER, fbo_); |
| GlFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| texture_, 0); |
| if (GlCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { |
| RTC_LOG(LS_ERROR) << "Failed to bind DMA buf framebuffer"; |
| EglDestroyImageKHR(egl_.display, image); |
| return false; |
| } |
| |
| 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; |
| } |
| |
| GlReadPixels(offset.x(), offset.y(), buffer_size.width(), |
| buffer_size.height(), gl_format, GL_UNSIGNED_BYTE, data); |
| |
| const GLenum error = GlGetError(); |
| if (error) { |
| RTC_LOG(LS_ERROR) << "Failed to get image from DMA buffer."; |
| } |
| |
| EglDestroyImageKHR(egl_.display, image); |
| |
| return !error; |
| } |
| |
| std::unique_ptr<EglDmaBuf> EglDmaBuf::CreateDefault() { |
| auto instance = absl::WrapUnique(new EglDmaBuf()); |
| if (!instance->Initialize()) { |
| RTC_LOG(LS_WARNING) << "EglDmaBuf initialization failed"; |
| return nullptr; |
| } |
| return instance; |
| } |
| |
| RTC_NO_SANITIZE("cfi-icall") |
| bool EglDmaBuf::Initialize() { |
| if (!LoadEGL()) { |
| RTC_LOG(LS_ERROR) << "Unable to load EGL entry functions."; |
| CloseLibrary(g_lib_egl); |
| return false; |
| } |
| |
| if (!LoadGL()) { |
| RTC_LOG(LS_ERROR) << "Failed to load OpenGL entry functions."; |
| CloseLibrary(g_lib_gl); |
| return false; |
| } |
| |
| std::vector<std::string> client_extensions = |
| GetClientExtensions(EGL_NO_DISPLAY, EGL_EXTENSIONS); |
| if (client_extensions.empty()) { |
| return false; |
| } |
| |
| bool has_platform_base_ext = false; |
| bool has_platform_gbm_ext = false; |
| bool has_khr_platform_gbm_ext = false; |
| |
| for (const auto& extension : client_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 false; |
| } |
| |
| CreatePlatformDevice(); |
| EnumerateDrmDevices(); |
| |
| return GetRenderDevice() != nullptr; |
| } |
| |
| // 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 close it either |
| // EglDmaBuf::~EglDmaBuf() { |
| // CloseLibrary(g_lib_egl); |
| // CloseLibrary(g_lib_gl); |
| // } |
| |
| RTC_NO_SANITIZE("cfi-icall") |
| bool EglDmaBuf::CreatePlatformDevice() { |
| EGLDisplay display = EGL_NO_DISPLAY; |
| |
| display = EglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, |
| (void*)EGL_DEFAULT_DISPLAY, nullptr); |
| |
| if (display == EGL_NO_DISPLAY) { |
| RTC_LOG(LS_ERROR) << "Failed to obtain platform EGL display: " |
| << FormatEGLError(EglGetError()); |
| return false; |
| } |
| |
| auto drm_device = std::make_unique<EglDrmDevice>(display); |
| drm_device->EnsureInitialized(); |
| if (!drm_device->IsInitialized()) { |
| RTC_LOG(LS_ERROR) << "Failed to create default device for wayland platform"; |
| return false; |
| } |
| default_platform_device_ = std::move(drm_device); |
| |
| RTC_LOG(LS_INFO) << "Created default device for wayland platform"; |
| |
| return true; |
| } |
| |
| void EglDmaBuf::EnumerateDrmDevices() { |
| int max_devices = drmGetDevices2(0, nullptr, 0); |
| if (max_devices <= 0) { |
| RTC_LOG(LS_WARNING) << "drmGetDevices2() has not found any devices (errno=" |
| << -max_devices << ")"; |
| return; |
| } |
| |
| std::vector<drmDevicePtr> devices(max_devices); |
| int ret = drmGetDevices2(0, devices.data(), max_devices); |
| if (ret < 0) { |
| RTC_LOG(LS_WARNING) << "drmGetDevices2() returned an error " << ret; |
| return; |
| } |
| |
| int devices_created = 0; |
| for (const drmDevicePtr& device : devices) { |
| if (!(device->available_nodes & (1 << DRM_NODE_RENDER))) { |
| continue; |
| } |
| |
| std::string render_node = device->nodes[DRM_NODE_RENDER]; |
| struct stat device_stat; |
| if (stat(render_node.c_str(), &device_stat) != 0) { |
| RTC_LOG(LS_WARNING) << "Failed to fetch device file ID for render node " |
| << render_node << ": " << strerror(errno); |
| continue; |
| } |
| |
| dev_t dev_id = device_stat.st_rdev; |
| auto it = devices_.find(dev_id); |
| if (it != devices_.end()) { |
| RTC_LOG(LS_INFO) << "Skipping device. Device " << render_node |
| << " already exists with ID: " << major(dev_id) << ":" |
| << minor(dev_id); |
| continue; |
| } |
| |
| auto drm_device = std::make_unique<EglDrmDevice>(render_node, dev_id); |
| // All devices are lazy initialized, because at this point we don't need |
| // them and we don't know which one is in-use. Initializing a GPU in |
| // low-power state (especially discrete GPUs with runtime PM) may cause |
| // initialization delays, so we defer until the device is actually needed |
| // for rendering. |
| devices_.insert({dev_id, std::move(drm_device)}); |
| |
| RTC_LOG(LS_INFO) << "Created new DRM device with device ID: " |
| << major(dev_id) << ":" << minor(dev_id) |
| << ", render node: " << render_node; |
| devices_created++; |
| } |
| |
| drmFreeDevices(devices.data(), ret); |
| |
| RTC_LOG(LS_INFO) << "Created " << devices_created << " DRM device(s) out of " |
| << ret << " available"; |
| } |
| |
| EglDrmDevice* EglDmaBuf::GetRenderDevice() { |
| if (auto it = devices_.find(preferred_render_device_id_); |
| it != devices_.end()) { |
| return it->second.get(); |
| } |
| |
| if (default_platform_device_) { |
| return default_platform_device_.get(); |
| } |
| |
| if (!devices_.empty()) { |
| return devices_.begin()->second.get(); |
| } |
| |
| return nullptr; |
| } |
| |
| EglDrmDevice* EglDmaBuf::GetRenderDevice(dev_t id) { |
| if (auto it = devices_.find(id); it != devices_.end()) { |
| return it->second.get(); |
| } |
| |
| return nullptr; |
| } |
| |
| bool EglDmaBuf::SetPreferredRenderDevice(dev_t device_id) { |
| if (device_id == DEVICE_ID_INVALID) { |
| RTC_LOG(LS_ERROR) << "Cannot set invalid device ID as render device"; |
| return false; |
| } |
| |
| auto it = devices_.find(device_id); |
| if (it == devices_.end()) { |
| RTC_LOG(LS_ERROR) << "Device ID " << device_id << " not found"; |
| return false; |
| } |
| |
| preferred_render_device_id_ = device_id; |
| RTC_LOG(LS_INFO) << "Render device set to device ID: " << major(device_id) |
| << ":" << minor(device_id); |
| |
| return true; |
| } |
| |
| std::vector<dev_t> EglDmaBuf::GetDevices() const { |
| std::vector<dev_t> device_ids; |
| device_ids.reserve(devices_.size()); |
| for (const auto& [device_id, device] : devices_) { |
| device_ids.push_back(device_id); |
| } |
| return device_ids; |
| } |
| |
| } // namespace webrtc |