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

#include "ui/gfx/linux/gbm_support_x11.h"

#include <fcntl.h>
#include <xcb/xcb.h>

#include <algorithm>
#include <memory>

#include "base/debug/crash_logging.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/posix/eintr_wrapper.h"
#include "ui/gfx/buffer_types.h"
#include "ui/gfx/buffer_usage_util.h"
#include "ui/gfx/linux/drm_util_linux.h"
#include "ui/gfx/linux/gbm_buffer.h"
#include "ui/gfx/linux/gbm_device.h"
#include "ui/gfx/linux/gbm_util.h"
#include "ui/gfx/linux/gbm_wrapper.h"
#include "ui/gfx/x/connection.h"
#include "ui/gfx/x/dri3.h"
#include "ui/gfx/x/future.h"

namespace ui {

namespace {

// Obtain an authenticated DRM fd from X11 and create a GbmDevice with it.
std::unique_ptr<ui::GbmDevice> CreateX11GbmDevice() {
  if (getenv("RUNNING_UNDER_RR") != nullptr) {
    LOG(WARNING) << "Running under rr, disabling dri3";
    return nullptr;
  }

  auto* connection = x11::Connection::Get();
  // |connection| may be nullptr in headless mode.
  if (!connection) {
    LOG(WARNING) << "Could not create x11 connection.";
    return nullptr;
  }

  auto& dri3 = connection->dri3();
  if (!dri3.present()) {
    LOG(WARNING) << "dri3 extension not supported.";
    return nullptr;
  }

  // Obtain an authenticated DRM fd.
  auto reply = dri3.Open({connection->default_root(), 0}).Sync();
  if (!reply) {
    return nullptr;
  }

  base::ScopedFD fd(HANDLE_EINTR(dup(reply->device_fd.get())));
  if (!fd.is_valid()) {
    return nullptr;
  }
  if (HANDLE_EINTR(fcntl(fd.get(), F_SETFD, FD_CLOEXEC)) == -1) {
    return nullptr;
  }

  return ui::CreateGbmDevice(fd.release());
}

}  // namespace

// static
GBMSupportX11* GBMSupportX11::GetInstance() {
  static base::NoDestructor<GBMSupportX11> instance;
  return instance.get();
}

// static
std::vector<GBMSupportX11::BufferUsageAndSIFormat>
GBMSupportX11::CreateSupportedConfigList(ui::GbmDevice* device) {
  if (!device) {
    return {};
  }

  std::vector<BufferUsageAndSIFormat> configs;
  for (gfx::BufferUsage usage : {
           gfx::BufferUsage::GPU_READ,
           gfx::BufferUsage::SCANOUT,
           gfx::BufferUsage::SCANOUT_CPU_READ_WRITE,
           gfx::BufferUsage::GPU_READ_CPU_READ_WRITE,
           gfx::BufferUsage::SCANOUT_VDA_WRITE,
       }) {
    for (viz::SharedImageFormat format : {
             viz::SinglePlaneFormat::kR_8,
             viz::SinglePlaneFormat::kRG_88,
             viz::SinglePlaneFormat::kRGBA_8888,
             viz::SinglePlaneFormat::kRGBX_8888,
             viz::SinglePlaneFormat::kBGRA_8888,
             viz::SinglePlaneFormat::kBGRX_8888,
             viz::SinglePlaneFormat::kBGRA_1010102,

             // On some Intel setups calling gbm_bo_create() with this format
             // results in a crash caused by an integer-divide-by-zero.
             // TODO(thomasanderson): Enable this format.
             // viz::SinglePlaneFormat::kRGBA_1010102,
             viz::SinglePlaneFormat::kBGR_565,
             viz::MultiPlaneFormat::kNV12,
             viz::MultiPlaneFormat::kYV12,
             viz::MultiPlaneFormat::kP010,
         }) {
      // At least on mesa/amdgpu, gbm_device_is_format_supported() lies.  Test
      // format support by creating a buffer directly.  Use a 2x2 buffer so that
      // NV12 formats get properly tested.
      if (device->CreateBuffer(GetFourCCFormatFromSharedImageFormat(format),
                               gfx::Size(2, 2), BufferUsageToGbmFlags(usage))) {
        configs.push_back(BufferUsageAndSIFormat(usage, format));
      }
    }
  }
  return configs;
}

GBMSupportX11::GBMSupportX11()
    : device_(CreateX11GbmDevice()),
      supported_configs_(CreateSupportedConfigList(device_.get())) {}

GBMSupportX11::~GBMSupportX11() = default;

std::unique_ptr<GbmBuffer> GBMSupportX11::CreateBuffer(
    viz::SharedImageFormat format,
    const gfx::Size& size,
    gfx::BufferUsage usage) {
  if (!device_) {
    LOG(ERROR) << "Can't create buffer -- gbm  device is missing.";
    return nullptr;
  }
  if (!std::ranges::contains(supported_configs_,
                             BufferUsageAndSIFormat(usage, format))) {
    LOG(ERROR) << "Can't create buffer -- unsupported config: usage="
               << gfx::BufferUsageToString(usage)
               << ", format=" << format.ToString();
    return nullptr;
  }

  static base::debug::CrashKeyString* crash_key_string =
      base::debug::AllocateCrashKeyString("buffer_usage_and_format",
                                          base::debug::CrashKeySize::Size64);
  std::string buffer_usage_and_format =
      format.ToString() + std::string(",") + gfx::BufferUsageToString(usage);
  base::debug::ScopedCrashKeyString scoped_crash_key(crash_key_string,
                                                     buffer_usage_and_format);

  return device_->CreateBuffer(GetFourCCFormatFromSharedImageFormat(format),
                               size, BufferUsageToGbmFlags(usage));
}

bool GBMSupportX11::CanCreateBufferForFormat(viz::SharedImageFormat format) {
  return device_ && device_->CanCreateBufferForFormat(
                        GetFourCCFormatFromSharedImageFormat(format));
}

std::unique_ptr<GbmBuffer> GBMSupportX11::CreateBufferFromHandle(
    const gfx::Size& size,
    viz::SharedImageFormat format,
    gfx::NativePixmapHandle handle) {
  if (!device_) {
    LOG(ERROR) << "Can't create buffer from handle -- gbm  device is missing.";
    return nullptr;
  }

  static base::debug::CrashKeyString* crash_key_string =
      base::debug::AllocateCrashKeyString("buffer_from_handle_format",
                                          base::debug::CrashKeySize::Size64);
  base::debug::ScopedCrashKeyString scoped_crash_key(crash_key_string,
                                                     format.ToString());

  return device_->CreateBufferFromHandle(
      GetFourCCFormatFromSharedImageFormat(format), size, std::move(handle));
}

}  // namespace ui
