PipeWire capturer: Import DMA-BUFs with correct render node

With more GPUs it might happen that server used different render
node from the one we pick from the list. This would cause DMA-BUF to
fail to import so we use Wayland client library to obtain wl_display in
order to initialize EGLDisplay using same render node and have previous
approach as a fallback. Also everyone else uses EGL_LINUX_DMA_BUF_EXT
target for importing EGLImages from DMA-BUF file descriptors so use it
as well to be sure we import buffers same way as they are produced.

Bug: chromium:1290566
Bug: webrtc:13429
Change-Id: I32bbb0bdb28c08b6e7fcb3f94009f82a2041b6ee
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/250661
Reviewed-by: Mark Foltz <mfoltz@chromium.org>
Commit-Queue: Jan Grulich <grulja@gmail.com>
Commit-Queue: Mark Foltz <mfoltz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#35997}
diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn
index 8701c29..63a0bb3 100644
--- a/modules/desktop_capture/BUILD.gn
+++ b/modules/desktop_capture/BUILD.gn
@@ -228,10 +228,6 @@
     pkg_config("egl") {
       packages = [ "egl" ]
     }
-    pkg_config("epoxy") {
-      packages = [ "epoxy" ]
-      ignore_libs = true
-    }
     pkg_config("libdrm") {
       packages = [ "libdrm" ]
       if (!rtc_link_pipewire) {
@@ -578,7 +574,6 @@
       ":pipewire",
       ":gbm",
       ":egl",
-      ":epoxy",
       ":libdrm",
     ]
 
diff --git a/modules/desktop_capture/linux/wayland/egl_dmabuf.cc b/modules/desktop_capture/linux/wayland/egl_dmabuf.cc
index 088d1c4..1d364da 100644
--- a/modules/desktop_capture/linux/wayland/egl_dmabuf.cc
+++ b/modules/desktop_capture/linux/wayland/egl_dmabuf.cc
@@ -49,6 +49,10 @@
 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);
@@ -83,6 +87,7 @@
 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;
@@ -225,6 +230,8 @@
     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");
@@ -234,8 +241,9 @@
 
     return EglBindAPI && EglCreateContext && EglCreateImageKHR &&
            EglTerminate && EglDestroyContext && EglDestroyImageKHR &&
-           EglGetError && EglGetPlatformDisplayEXT && EglInitialize &&
-           EglMakeCurrent && EglQueryString && GlEGLImageTargetTexture2DOES;
+           EglGetError && EglGetPlatformDisplayEXT && EglGetPlatformDisplay &&
+           EglInitialize && EglMakeCurrent && EglQueryString &&
+           GlEGLImageTargetTexture2DOES;
   }
 
   return false;
@@ -284,25 +292,6 @@
 
 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;
-  }
-
   if (!LoadEGL()) {
     RTC_LOG(LS_ERROR) << "Unable to load EGL entry functions.";
     CloseLibrary(g_lib_egl);
@@ -315,26 +304,13 @@
     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! "
-                      << FormatEGLError(EglGetError());
+  if (!GetClientExtensions(EGL_NO_DISPLAY, EGL_EXTENSIONS)) {
     return;
   }
 
-  std::vector<absl::string_view> client_extensions_no_display =
-      rtc::split(client_extensions_cstring_no_display, ' ');
-  for (const auto& extension : client_extensions_no_display) {
-    egl_.extensions.push_back(std::string(extension));
-  }
-
   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") {
@@ -343,18 +319,51 @@
     } 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) {
+  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;
   }
 
-  // Use eglGetPlatformDisplayEXT() to get the display pointer
-  // if the implementation supports it.
-  egl_.display =
-      EglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA, gbm_device_, nullptr);
+  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: "
@@ -383,14 +392,8 @@
     return;
   }
 
