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

#include "media/gpu/chromeos/mappable_si_video_frame_mapper.h"

#include <sys/mman.h>

#include <array>

#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "media/gpu/macros.h"

namespace media {
// static
std::unique_ptr<MappableSIVideoFrameMapper> MappableSIVideoFrameMapper::Create(
    VideoPixelFormat format) {
  return base::WrapUnique(new MappableSIVideoFrameMapper(format));
}

MappableSIVideoFrameMapper::MappableSIVideoFrameMapper(VideoPixelFormat format)
    : VideoFrameMapper(format) {}

scoped_refptr<VideoFrame> MappableSIVideoFrameMapper::MapFrame(
    scoped_refptr<const FrameResource> video_frame,
    int permissions) {
  if (!video_frame) {
    LOG(ERROR) << "Video frame is nullptr";
    return nullptr;
  }

  if (!(permissions & PROT_READ && permissions & PROT_WRITE)) {
    LOG(ERROR) << "GPU Memory Buffer must be mapped read/write.";
    return nullptr;
  }

  if (!video_frame->HasMappableSharedImage()) {
    VLOGF(1) << "VideoFrame is not holding a mappable SharedImage";
    return nullptr;
  }

  if (video_frame->format() != format_) {
    VLOGF(1) << "Unexpected format: " << video_frame->format()
             << ", expected: " << format_;
    return nullptr;
  }

  auto scoped_mapping = video_frame->GetSharedImage()->Map();
  if (!scoped_mapping) {
    VLOGF(1) << "Failed to get the mapped memory.";
    return nullptr;
  }

  const size_t num_planes = VideoFrame::NumPlanes(format_);
  std::array<base::span<uint8_t>, VideoFrame::kMaxPlanes> planes = {};

  for (size_t i = 0; i < num_planes; i++) {
    planes[i] = scoped_mapping->GetMemoryForPlane(i);
  }

  scoped_refptr<VideoFrame> mapped_frame;
  if (IsYuvPlanar(format_)) {
    mapped_frame = VideoFrame::WrapExternalYuvDataWithLayout(
        video_frame->layout(), video_frame->visible_rect(),
        video_frame->natural_size(), planes[0], planes[1], planes[2],
        video_frame->timestamp());
  } else if (num_planes == 1) {
    mapped_frame = VideoFrame::WrapExternalDataWithLayout(
        video_frame->layout(), video_frame->visible_rect(),
        video_frame->natural_size(), planes[0], video_frame->timestamp());
  }

  if (!mapped_frame) {
    return nullptr;
  }

  mapped_frame->set_color_space(video_frame->ColorSpace());
  mapped_frame->metadata().MergeMetadataFrom(video_frame->metadata());

  // Pass |video_frame| so that it outlives |mapped_frame| and the mapped buffer
  // is unmapped on destruction.
  mapped_frame->AddDestructionObserver(base::BindOnce(
      [](scoped_refptr<const FrameResource> frame,
         std::unique_ptr<gpu::ClientSharedImage::ScopedMapping>
             scoped_mapping) {
        CHECK(scoped_mapping);
        // The ScopedMapping must be destroyed before the FrameResource that
        // produced it in order to avoid dangling pointers.
        scoped_mapping.reset();
      },
      std::move(video_frame), std::move(scoped_mapping)));
  return mapped_frame;
}
}  // namespace media
