/*
 *  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 <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;

// GBM
typedef struct gbm_device *(*gbm_create_device_func)(int fd);
typedef void (*gbm_device_destroy_func)(struct gbm_device *gbm);

gbm_create_device_func Gbm_create_device = nullptr;
gbm_device_destroy_func Gbm_device_destroy = nullptr;

// libdrm
typedef int (*drmGetDevices2_func)(uint32_t flags, drmDevicePtr devices[], int max_devices);
typedef void (*drmFreeDevices_func)(drmDevicePtr devices[], int count);

drmGetDevices2_func DrmGetDevices2 = nullptr;
drmFreeDevices_func DrmFreeDevices = 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;
}

static void* g_lib_gbm = nullptr;

RTC_NO_SANITIZE("cfi-icall")
static bool OpenGBM() {
  g_lib_gbm = dlopen("libgbm.so.1", RTLD_NOW | RTLD_GLOBAL);
  if (g_lib_gbm) {
    return true;
  }

  return false;
}

RTC_NO_SANITIZE("cfi-icall")
static bool LoadGBM() {
  if (OpenGBM()) {
    Gbm_create_device = (gbm_create_device_func)dlsym(g_lib_gbm, "gbm_create_device");
    Gbm_device_destroy = (gbm_device_destroy_func)dlsym(g_lib_gbm, "gbm_device_destroy");
    return Gbm_create_device && Gbm_device_destroy;
  }

  return false;
}

static void* g_lib_drm = nullptr;

RTC_NO_SANITIZE("cfi-icall")
static bool OpenDRM() {
  g_lib_drm = dlopen("libdrm.so.2", RTLD_NOW | RTLD_GLOBAL);
  if (g_lib_drm) {
    return true;
  }

  return false;
}

RTC_NO_SANITIZE("cfi-icall")
static bool LoadDRM() {
  if (OpenDRM()) {
    DrmGetDevices2 = (drmGetDevices2_func)dlsym(g_lib_drm, "drmGetDevices2");
    DrmFreeDevices = (drmFreeDevices_func)dlsym(g_lib_drm, "drmFreeDevices");
    return DrmGetDevices2 && DrmFreeDevices;
  }

  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";
    if (!LoadDRM()) {
      RTC_LOG(LS_ERROR) << "Unable to load libdrm library.";
      return;
    }

    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;
    }

    if (!LoadGBM()) {
      RTC_LOG(LS_ERROR) << "Unable to load GBM library.";
      close(drm_fd_);
      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
