// Copyright 2020 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 "gpu/command_buffer/service/shared_memory_region_wrapper.h"

#include "base/logging.h"
#include "base/numerics/checked_math.h"
#include "base/system/sys_info.h"
#include "components/viz/common/resources/resource_format_utils.h"
#include "components/viz/common/resources/resource_sizes.h"
#include "ui/gfx/gpu_memory_buffer.h"

namespace gpu {
namespace {

// Validate that |stride| will work for pixels with |size| and |format|.
bool ValidateStride(const gfx::Size size,
                    viz::ResourceFormat format,
                    int32_t stride) {
  if (!base::IsValueInRangeForNumericType<size_t>(stride))
    return false;

  int32_t min_width_in_bytes = 0;
  if (!viz::ResourceSizes::MaybeWidthInBytes(size.width(), format,
                                             &min_width_in_bytes)) {
    return false;
  }

  if (stride < min_width_in_bytes)
    return false;

  // Check that stride is a multiple of pixel byte size.
  int bits_per_pixel = viz::BitsPerPixel(format);
  switch (bits_per_pixel) {
    case 64:
    case 32:
    case 16:
      if (stride % (bits_per_pixel / 8) != 0)
        return false;
      break;
    case 8:
    case 4:
      break;
    default:
      // YVU420 and YUV_420_BIPLANAR format aren't supported.
      NOTREACHED();
      return false;
  }

  return true;
}

}  // namespace

SharedMemoryRegionWrapper::SharedMemoryRegionWrapper() = default;
SharedMemoryRegionWrapper::SharedMemoryRegionWrapper(
    SharedMemoryRegionWrapper&& other) = default;
SharedMemoryRegionWrapper& SharedMemoryRegionWrapper::operator=(
    SharedMemoryRegionWrapper&& other) = default;
SharedMemoryRegionWrapper::~SharedMemoryRegionWrapper() = default;

bool SharedMemoryRegionWrapper::Initialize(
    const gfx::GpuMemoryBufferHandle& handle,
    const gfx::Size& size,
    viz::ResourceFormat format) {
  DCHECK(!mapping_.IsValid());

  if (!handle.region.IsValid()) {
    DLOG(ERROR) << "Invalid GMB shared memory region.";
    return false;
  }

  if (!ValidateStride(size, format, handle.stride)) {
    DLOG(ERROR) << "Invalid GMB stride.";
    return false;
  }

  // Minimize the amount of address space we use but make sure offset is a
  // multiple of page size as required by MapAt().
  size_t allocation_granularity = base::SysInfo::VMAllocationGranularity();
  size_t memory_offset = handle.offset % allocation_granularity;
  size_t map_offset =
      allocation_granularity * (handle.offset / allocation_granularity);

  base::CheckedNumeric<size_t> checked_size = handle.stride;
  checked_size *= size.height();
  checked_size += memory_offset;
  if (!checked_size.IsValid()) {
    DLOG(ERROR) << "Invalid GMB size.";
    return false;
  }

  mapping_ = handle.region.MapAt(static_cast<off_t>(map_offset),
                                 checked_size.ValueOrDie());

  if (!mapping_.IsValid()) {
    DLOG(ERROR) << "Failed to map shared memory.";
    return false;
  }

  offset_ = memory_offset;
  stride_ = handle.stride;

  return true;
}

bool SharedMemoryRegionWrapper::IsValid() const {
  return mapping_.IsValid();
}

uint8_t* SharedMemoryRegionWrapper::GetMemory() const {
  DCHECK(IsValid());
  return mapping_.GetMemoryAs<uint8_t>() + offset_;
}

base::span<const uint8_t> SharedMemoryRegionWrapper::GetMemoryAsSpan() const {
  DCHECK(IsValid());
  return mapping_.GetMemoryAsSpan<const uint8_t>().subspan(offset_);
}

size_t SharedMemoryRegionWrapper::GetStride() const {
  DCHECK(IsValid());
  return stride_;
}

}  // namespace gpu