-  const char* client_extensions_cstring_display =
-      EglQueryString(egl_.display, EGL_EXTENSIONS);
-  client_extensions_string = client_extensions_cstring_display;
-
-  std::vector<absl::string_view> client_extensions =
-      rtc::split(client_extensions_string, ' ');
-  for (const auto& extension : client_extensions) {
-    egl_.extensions.push_back(std::string(extension));
+  if (!GetClientExtensions(egl_.display, EGL_EXTENSIONS)) {
+    return;
   }
 
   bool has_image_dma_buf_import_modifiers_ext = false;
@@ -421,6 +424,7 @@
 EglDmaBuf::~EglDmaBuf() {
   if (gbm_device_) {
     gbm_device_destroy(gbm_device_);
+    close(drm_fd_);
   }
 
   if (egl_.context != EGL_NO_CONTEXT) {
@@ -441,6 +445,27 @@
 }
 
 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,
@@ -457,50 +482,92 @@
     return src;
   }
 
-  gbm_bo* imported;
+  EGLint attribs[47];
+  int atti = 0;
 
-  if (modifier == DRM_FORMAT_MOD_INVALID) {
-    gbm_import_fd_data import_info = {
-        plane_datas[0].fd, static_cast<uint32_t>(size.width()),
-        static_cast<uint32_t>(size.height()), plane_datas[0].stride,
-        GBM_BO_FORMAT_ARGB8888};
+  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);
 
-    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 = plane_datas.size();
-    import_info.modifier = modifier;
-    for (uint32_t i = 0; i < plane_datas.size(); i++) {
-      import_info.fds[i] = plane_datas[i].fd;
-      import_info.offsets[i] = plane_datas[i].offset;
-      import_info.strides[i] = plane_datas[i].stride;
+  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;
     }
-
-    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;
+  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 imported BO
+  // create EGL image from attribute list
   EGLImageKHR image = EglCreateImageKHR(
-      egl_.display, EGL_NO_CONTEXT, EGL_NATIVE_PIXMAP_KHR, imported, nullptr);
+      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());
-    gbm_bo_destroy(imported);
     return src;
   }
 
@@ -527,12 +594,6 @@
     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;
@@ -541,15 +602,12 @@
 
   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;
 }
 
diff --git a/modules/desktop_capture/linux/wayland/egl_dmabuf.h b/modules/desktop_capture/linux/wayland/egl_dmabuf.h
index 75a8d81..f1d96b2 100644
--- a/modules/desktop_capture/linux/wayland/egl_dmabuf.h
+++ b/modules/desktop_capture/linux/wayland/egl_dmabuf.h
@@ -51,6 +51,8 @@
   bool IsEglInitialized() const { return egl_initialized_; }
 
  private:
+  bool GetClientExtensions(EGLDisplay dpy, EGLint name);
+
   bool egl_initialized_ = false;
   bool has_image_dma_buf_import_ext_ = false;
   int32_t drm_fd_ = -1;               // for GBM buffer mmap
diff --git a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
index 92dbbc8..f49bf1e 100644
--- a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
+++ b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc
@@ -104,21 +104,28 @@
   spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
 
   if (modifiers.size()) {
-    spa_pod_builder_prop(
-        builder, SPA_FORMAT_VIDEO_modifier,
-        SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
-    spa_pod_builder_push_choice(builder, &frames[1], SPA_CHOICE_Enum, 0);
-    // modifiers from the array
-    for (int64_t val : modifiers) {
-      spa_pod_builder_long(builder, val);
-      // Add the first modifier twice as the very first value is the default
-      // option
-      if (first) {
+    if (modifiers.size() == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) {
+      spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier,
+                           SPA_POD_PROP_FLAG_MANDATORY);
+      spa_pod_builder_long(builder, modifiers[0]);
+    } else {
+      spa_pod_builder_prop(
+          builder, SPA_FORMAT_VIDEO_modifier,
+          SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
+      spa_pod_builder_push_choice(builder, &frames[1], SPA_CHOICE_Enum, 0);
+
+      // modifiers from the array
+      for (int64_t val : modifiers) {
         spa_pod_builder_long(builder, val);
-        first = false;
+        // Add the first modifier twice as the very first value is the default
+        // option
+        if (first) {
+          spa_pod_builder_long(builder, val);
+          first = false;
+        }
       }
+      spa_pod_builder_pop(builder, &frames[1]);
     }
-    spa_pod_builder_pop(builder, &frames[1]);
   }
 
   spa_pod_builder_add(
@@ -471,8 +478,12 @@
   // Check if the PipeWire and DRM libraries are available.
   paths[kModulePipewire].push_back(kPipeWireLib);
   paths[kModuleDrm].push_back(kDrmLib);
+
   if (!InitializeStubs(paths)) {
-    RTC_LOG(LS_ERROR) << "Failed to load the PipeWire library and symbols.";
+    RTC_LOG(LS_ERROR)
+        << "One of following libraries is missing on your system:\n"
+        << " - PipeWire (" << kPipeWireLib << ")\n"
+        << " - drm (" << kDrmLib << ")";
     return false;
   }
 #endif  // defined(WEBRTC_DLOPEN_PIPEWIRE)