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

// Fake GBM is a GBM "implementation" designed to run in a completely headless
// environment. This means that fake GBM does not need an actual GEM driver to
// operate, and instead uses simple tmpfiles to back its memory allocation.
//
// The motivation behind Fake GBM is that it was discovered that not all system
// GBM implementations behave the same, and this can sometimes cause problems
// when testing hardware accelerated video decoders on different systems, even
// when using the fake VA-API backend.
//
// Fake GBM should be ABI compatible with Minigbm. So usually the shared library
// built for Fake GBM will simply be DLL injected (using LD_PRELOAD generally)
// into the test process.

#ifdef UNSAFE_BUFFERS_BUILD
// We need to conform to the GBM API, which unfortunately involves a lot of
// unsafe buffer access to maintain C99 compatibility.
#pragma allow_unsafe_buffers
#endif

#include <errno.h>
#include <gbm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

#include "base/logging.h"

#define PAGE_SIZE 4096

#define ALIGN(x, y) (((x + y - 1) / y) * y)

#define DRM_NODE_RENDER 2

#define GBM_EXPORT __attribute__((visibility("default")))

// This is an opaque type in GBM, so its definition does not really matter.
struct gbm_device {
  uint8_t pad;
};

// This is technically not GBM, it's DRM, but it's necessary for some GBM and
// libva clients to be happy with our fake drivers.
extern "C" GBM_EXPORT int drmGetNodeTypeFromFd(int fd) {
  return DRM_NODE_RENDER;
}

struct gbm_bo {
  struct gbm_import_fd_modifier_data meta;
};

struct gbm_bo_mapping {
  void* addr;
  size_t size;
};

uint32_t get_y_subsample(struct gbm_bo* bo, size_t plane) {
  if (plane == 0) {
    return 1;
  } else {
    return 2;
  }
}

uint32_t get_x_subsample(struct gbm_bo* bo, size_t plane) {
  if (plane == 0) {
    return 1;
  }

  switch (bo->meta.format) {
    case GBM_FORMAT_NV12:
    case GBM_FORMAT_P010:
      return 1;
    case GBM_FORMAT_YUV420:
      return 2;
    default:
      CHECK(false);
  }
}

uint32_t get_plane_min_size(struct gbm_bo* bo, size_t plane) {
  return bo->meta.strides[plane] *
         ALIGN(bo->meta.height, get_y_subsample(bo, plane)) /
         get_y_subsample(bo, plane);
}

extern "C" GBM_EXPORT struct gbm_device* gbm_create_device(int fd) {
  return new struct gbm_device;
}

extern "C" GBM_EXPORT void gbm_device_destroy(struct gbm_device* gbm) {
  delete gbm;
}

extern "C" GBM_EXPORT uint32_t gbm_bo_get_bpp(struct gbm_bo* bo) {
  CHECK(bo);

  switch (bo->meta.format) {
    case GBM_FORMAT_NV12:
    case GBM_FORMAT_YUV420:
      return 1;
    case GBM_FORMAT_P010:
      return 2;
    default:
      CHECK(false);
  }
}

extern "C" GBM_EXPORT int gbm_bo_get_plane_count(struct gbm_bo* bo) {
  CHECK(bo);

  switch (bo->meta.format) {
    case GBM_FORMAT_NV12:
    case GBM_FORMAT_P010:
      return 2;
    case GBM_FORMAT_YUV420:
      return 3;
    default:
      CHECK(false);
  }
}

extern "C" GBM_EXPORT struct gbm_bo* gbm_bo_create(struct gbm_device* gbm,
                                                   uint32_t width,
                                                   uint32_t height,
                                                   uint32_t format,
                                                   uint32_t flags) {
  CHECK(format == GBM_FORMAT_NV12 || format == GBM_FORMAT_P010 ||
        format == GBM_FORMAT_YUV420);

  FILE* backing_file = tmpfile();
  CHECK(backing_file);
  int fd = fileno(backing_file);

  struct gbm_bo* bo = new struct gbm_bo;
  bo->meta.num_fds = 1;
  bo->meta.fds[0] = fd;
  bo->meta.width = width;
  bo->meta.height = height;
  bo->meta.format = format;
  bo->meta.modifier = 0;

  uint32_t size = 0;
  for (int i = 0; i < gbm_bo_get_plane_count(bo); i++) {
    bo->meta.offsets[i] = size;
    bo->meta.strides[i] = ALIGN(width, get_x_subsample(bo, i)) /
                          get_x_subsample(bo, i) * gbm_bo_get_bpp(bo);
    size += ALIGN(get_plane_min_size(bo, i), PAGE_SIZE);
  }

  CHECK(ftruncate(fd, size) == 0);

  return bo;
}

