// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/exo/wayland/clients/client_base.h"

#include <aura-shell-client-protocol.h>
#include <fcntl.h>
#include <linux-dmabuf-unstable-v1-client-protocol.h>
#include <presentation-time-client-protocol.h>
#include <wayland-client-core.h>
#include <wayland-client-protocol.h>

#include <memory>
#include <string>
#include <utility>

#include "base/command_line.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "third_party/skia/include/gpu/gl/GrGLAssembleInterface.h"
#include "third_party/skia/include/gpu/gl/GrGLInterface.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_enums.h"
#include "ui/gl/gl_surface_egl.h"
#include "ui/gl/init/gl_factory.h"

#if defined(USE_GBM)
#include <drm_fourcc.h>
#include <gbm.h>
#include <xf86drm.h>

#include "ui/ozone/public/ozone_platform.h"  // nogncheck
#endif

namespace exo {
namespace wayland {
namespace clients {
namespace switches {

// Specifies the client buffer size.
const char kSize[] = "size";

// Specifies the client scale factor (ie. number of physical pixels per DIP).
const char kScale[] = "scale";

// Specifies the client transform (ie. rotation).
const char kTransform[] = "transform";

// Specifies if the background should be transparent.
const char kTransparentBackground[] = "transparent-background";

// Use drm buffer instead of shared memory.
const char kUseDrm[] = "use-drm";

// Specifies if client should be fullscreen.
const char kFullscreen[] = "fullscreen";

}  // namespace switches

namespace {

// Buffer format.
const int32_t kShmFormat = WL_SHM_FORMAT_ARGB8888;
const SkColorType kColorType = kBGRA_8888_SkColorType;
#if defined(USE_GBM)
const GLenum kSizedInternalFormat = GL_BGRA8_EXT;
#endif
const size_t kBytesPerPixel = 4;

#if defined(USE_GBM)
// DRI render node path template.
const char kDriRenderNodeTemplate[] = "/dev/dri/renderD%u";
#endif

void RegistryHandler(void* data,
                     wl_registry* registry,
                     uint32_t id,
                     const char* interface,
                     uint32_t version) {
  ClientBase::Globals* globals = static_cast<ClientBase::Globals*>(data);

  if (strcmp(interface, "wl_compositor") == 0) {
    globals->compositor.reset(static_cast<wl_compositor*>(
        wl_registry_bind(registry, id, &wl_compositor_interface, 3)));
  } else if (strcmp(interface, "wl_shm") == 0) {
    globals->shm.reset(static_cast<wl_shm*>(
        wl_registry_bind(registry, id, &wl_shm_interface, 1)));
  } else if (strcmp(interface, "wl_shell") == 0) {
    globals->shell.reset(static_cast<wl_shell*>(
        wl_registry_bind(registry, id, &wl_shell_interface, 1)));
  } else if (strcmp(interface, "wl_seat") == 0) {
    globals->seat.reset(static_cast<wl_seat*>(
        wl_registry_bind(registry, id, &wl_seat_interface, 5)));
  } else if (strcmp(interface, "wp_presentation") == 0) {
    globals->presentation.reset(static_cast<wp_presentation*>(
        wl_registry_bind(registry, id, &wp_presentation_interface, 1)));
  } else if (strcmp(interface, "zaura_shell") == 0) {
    globals->aura_shell.reset(static_cast<zaura_shell*>(
        wl_registry_bind(registry, id, &zaura_shell_interface, 5)));
  } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) {
    globals->linux_dmabuf.reset(static_cast<zwp_linux_dmabuf_v1*>(
        wl_registry_bind(registry, id, &zwp_linux_dmabuf_v1_interface, 2)));
  } else if (strcmp(interface, "wl_subcompositor") == 0) {
    globals->subcompositor.reset(static_cast<wl_subcompositor*>(
        wl_registry_bind(registry, id, &wl_subcompositor_interface, 1)));
  } else if (strcmp(interface, "zwp_input_timestamps_manager_v1") == 0) {
    globals->input_timestamps_manager.reset(
        static_cast<zwp_input_timestamps_manager_v1*>(wl_registry_bind(
            registry, id, &zwp_input_timestamps_manager_v1_interface, 1)));
  }
}

void RegistryRemover(void* data, wl_registry* registry, uint32_t id) {
  LOG(WARNING) << "Got a registry losing event for " << id;
}

void BufferRelease(void* data, wl_buffer* /* buffer */) {
  ClientBase::Buffer* buffer = static_cast<ClientBase::Buffer*>(data);
  buffer->busy = false;
}

wl_registry_listener g_registry_listener = {RegistryHandler, RegistryRemover};

wl_buffer_listener g_buffer_listener = {BufferRelease};

#if defined(USE_GBM)
const GrGLInterface* GrGLCreateNativeInterface() {
  return GrGLAssembleInterface(nullptr, [](void* ctx, const char name[]) {
    return eglGetProcAddress(name);
  });
}

#if defined(USE_VULKAN)
uint32_t VulkanChooseGraphicsQueueFamily(VkPhysicalDevice device) {
  uint32_t properties_number = 0;
  vkGetPhysicalDeviceQueueFamilyProperties(device, &properties_number, nullptr);

  std::vector<VkQueueFamilyProperties> properties(properties_number);
  vkGetPhysicalDeviceQueueFamilyProperties(device, &properties_number,
                                           properties.data());

  // Choose the first graphics queue.
  for (uint32_t i = 0; i < properties_number; ++i) {
    if ((properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)) {
      DCHECK_GT(properties[i].queueCount, 0u);
      return i;
    }
  }
  return UINT32_MAX;
}

std::unique_ptr<ScopedVkInstance> CreateVkInstance() {
  VkApplicationInfo application_info{
      .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
      .pApplicationName = nullptr,
      .applicationVersion = 0,
      .pEngineName = nullptr,
      .engineVersion = 0,
      .apiVersion = VK_MAKE_VERSION(1, 0, 0),
  };
  VkInstanceCreateInfo create_info{
      .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
      .flags = 0,
      .pApplicationInfo = &application_info,
      .enabledLayerCount = 0,
      .ppEnabledLayerNames = nullptr,
      .enabledExtensionCount = 0,
      .ppEnabledExtensionNames = nullptr,
  };

  std::unique_ptr<ScopedVkInstance> vk_instance(new ScopedVkInstance());
  VkResult result =
      vkCreateInstance(&create_info, nullptr, vk_instance->receive());
  CHECK_EQ(VK_SUCCESS, result)
      << "Failed to create a Vulkan instance. Do you have an ICD "
         "driver (e.g: "
         "/usr/share/vulkan/icd.d/intel_icd.x86_64.json). Does it "
         "point to a valid .so? Try to set export VK_LOADER_DEBUG=all "
         "for more debuggining info.";
  return vk_instance;
}

std::unique_ptr<ScopedVkDevice> CreateVkDevice(VkInstance vk_instance,
                                               uint32_t* queue_family_index) {
  uint32_t physical_devices_number = 1;
  VkPhysicalDevice physical_device;
  VkResult result = vkEnumeratePhysicalDevices(
      vk_instance, &physical_devices_number, &physical_device);
  CHECK(result == VK_SUCCESS || result == VK_INCOMPLETE)
      << "Failed to enumerate physical devices.";

  *queue_family_index = VulkanChooseGraphicsQueueFamily(physical_device);
  CHECK_NE(UINT32_MAX, *queue_family_index);

  float priority = 1.0f;
  VkDeviceQueueCreateInfo device_queue_create_info{
      .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
      .queueFamilyIndex = *queue_family_index,
      .queueCount = 1,
      .pQueuePriorities = &priority,
  };
  VkDeviceCreateInfo device_create_info{
      .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
      .queueCreateInfoCount = 1,
      .pQueueCreateInfos = &device_queue_create_info,
  };
  std::unique_ptr<ScopedVkDevice> vk_device(new ScopedVkDevice());
  result = vkCreateDevice(physical_device, &device_create_info, nullptr,
                          vk_device->receive());
  CHECK_EQ(VK_SUCCESS, result);
  return vk_device;
}

std::unique_ptr<ScopedVkRenderPass> CreateVkRenderPass(VkDevice vk_device) {
  VkAttachmentDescription attach_description[]{
      {
          .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
          .samples = static_cast<VkSampleCountFlagBits>(1),
          .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
          .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
          .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
          .finalLayout = VK_IMAGE_LAYOUT_GENERAL,
      },
  };
  VkAttachmentReference attachment_reference[]{
      {
          .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
      },
  };
  VkSubpassDescription subpass_description[]{
      {
          .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
          .colorAttachmentCount = 1,
          .pColorAttachments = attachment_reference,
      },
  };
  VkRenderPassCreateInfo render_pass_create_info{
      .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
      .attachmentCount = 1,
      .pAttachments = attach_description,
      .subpassCount = 1,
      .pSubpasses = subpass_description,
  };
  std::unique_ptr<ScopedVkRenderPass> vk_render_pass(
      new ScopedVkRenderPass(VK_NULL_HANDLE, {vk_device}));
  VkResult result = vkCreateRenderPass(vk_device, &render_pass_create_info,
                                       nullptr, vk_render_pass->receive());
  CHECK_EQ(VK_SUCCESS, result);
  return vk_render_pass;
}

std::unique_ptr<ScopedVkCommandPool> CreateVkCommandPool(
    VkDevice vk_device,
    uint32_t queue_family_index) {
  VkCommandPoolCreateInfo command_pool_create_info{
      .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
      .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
               VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
      .queueFamilyIndex = queue_family_index,
  };
  std::unique_ptr<ScopedVkCommandPool> vk_command_pool(
      new ScopedVkCommandPool(VK_NULL_HANDLE, {vk_device}));
  VkResult result = vkCreateCommandPool(vk_device, &command_pool_create_info,
                                        nullptr, vk_command_pool->receive());
  CHECK_EQ(VK_SUCCESS, result);
  return vk_command_pool;
}

#endif  // defined(USE_VULKAN)
#endif  // defined(USE_GBM)

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// ClientBase::InitParams, public:

ClientBase::InitParams::InitParams() {
#if defined(USE_GBM)
  drm_format = DRM_FORMAT_ABGR8888;
  bo_usage = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING | GBM_BO_USE_TEXTURING;
#endif
}

ClientBase::InitParams::~InitParams() {}

bool ClientBase::InitParams::FromCommandLine(
    const base::CommandLine& command_line) {
  if (command_line.HasSwitch(switches::kSize)) {
    std::string size_str = command_line.GetSwitchValueASCII(switches::kSize);
    if (sscanf(size_str.c_str(), "%zdx%zd", &width, &height) != 2) {
      LOG(ERROR) << "Invalid value for " << switches::kSize;
      return false;
    }
  }

  if (command_line.HasSwitch(switches::kScale) &&
      !base::StringToInt(command_line.GetSwitchValueASCII(switches::kScale),
                         &scale)) {
    LOG(ERROR) << "Invalid value for " << switches::kScale;
    return false;
  }

  if (command_line.HasSwitch(switches::kTransform)) {
    std::string transform_str =
        command_line.GetSwitchValueASCII(switches::kTransform);
    if (transform_str == "0") {
      transform = WL_OUTPUT_TRANSFORM_NORMAL;
    } else if (transform_str == "90") {
      transform = WL_OUTPUT_TRANSFORM_90;
    } else if (transform_str == "180") {
      transform = WL_OUTPUT_TRANSFORM_180;
    } else if (transform_str == "270") {
      transform = WL_OUTPUT_TRANSFORM_270;
    } else {
      LOG(ERROR) << "Invalid value for " << switches::kTransform;
      return false;
    }
  }

  use_drm = command_line.HasSwitch(switches::kUseDrm);
  if (use_drm)
    use_drm_value = command_line.GetSwitchValueASCII(switches::kUseDrm);

  fullscreen = command_line.HasSwitch(switches::kFullscreen);
  transparent_background =
      command_line.HasSwitch(switches::kTransparentBackground);
  return true;
}

////////////////////////////////////////////////////////////////////////////////
// ClientBase::Globals, public:

ClientBase::Globals::Globals() {}

ClientBase::Globals::~Globals() {}

////////////////////////////////////////////////////////////////////////////////
// ClientBase::Buffer, public:

ClientBase::Buffer::Buffer() {}

ClientBase::Buffer::~Buffer() {}

////////////////////////////////////////////////////////////////////////////////
// ClientBase, public:

bool ClientBase::Init(const InitParams& params) {
  size_.SetSize(params.width, params.height);
  scale_ = params.scale;
  transform_ = params.transform;
  switch (params.transform) {
    case WL_OUTPUT_TRANSFORM_NORMAL:
    case WL_OUTPUT_TRANSFORM_180:
      surface_size_.SetSize(params.width, params.height);
      break;
    case WL_OUTPUT_TRANSFORM_90:
    case WL_OUTPUT_TRANSFORM_270:
      surface_size_.SetSize(params.height, params.width);
      break;
    default:
      NOTREACHED();
      break;
  }
  surface_size_ = gfx::ToCeiledSize(
      gfx::ScaleSize(gfx::SizeF(surface_size_), 1.0f / params.scale));
  fullscreen_ = params.fullscreen;
  transparent_background_ = params.transparent_background;

  display_.reset(wl_display_connect(nullptr));
  if (!display_) {
    LOG(ERROR) << "wl_display_connect failed";
    return false;
  }
  registry_.reset(wl_display_get_registry(display_.get()));
  wl_registry_add_listener(registry_.get(), &g_registry_listener, &globals_);

  wl_display_roundtrip(display_.get());

  if (!globals_.compositor) {
    LOG(ERROR) << "Can't find compositor interface";
    return false;
  }
  if (!globals_.shm) {
    LOG(ERROR) << "Can't find shm interface";
    return false;
  }
  if (!globals_.presentation) {
    LOG(ERROR) << "Can't find presentation interface";
    return false;
  }
  if (params.use_drm && !globals_.linux_dmabuf) {
    LOG(ERROR) << "Can't find linux_dmabuf interface";
    return false;
  }
  if (!globals_.shell) {
    LOG(ERROR) << "Can't find shell interface";
    return false;
  }
  if (!globals_.seat) {
    LOG(ERROR) << "Can't find seat interface";
    return false;
  }
  if (!globals_.aura_shell) {
    LOG(ERROR) << "Can't find aura shell interface";
    return false;
  }

#if defined(USE_GBM)
  sk_sp<const GrGLInterface> native_interface;
  if (params.use_drm) {
    // Number of files to look for when discovering DRM devices.
    const uint32_t kDrmMaxMinor = 15;
    const uint32_t kRenderNodeStart = 128;
    const uint32_t kRenderNodeEnd = kRenderNodeStart + kDrmMaxMinor + 1;

    for (uint32_t i = kRenderNodeStart; i < kRenderNodeEnd; i++) {
      std::string dri_render_node(
          base::StringPrintf(kDriRenderNodeTemplate, i));
      base::ScopedFD drm_fd(open(dri_render_node.c_str(), O_RDWR));
      if (drm_fd.get() < 0)
        continue;
      drmVersionPtr drm_version = drmGetVersion(drm_fd.get());
      if (!drm_version) {
        LOG(ERROR) << "Can't get version for device: '" << dri_render_node
                   << "'";
        return false;
      }
      if (strstr(drm_version->name, params.use_drm_value.c_str())) {
        drm_fd_ = std::move(drm_fd);
        break;
      }
    }

    if (drm_fd_.get() < 0) {
      LOG_IF(ERROR, params.use_drm)
          << "Can't find drm device: '" << params.use_drm_value << "'";
      LOG_IF(ERROR, !params.use_drm) << "Can't find drm device";
      return false;
    }

    device_.reset(gbm_create_device(drm_fd_.get()));
    if (!device_) {
      LOG(ERROR) << "Can't create gbm device";
      return false;
    }
    ui::OzonePlatform::InitParams params;
    params.single_process = true;
    ui::OzonePlatform::InitializeForGPU(params);
    bool gl_initialized = gl::init::InitializeGLOneOff();
    DCHECK(gl_initialized);
    gl_surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
    gl_context_ =
        gl::init::CreateGLContext(nullptr,  // share_group
                                  gl_surface_.get(), gl::GLContextAttribs());

    make_current_.reset(
        new ui::ScopedMakeCurrent(gl_context_.get(), gl_surface_.get()));

    if (gl::GLSurfaceEGL::HasEGLExtension("EGL_EXT_image_flush_external") ||
        gl::GLSurfaceEGL::HasEGLExtension("EGL_ARM_implicit_external_sync")) {
      egl_sync_type_ = EGL_SYNC_FENCE_KHR;
    }
    if (gl::GLSurfaceEGL::HasEGLExtension("EGL_ANDROID_native_fence_sync")) {
      egl_sync_type_ = EGL_SYNC_NATIVE_FENCE_ANDROID;
    }

    native_interface = sk_sp<const GrGLInterface>(GrGLCreateNativeInterface());
    DCHECK(native_interface);
    gr_context_ = GrContext::MakeGL(std::move(native_interface));
    DCHECK(gr_context_);

#if defined(USE_VULKAN)
    vk_instance_ = CreateVkInstance();

    uint32_t queue_family_index = UINT32_MAX;
    vk_device_ = CreateVkDevice(vk_instance_->get(), &queue_family_index);
    vk_render_pass_ = CreateVkRenderPass(vk_device_->get());

    vkGetDeviceQueue(vk_device_->get(), queue_family_index, 0, &vk_queue_);

    vk_command_pool_ =
        CreateVkCommandPool(vk_device_->get(), queue_family_index);
#endif  // defined(USE_VULKAN)
  }
#endif  // defined(USE_GBM)
  for (size_t i = 0; i < params.num_buffers; ++i) {
    auto buffer = CreateBuffer(size_, params.drm_format, params.bo_usage);
    if (!buffer) {
      LOG(ERROR) << "Failed to create buffer";
      return false;
    }
    buffers_.push_back(std::move(buffer));
  }

  for (size_t i = 0; i < buffers_.size(); ++i) {
    // If the buffer handle doesn't exist, we would either be killed by the
    // server or die here.
    if (!buffers_[i]->buffer) {
      LOG(ERROR) << "buffer handle uninitialized.";
      return false;
    }
  }

  surface_.reset(static_cast<wl_surface*>(
      wl_compositor_create_surface(globals_.compositor.get())));
  if (!surface_) {
    LOG(ERROR) << "Can't create surface";
    return false;
  }

  if (!transparent_background_) {
    std::unique_ptr<wl_region> opaque_region(static_cast<wl_region*>(
        wl_compositor_create_region(globals_.compositor.get())));
    if (!opaque_region) {
      LOG(ERROR) << "Can't create region";
      return false;
    }

    wl_region_add(opaque_region.get(), 0, 0, size_.width(), size_.height());
    wl_surface_set_opaque_region(surface_.get(), opaque_region.get());
  }
  std::unique_ptr<wl_shell_surface> shell_surface(
      static_cast<wl_shell_surface*>(
          wl_shell_get_shell_surface(globals_.shell.get(), surface_.get())));
  if (!shell_surface) {
    LOG(ERROR) << "Can't get shell surface";
    return false;
  }

  wl_shell_surface_set_title(shell_surface.get(), params.title.c_str());

  std::unique_ptr<zaura_surface> aura_surface(
      static_cast<zaura_surface*>(
          zaura_shell_get_aura_surface(globals_.aura_shell.get(),
                                       surface_.get())));
  if (!aura_surface) {
    LOG(ERROR) << "Can't get aura surface";
    return false;
  }

  zaura_surface_set_frame(aura_surface.get(), ZAURA_SURFACE_FRAME_TYPE_NORMAL);

  if (fullscreen_) {
    wl_shell_surface_set_fullscreen(shell_surface.get(),
                                    WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
                                    0, nullptr);
  } else {
    wl_shell_surface_set_toplevel(shell_surface.get());
  }

  return true;
}

////////////////////////////////////////////////////////////////////////////////
// ClientBase, protected:

ClientBase::ClientBase() {}

ClientBase::~ClientBase() {}

////////////////////////////////////////////////////////////////////////////////
// ClientBase, private:

std::unique_ptr<ClientBase::Buffer> ClientBase::CreateBuffer(
    const gfx::Size& size,
    int32_t drm_format,
    int32_t bo_usage) {
  std::unique_ptr<Buffer> buffer;
#if defined(USE_GBM)
  if (device_) {
    buffer = CreateDrmBuffer(size, drm_format, bo_usage);
    CHECK(buffer) << "Can't create drm buffer";
  }
#endif

  if (!buffer) {
    buffer = std::make_unique<Buffer>();

    size_t stride = size.width() * kBytesPerPixel;
    buffer->shared_memory.reset(new base::SharedMemory());
    buffer->shared_memory->CreateAndMapAnonymous(stride * size.height());
    buffer->shm_pool.reset(wl_shm_create_pool(
        globals_.shm.get(), buffer->shared_memory->handle().GetHandle(),
        buffer->shared_memory->requested_size()));

    buffer->buffer.reset(static_cast<wl_buffer*>(
        wl_shm_pool_create_buffer(buffer->shm_pool.get(), 0, size.width(),
                                  size.height(), stride, kShmFormat)));
    if (!buffer->buffer) {
      LOG(ERROR) << "Can't create buffer";
      return nullptr;
    }

    buffer->sk_surface = SkSurface::MakeRasterDirect(
        SkImageInfo::Make(size.width(), size.height(), kColorType,
                          kOpaque_SkAlphaType),
        static_cast<uint8_t*>(buffer->shared_memory->memory()), stride);
    DCHECK(buffer->sk_surface);
  }

  wl_buffer_add_listener(buffer->buffer.get(), &g_buffer_listener,
                         buffer.get());
  return buffer;
}

std::unique_ptr<ClientBase::Buffer> ClientBase::CreateDrmBuffer(
    const gfx::Size& size,
    int32_t drm_format,
    int32_t bo_usage) {
  std::unique_ptr<Buffer> buffer;
#if defined(USE_GBM)
  if (device_) {
    buffer = std::make_unique<Buffer>();
    buffer->bo.reset(gbm_bo_create(device_.get(), size.width(), size.height(),
                                   drm_format, bo_usage));
    if (!buffer->bo) {
      LOG(ERROR) << "Can't create gbm buffer";
      return nullptr;
    }
    base::ScopedFD fd(gbm_bo_get_plane_fd(buffer->bo.get(), 0));

    buffer->params.reset(
        zwp_linux_dmabuf_v1_create_params(globals_.linux_dmabuf.get()));
    for (size_t i = 0; i < gbm_bo_get_num_planes(buffer->bo.get()); ++i) {
      base::ScopedFD fd(gbm_bo_get_plane_fd(buffer->bo.get(), i));
      uint32_t stride = gbm_bo_get_plane_stride(buffer->bo.get(), i);
      uint32_t offset = gbm_bo_get_plane_offset(buffer->bo.get(), i);
      zwp_linux_buffer_params_v1_add(buffer->params.get(), fd.get(), i, offset,
                                     stride, 0, 0);
    }
    buffer->buffer.reset(zwp_linux_buffer_params_v1_create_immed(
        buffer->params.get(), size.width(), size.height(), drm_format, 0));

    if (gbm_bo_get_num_planes(buffer->bo.get()) != 1)
      return buffer;

    EGLint khr_image_attrs[] = {EGL_DMA_BUF_PLANE0_FD_EXT,
                                fd.get(),
                                EGL_WIDTH,
                                size.width(),
                                EGL_HEIGHT,
                                size.height(),
                                EGL_LINUX_DRM_FOURCC_EXT,
                                drm_format,
                                EGL_DMA_BUF_PLANE0_PITCH_EXT,
                                gbm_bo_get_plane_stride(buffer->bo.get(), 0),
                                EGL_DMA_BUF_PLANE0_OFFSET_EXT,
                                0,
                                EGL_NONE};
    EGLImageKHR image = eglCreateImageKHR(
        eglGetCurrentDisplay(), EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT,
        nullptr /* no client buffer */, khr_image_attrs);

    buffer->egl_image.reset(new ScopedEglImage(image));
    GLuint texture = 0;
    glGenTextures(1, &texture);
    buffer->texture.reset(new ScopedTexture(texture));
    glBindTexture(GL_TEXTURE_2D, buffer->texture->get());
    glEGLImageTargetTexture2DOES(
        GL_TEXTURE_2D, static_cast<GLeglImageOES>(buffer->egl_image->get()));
    glBindTexture(GL_TEXTURE_2D, 0);

    GrGLTextureInfo texture_info;
    texture_info.fID = buffer->texture->get();
    texture_info.fTarget = GL_TEXTURE_2D;
    texture_info.fFormat = kSizedInternalFormat;
    GrBackendTexture backend_texture(size.width(), size.height(),
                                     GrMipMapped::kNo, texture_info);
    buffer->sk_surface = SkSurface::MakeFromBackendTextureAsRenderTarget(
        gr_context_.get(), backend_texture, kTopLeft_GrSurfaceOrigin,
        /* sampleCnt */ 0, kColorType, /* colorSpace */ nullptr,
        /* props */ nullptr);
    DCHECK(buffer->sk_surface);

#if defined(USE_VULKAN)
    // TODO(dcastagna): remove this hack as soon as the extension
    // "VK_EXT_external_memory_dma_buf" is available.
    PFN_vkCreateDmaBufImageINTEL create_dma_buf_image_intel =
        reinterpret_cast<PFN_vkCreateDmaBufImageINTEL>(
            vkGetDeviceProcAddr(vk_device_->get(), "vkCreateDmaBufImageINTEL"));
    if (!create_dma_buf_image_intel) {
      LOG(ERROR) << "Vulkan wayland clients work only where "
                    "vkCreateDmaBufImageINTEL is available.";
      return nullptr;
    }
    base::ScopedFD vk_image_fd(gbm_bo_get_plane_fd(buffer->bo.get(), 0));
    CHECK(vk_image_fd.is_valid());

    VkDmaBufImageCreateInfo dma_buf_image_create_info{
        .sType = static_cast<VkStructureType>(
            VK_STRUCTURE_TYPE_DMA_BUF_IMAGE_CREATE_INFO_INTEL),
        .fd = vk_image_fd.release(),
        .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
        .extent = (VkExtent3D){size.width(), size.height(), 1},
        .strideInBytes = gbm_bo_get_stride(buffer->bo.get()),
    };

    buffer->vk_memory.reset(
        new ScopedVkDeviceMemory(VK_NULL_HANDLE, {vk_device_->get()}));
    buffer->vk_image.reset(
        new ScopedVkImage(VK_NULL_HANDLE, {vk_device_->get()}));
    VkResult result = create_dma_buf_image_intel(
        vk_device_->get(), &dma_buf_image_create_info, nullptr,
        buffer->vk_memory->receive(), buffer->vk_image->receive());

    if (result != VK_SUCCESS) {
      LOG(ERROR) << "Failed to create a Vulkan image from a dmabuf.";
      return buffer;
    }
    VkImageViewCreateInfo vk_image_view_create_info{
        .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
        .image = buffer->vk_image->get(),
        .viewType = VK_IMAGE_VIEW_TYPE_2D,
        .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32,
        .components =
            {
                .r = VK_COMPONENT_SWIZZLE_IDENTITY,
                .b = VK_COMPONENT_SWIZZLE_IDENTITY,
                .g = VK_COMPONENT_SWIZZLE_IDENTITY,
                .a = VK_COMPONENT_SWIZZLE_IDENTITY,
            },
        .subresourceRange =
            {
                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                .baseMipLevel = 0,
                .levelCount = 1,
                .baseArrayLayer = 0,
                .layerCount = 1,
            },
    };

    buffer->vk_image_view.reset(
        new ScopedVkImageView(VK_NULL_HANDLE, {vk_device_->get()}));
    result = vkCreateImageView(vk_device_->get(), &vk_image_view_create_info,
                               nullptr, buffer->vk_image_view->receive());
    if (result != VK_SUCCESS) {
      LOG(ERROR) << "Failed to create a Vulkan image view.";
      return buffer;
    }
    VkFramebufferCreateInfo vk_framebuffer_create_info{
        .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
        .renderPass = vk_render_pass_->get(),
        .attachmentCount = 1,
        .pAttachments = &buffer->vk_image_view->get(),
        .width = size.width(),
        .height = size.height(),
        .layers = 1,
    };
    buffer->vk_framebuffer.reset(
        new ScopedVkFramebuffer(VK_NULL_HANDLE, {vk_device_->get()}));

    result = vkCreateFramebuffer(vk_device_->get(), &vk_framebuffer_create_info,
                                 nullptr, buffer->vk_framebuffer->receive());
    if (result != VK_SUCCESS) {
      LOG(ERROR) << "Failed to create a Vulkan framebuffer.";
      return buffer;
    }
#endif  // defined(USE_VULKAN)
  }
#endif  // defined(USE_GBM)

  return buffer;
}

ClientBase::Buffer* ClientBase::DequeueBuffer() {
  auto buffer_it =
      std::find_if(buffers_.begin(), buffers_.end(),
                   [](const std::unique_ptr<ClientBase::Buffer>& buffer) {
                     return !buffer->busy;
                   });
  if (buffer_it == buffers_.end())
    return nullptr;

  Buffer* buffer = buffer_it->get();
  buffer->busy = true;
  return buffer;
}

}  // namespace clients
}  // namespace wayland
}  // namespace exo
