// Copyright 2022 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.
//
#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 "fake_surface.h"

#include <libdrm/drm_fourcc.h>
#include <va/va_drmcommon.h>

#include <unordered_set>

#include "base/logging.h"
#include "base/ptr_util.h"

namespace libvafake {

FakeSurface::FakeSurface(FakeSurface::IdType id,
                         unsigned int format,
                         uint32_t va_fourcc,
                         unsigned int width,
                         unsigned int height,
                         std::vector<VASurfaceAttrib> attrib_list,
                         ScopedBOMapping mapped_bo)
    : id_(id),
      format_(format),
      va_fourcc_(va_fourcc),
      width_(width),
      height_(height),
      attrib_list_(std::move(attrib_list)),
      mapped_bo_(std::move(mapped_bo)) {}

FakeSurface::~FakeSurface() = default;

std::unique_ptr<FakeSurface> FakeSurface::Create(
    IdType id,
    unsigned int format,
    unsigned int width,
    unsigned int height,
    std::vector<VASurfaceAttrib> attrib_list,
    ScopedBOMappingFactory& scoped_bo_mapping_factory) {
  // There are no specified attributes to this surface
  if (attrib_list.empty()) {
    return base::WrapUnique(new FakeSurface(id, format, /*va_fourcc=*/0u, width,
                                            height, std::move(attrib_list),
                                            /*mapped_bo=*/{}));
  }

  // Verify attributes and extract surface descriptor.
  std::unordered_set<VASurfaceAttribType> attribs;
  VADRMPRIMESurfaceDescriptor* surf_desc = nullptr;
  for (auto attrib : attrib_list) {
    // Some libva clients are quirky about their surface attributes, so
    // simply ignore unexpected attribute types.
    CHECK(attribs.find(attrib.type) == attribs.end());
    attribs.insert(attrib.type);

    if (attrib.type == VASurfaceAttribMemoryType) {
      CHECK_EQ(attrib.value.type, VAGenericValueTypeInteger);
      CHECK_EQ(attrib.value.value.i, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2);
    } else if (attrib.type == VASurfaceAttribExternalBufferDescriptor) {
      CHECK_EQ(attrib.value.type, VAGenericValueTypePointer);
      surf_desc =
          static_cast<VADRMPRIMESurfaceDescriptor*>(attrib.value.value.p);
    }
  }
  CHECK(surf_desc);
  CHECK(attribs.find(VASurfaceAttribMemoryType) != attribs.end());

  struct gbm_import_fd_modifier_data fd_data {};

  CHECK_GT(width, 0u);
  CHECK_GT(height, 0u);
  CHECK_EQ(static_cast<unsigned int>(surf_desc->width), width);
  CHECK_EQ(static_cast<unsigned int>(surf_desc->height), height);

  fd_data.width = surf_desc->width;
  fd_data.height = surf_desc->height;
  CHECK(surf_desc->fourcc == VA_FOURCC_NV12 ||
        surf_desc->fourcc == VA_FOURCC_P010);
  const unsigned int expected_rt_format = surf_desc->fourcc == VA_FOURCC_NV12
                                              ? VA_RT_FORMAT_YUV420
                                              : VA_RT_FORMAT_YUV420_10;

  const uint32_t expected_drm_format =
      surf_desc->fourcc == VA_FOURCC_NV12 ? DRM_FORMAT_NV12 : DRM_FORMAT_P010;
  const uint32_t expected_num_planes = 2u;
  fd_data.format =
      surf_desc->fourcc == VA_FOURCC_NV12 ? GBM_FORMAT_NV12 : GBM_FORMAT_P010;

  CHECK_EQ(format, expected_rt_format);
  CHECK_GT(surf_desc->num_objects, 0u);
  CHECK_LE(surf_desc->num_objects,
           static_cast<uint32_t>(std::size(surf_desc->objects)));
  fd_data.num_fds = surf_desc->num_objects;

  fd_data.modifier = surf_desc->objects[0].drm_format_modifier;
  for (uint32_t i = 1u; i < surf_desc->num_objects; i++) {
    CHECK_EQ(surf_desc->objects[i].drm_format_modifier, fd_data.modifier);
  }

  // In general, the planes may be distributed across multiple layers, but let's
  // only handle the situation in which all the planes are in one layer.
  CHECK_EQ(surf_desc->num_layers, 1u);
  CHECK_EQ(surf_desc->layers[0].drm_format, expected_drm_format);
  CHECK_EQ(surf_desc->layers[0].num_planes, expected_num_planes);

  for (uint32_t plane = 0u; plane < expected_num_planes; ++plane) {
    CHECK_LT(surf_desc->layers[0].object_index[plane], surf_desc->num_objects);
    fd_data.fds[plane] =
        surf_desc->objects[surf_desc->layers[0].object_index[plane]].fd;
    fd_data.strides[plane] =
        static_cast<int>(surf_desc->layers[0].pitch[plane]);
    fd_data.offsets[plane] =
        static_cast<int>(surf_desc->layers[0].offset[plane]);
  }

  ScopedBOMapping mapped_bo = scoped_bo_mapping_factory.Create(fd_data);
  CHECK(!!mapped_bo);
  return base::WrapUnique(new FakeSurface(id, format, surf_desc->fourcc, width,
                                          height, std::move(attrib_list),
                                          std::move(mapped_bo)));
}

FakeSurface::IdType FakeSurface::GetID() const {
  return id_;
}

unsigned int FakeSurface::GetFormat() const {
  return format_;
}

uint32_t FakeSurface::GetVAFourCC() const {
  return va_fourcc_;
}

unsigned int FakeSurface::GetWidth() const {
  return width_;
}

unsigned int FakeSurface::GetHeight() const {
  return height_;
}

const std::vector<VASurfaceAttrib>& FakeSurface::GetSurfaceAttribs() const {
  return attrib_list_;
}

const ScopedBOMapping& FakeSurface::GetMappedBO() const {
  return mapped_bo_;
}

}  // namespace libvafake