extern "C" GBM_EXPORT void gbm_bo_destroy(struct gbm_bo* bo) {
  CHECK(bo);
  delete bo;
}

extern "C" GBM_EXPORT void* gbm_bo_map2(struct gbm_bo* bo,
                                        uint32_t x,
                                        uint32_t y,
                                        uint32_t width,
                                        uint32_t height,
                                        uint32_t transfer_flags,
                                        uint32_t* stride,
                                        void** map_data,
                                        int plane) {
  CHECK(bo);
  CHECK(x + width <= bo->meta.width);
  CHECK(y + height <= bo->meta.height);
  CHECK(ALIGN(x, get_x_subsample(bo, plane)) == x);
  CHECK(ALIGN(y, get_y_subsample(bo, plane)) == y);
  CHECK(static_cast<int>(plane) < gbm_bo_get_plane_count(bo));

  size_t size = ALIGN(get_plane_min_size(bo, plane), PAGE_SIZE);
  void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,
                    bo->meta.fds[0], bo->meta.offsets[plane]);
  CHECK(addr);
  CHECK(addr != MAP_FAILED);

  struct gbm_bo_mapping** _map_data =
      reinterpret_cast<struct gbm_bo_mapping**>(map_data);
  *_map_data = new struct gbm_bo_mapping;
  (*_map_data)->addr = addr;
  (*_map_data)->size = size;
  *stride = bo->meta.strides[plane];

  size_t offset = y / get_y_subsample(bo, plane) * bo->meta.strides[plane] +
                  x / get_x_subsample(bo, plane) * gbm_bo_get_bpp(bo);
  return reinterpret_cast<void*>(reinterpret_cast<uint8_t*>(addr) + offset);
}

extern "C" GBM_EXPORT void gbm_bo_unmap(struct gbm_bo* bo, void* map_data) {
  CHECK(bo);

  struct gbm_bo_mapping* _map_data =
      reinterpret_cast<struct gbm_bo_mapping*>(map_data);
  void* addr = _map_data->addr;
  size_t size = _map_data->size;
  munmap(addr, size);
  delete _map_data;
}

extern "C" GBM_EXPORT struct gbm_bo* gbm_bo_import(struct gbm_device* gbm,
                                                   uint32_t type,
                                                   void* buffer,
                                                   uint32_t usage) {
  CHECK(buffer);
  CHECK(type == GBM_BO_IMPORT_FD_MODIFIER || type == GBM_BO_IMPORT_FD_PLANAR);

  struct gbm_bo* bo = new struct gbm_bo;
  bo->meta = *reinterpret_cast<struct gbm_import_fd_modifier_data*>(buffer);

  return bo;
}

extern "C" GBM_EXPORT int gbm_bo_get_fd(struct gbm_bo* bo) {
  CHECK(bo);

  return dup(bo->meta.fds[0]);
}

extern "C" GBM_EXPORT int gbm_bo_get_fd_for_plane(struct gbm_bo* bo,
                                                  int plane) {
  CHECK(bo);
  CHECK(plane >= 0);
  CHECK(static_cast<int>(plane) < gbm_bo_get_plane_count(bo));

  return gbm_bo_get_fd(bo);
}

extern "C" GBM_EXPORT uint64_t gbm_bo_get_modifier(struct gbm_bo* bo) {
  return 0;
}

extern "C" GBM_EXPORT uint32_t gbm_bo_get_format(struct gbm_bo* bo) {
  CHECK(bo);

  return bo->meta.format;
}

extern "C" GBM_EXPORT uint32_t gbm_bo_get_offset(struct gbm_bo* bo,
                                                 int plane) {
  CHECK(bo);
  CHECK(static_cast<int>(plane) < gbm_bo_get_plane_count(bo));

  return bo->meta.offsets[plane];
}

extern "C" GBM_EXPORT uint32_t gbm_bo_get_stride_for_plane(struct gbm_bo* bo,
                                                           int plane) {
  CHECK(bo);
  CHECK(static_cast<int>(plane) < gbm_bo_get_plane_count(bo));

  return bo->meta.strides[plane];
}

extern "C" GBM_EXPORT uint32_t gbm_bo_get_width(struct gbm_bo* bo) {
  CHECK(bo);

  return bo->meta.width;
}

extern "C" GBM_EXPORT uint32_t gbm_bo_get_height(struct gbm_bo* bo) {
  CHECK(bo);

  return bo->meta.height;
}