// Copyright 2023 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.

#include "vpx_decoder_delegate.h"

#include "fake_buffer.h"
#include "fake_surface.h"
#include "libyuv.h"
#include "vpx/vp8dx.h"
#include "vpx/vpx_decoder.h"
#include "vpx/vpx_image.h"

namespace libvafake {

VpxDecoderDelegate::VpxDecoderDelegate(int picture_width_hint,
                                       int picture_height_hint,
                                       VAProfile profile) {
  vpx_codec_dec_cfg_t vpx_config = {0};
  vpx_config.w = static_cast<unsigned int>(picture_width_hint);
  vpx_config.h = static_cast<unsigned int>(picture_height_hint);

  if (vpx_config.w >= 3840) {
    vpx_config.threads = 16;
  } else if (vpx_config.w >= 2560) {
    vpx_config.threads = 8;
  } else {
    vpx_config.threads = 4;
  }

  vpx_codec_ = std::make_unique<vpx_codec_ctx>();
  CHECK(profile == VAProfileVP8Version0_3 || profile == VAProfileVP9Profile0);
  const vpx_codec_err_t status = vpx_codec_dec_init(
      vpx_codec_.get(),
      (profile == VAProfileVP8Version0_3) ? vpx_codec_vp8_dx()
                                          : vpx_codec_vp9_dx(),
      &vpx_config, /*flags=*/0);

  CHECK_EQ(status, VPX_CODEC_OK);
}

VpxDecoderDelegate::~VpxDecoderDelegate() {
  vpx_codec_destroy(vpx_codec_.get());
}

void VpxDecoderDelegate::SetRenderTarget(const FakeSurface& surface) {
  render_target_ = &surface;
}

void VpxDecoderDelegate::EnqueueWork(
    const std::vector<const FakeBuffer*>& buffers) {
  CHECK(render_target_);
  CHECK(!encoded_data_buffer_);
  for (auto buffer : buffers) {
    if (buffer->GetType() == VASliceDataBufferType) {
      encoded_data_buffer_ = buffer;
      return;
    }
  }
}

void VpxDecoderDelegate::Run() {
  CHECK(render_target_);
  CHECK(encoded_data_buffer_);

  const vpx_codec_err_t status = vpx_codec_decode(
      vpx_codec_.get(), static_cast<uint8_t*>(encoded_data_buffer_->GetData()),
      static_cast<unsigned int>(encoded_data_buffer_->GetDataSize()),
      /*user_priv=*/nullptr,
      /*deadline=*/0);
  CHECK_EQ(status, VPX_CODEC_OK);

  vpx_codec_iter_t iter = nullptr;
  const vpx_image_t* vpx_image = vpx_codec_get_frame(vpx_codec_.get(), &iter);
  // The user of libva should ensure that at most one frame is available for
  // output for each vaBeginPicture()/vaRenderPicture()/vaEndPicture() cycle.
  CHECK(!vpx_codec_get_frame(vpx_codec_.get(), &iter));

  encoded_data_buffer_ = nullptr;

  // No show reference only frame.
  if (!vpx_image) {
    return;
  }

  // We currently only support reading from I420 and into NV12.
  CHECK_EQ(vpx_image->fmt, VPX_IMG_FMT_I420);
  CHECK_EQ(render_target_->GetVAFourCC(),
           static_cast<uint32_t>(VA_FOURCC_NV12));

  const ScopedBOMapping& bo_mapping = render_target_->GetMappedBO();
  CHECK(bo_mapping.IsValid());
  const ScopedBOMapping::ScopedAccess mapped_bo = bo_mapping.BeginAccess();

  const int convert_result = libyuv::I420ToNV12(
      /*src_y=*/vpx_image->planes[VPX_PLANE_Y],
      /*src_stride_y=*/vpx_image->stride[VPX_PLANE_Y],
      /*src_u=*/vpx_image->planes[VPX_PLANE_U],
      /*src_stride_u=*/vpx_image->stride[VPX_PLANE_U],
      /*src_v=*/vpx_image->planes[VPX_PLANE_V],
      /*src_stride_v=*/vpx_image->stride[VPX_PLANE_V],
      /*dst_y=*/mapped_bo.GetData(0),
      /*dst_stride_y=*/static_cast<int>(mapped_bo.GetStride(0)),
      /*dst_uv=*/mapped_bo.GetData(1),
      /*dst_stride_uv=*/static_cast<int>(mapped_bo.GetStride(1)),
      /*width=*/static_cast<int>(vpx_image->d_w),
      /*height=*/static_cast<int>(vpx_image->d_h));
  CHECK_EQ(convert_result, 0);
}

}  // namespace libvafake