// 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 "third_party/blink/renderer/modules/webgpu/gpu_texture.h"

#include "base/containers/heap_array.h"
#include "gpu/command_buffer/client/webgpu_interface.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_feature_name.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_texture_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_texture_view_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_gputextureviewdimension_undefined.h"
#include "third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h"
#include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
#include "third_party/blink/renderer/modules/webgpu/dawn_conversions.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_device.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_supported_features.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_texture_usage.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_texture_view.h"
#include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h"
#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_shared_image_wrapper_cache.h"

namespace blink {

namespace {

bool ConvertToDawn(
    const GPUTextureDescriptor* in,
    wgpu::TextureDescriptor* out,
    wgpu::TextureBindingViewDimension* out_texture_binding_view_dimension,
    std::string* label,
    base::HeapArray<wgpu::TextureFormat>* view_formats,
    GPUDevice* device,
    ExceptionState& exception_state) {
  DCHECK(in);
  DCHECK(out);
  DCHECK(out_texture_binding_view_dimension);
  DCHECK(label);
  DCHECK(view_formats);
  DCHECK(device);

  *out = {};
  out->usage = static_cast<wgpu::TextureUsage>(in->usage());
  out->dimension = AsDawnEnum(in->dimension());
  out->format = AsDawnEnum(in->format());
  out->mipLevelCount = in->mipLevelCount();
  out->sampleCount = in->sampleCount();

  if (in->hasTextureBindingViewDimension()) {
    wgpu::TextureViewDimension texture_binding_view_dimension =
        AsDawnEnum(in->textureBindingViewDimension());
    if (texture_binding_view_dimension !=
        wgpu::TextureViewDimension::Undefined) {
      *out_texture_binding_view_dimension = {};
      out_texture_binding_view_dimension->textureBindingViewDimension =
          texture_binding_view_dimension;
      out->nextInChain = out_texture_binding_view_dimension;
    }
  }

  *label = in->label().Utf8();
  if (!label->empty()) {
    out->label = label->c_str();
  }

  *view_formats = AsDawnEnum<wgpu::TextureFormat>(in->viewFormats());
  out->viewFormatCount = in->viewFormats().size();
  out->viewFormats = view_formats->data();

  return ConvertToDawn(in->size(), &out->size, device, exception_state);
}

void ConvertToDawnType(const GPUTextureViewDescriptor* webgpu_desc,
                       OwnedTextureViewDescriptor* dawn_desc_info) {
  DCHECK(webgpu_desc);
  DCHECK(dawn_desc_info);

  if (webgpu_desc->hasFormat()) {
    dawn_desc_info->dawn_desc.format = AsDawnEnum(webgpu_desc->format());
  }
  if (webgpu_desc->hasDimension()) {
    dawn_desc_info->dawn_desc.dimension = AsDawnEnum(webgpu_desc->dimension());
  }
  dawn_desc_info->dawn_desc.baseMipLevel = webgpu_desc->baseMipLevel();
  if (webgpu_desc->hasMipLevelCount()) {
    dawn_desc_info->dawn_desc.mipLevelCount = webgpu_desc->mipLevelCount();
  }
  dawn_desc_info->dawn_desc.baseArrayLayer = webgpu_desc->baseArrayLayer();
  if (webgpu_desc->hasArrayLayerCount()) {
    dawn_desc_info->dawn_desc.arrayLayerCount = webgpu_desc->arrayLayerCount();
  }
  dawn_desc_info->dawn_desc.aspect = AsDawnEnum(webgpu_desc->aspect());
  if (!webgpu_desc->label().empty()) {
    dawn_desc_info->label = webgpu_desc->label().Utf8();
    dawn_desc_info->dawn_desc.label = dawn_desc_info->label.c_str();
  }
  if (webgpu_desc->hasUsage()) {
    dawn_desc_info->dawn_desc.usage =
        static_cast<wgpu::TextureUsage>(webgpu_desc->usage());
  }
  const auto& swizzle = webgpu_desc->swizzle();
  // Only pass the swizzle descriptor to Dawn if swizzle is non-default because
  // the C API will produce validation errors if a chained struct is passed
  // without its feature being enabled.
  if (swizzle != "rgba") {
    dawn_desc_info->swizzle_desc =
        std::make_unique<wgpu::TextureComponentSwizzleDescriptor>();
    dawn_desc_info->swizzle_desc->swizzle.r = AsDawnEnum(swizzle[0]);
    dawn_desc_info->swizzle_desc->swizzle.g = AsDawnEnum(swizzle[1]);
    dawn_desc_info->swizzle_desc->swizzle.b = AsDawnEnum(swizzle[2]);
    dawn_desc_info->swizzle_desc->swizzle.a = AsDawnEnum(swizzle[3]);
    dawn_desc_info->dawn_desc.nextInChain = dawn_desc_info->swizzle_desc.get();
  }
}

// Validate swizzle must be a four-character string that only includes "r", "g",
// "b", "a", "0", or "1".
bool ValidateSwizzle(const String& swizzle, ExceptionState& exception_state) {
  if (swizzle.length() != 4) {
    exception_state.ThrowTypeError(
        StrCat({"Swizzle ('", swizzle,
                "') must be exactly a four-character string."}));
    return false;
  }

  if (AsDawnEnum(swizzle[0]) == wgpu::ComponentSwizzle::Undefined ||
      AsDawnEnum(swizzle[1]) == wgpu::ComponentSwizzle::Undefined ||
      AsDawnEnum(swizzle[2]) == wgpu::ComponentSwizzle::Undefined ||
      AsDawnEnum(swizzle[3]) == wgpu::ComponentSwizzle::Undefined) {
    exception_state.ThrowTypeError(
        StrCat({"Swizzle ('", swizzle,
                "') must contain only 'r', 'g', 'b', 'a', '0', "
                "or '1' characters."}));
    return false;
  }

  return true;
}

// Dawn represents `undefined` as the special uint32_t value (0xFFFF'FFFF).
// Blink must make sure that an actual value of 0xFFFF'FFFF coming in from JS
// is not treated as the special `undefined` value, so it injects an error in
// that case.
std::string ValidateTextureMipLevelAndArrayLayerCounts(
    const GPUTextureViewDescriptor* webgpu_desc) {
  DCHECK(webgpu_desc);

  if (webgpu_desc->hasMipLevelCount() &&
      webgpu_desc->mipLevelCount() == wgpu::kMipLevelCountUndefined) {
    std::ostringstream error;
    error << "mipLevelCount (" << webgpu_desc->mipLevelCount()
          << ") is too large when validating [GPUTextureViewDescriptor";
    if (!webgpu_desc->label().empty()) {
      error << " '" << webgpu_desc->label().Utf8() << "'";
    }
    error << "].";
    return error.str();
  }

  if (webgpu_desc->hasArrayLayerCount() &&
      webgpu_desc->arrayLayerCount() == wgpu::kArrayLayerCountUndefined) {
    std::ostringstream error;
    error << "arrayLayerCount (" << webgpu_desc->arrayLayerCount()
          << ") is too large when validating [GPUTextureViewDescriptor";
    if (!webgpu_desc->label().empty()) {
      error << " '" << webgpu_desc->label().Utf8() << "'";
    }
    error << "].";
    return error.str();
  }

  return std::string();
}

}  // anonymous namespace

// static
GPUTexture* GPUTexture::Create(GPUDevice* device,
                               const GPUTextureDescriptor* webgpu_desc,
                               ExceptionState& exception_state) {
  DCHECK(device);
  DCHECK(webgpu_desc);

  wgpu::TextureDescriptor dawn_desc;
  wgpu::TextureBindingViewDimension texture_binding_view_dimension_desc;

  std::string label;
  base::HeapArray<wgpu::TextureFormat> view_formats;
  if (!ConvertToDawn(webgpu_desc, &dawn_desc,
                     &texture_binding_view_dimension_desc, &label,
                     &view_formats, device, exception_state)) {
    return nullptr;
  }

  if (!device->ValidateTextureFormatUsage(webgpu_desc->format(),
                                          exception_state)) {
    return nullptr;
  }

  for (auto view_format : webgpu_desc->viewFormats()) {
    if (!device->ValidateTextureFormatUsage(view_format, exception_state)) {
      return nullptr;
    }
  }

  GPUTexture* texture = MakeGarbageCollected<GPUTexture>(
      device, device->GetHandle().CreateTexture(&dawn_desc),
      webgpu_desc->label());
  return texture;
}

GPUTexture* GPUTexture::Create(GPUDevice* device,
                               const wgpu::TextureDescriptor* desc) {
  DCHECK(device);
  DCHECK(desc);

  return MakeGarbageCollected<GPUTexture>(
      device, device->GetHandle().CreateTexture(desc),
      String::FromUtf8(desc->label));
}

// static
GPUTexture* GPUTexture::CreateError(GPUDevice* device,
                                    const wgpu::TextureDescriptor* desc) {
  DCHECK(device);
  DCHECK(desc);
  return MakeGarbageCollected<GPUTexture>(
      device, device->GetHandle().CreateErrorTexture(desc),
      String::FromUtf8(desc->label));
}

GPUTexture::GPUTexture(GPUDevice* device,
                       wgpu::Texture texture,
                       const String& label)
    : DawnObject<wgpu::Texture>(device, std::move(texture), label),
      dimension_(GetHandle().GetDimension()),
      texture_binding_view_dimension_(
          GetHandle().GetTextureBindingViewDimension()),
      format_(GetHandle().GetFormat()),
      usage_(GetHandle().GetUsage()) {}

GPUTexture::GPUTexture(GPUDevice* device,
                       wgpu::TextureFormat format,
                       wgpu::TextureUsage usage,
                       scoped_refptr<WebGPUMailboxTexture> mailbox_texture,
                       const String& label)
    : DawnObject<wgpu::Texture>(device, mailbox_texture->GetTexture(), label),
      format_(format),
      usage_(usage),
      mailbox_texture_(std::move(mailbox_texture)) {
  if (mailbox_texture_) {
    device_->TrackTextureWithMailbox(this);
  }

  // Mailbox textures are all 2d texture.
  dimension_ = wgpu::TextureDimension::e2D;
  texture_binding_view_dimension_ =
      device->features()->Has(V8GPUFeatureName::Enum::kCoreFeaturesAndLimits)
          ? wgpu::TextureViewDimension::Undefined
          : wgpu::TextureViewDimension::e2D;
}

GPUTextureView* GPUTexture::createView(
    const GPUTextureViewDescriptor* webgpu_desc,
    ExceptionState& exception_state) {
  DCHECK(webgpu_desc);

  if (webgpu_desc->hasFormat() && !device()->ValidateTextureFormatUsage(
                                      webgpu_desc->format(), exception_state)) {
    return nullptr;
  }

  if (!ValidateSwizzle(webgpu_desc->swizzle(), exception_state)) {
    return nullptr;
  }

  std::string error = ValidateTextureMipLevelAndArrayLayerCounts(webgpu_desc);
  if (!error.empty()) {
    device()->InjectError(wgpu::ErrorType::Validation, error.c_str());
    return MakeGarbageCollected<GPUTextureView>(
        device(), GetHandle().CreateErrorView(nullptr), String());
  }

  OwnedTextureViewDescriptor dawn_desc_info;
  ConvertToDawnType(webgpu_desc, &dawn_desc_info);
  GPUTextureView* view = MakeGarbageCollected<GPUTextureView>(
      device_, GetHandle().CreateView(&dawn_desc_info.dawn_desc),
      webgpu_desc->label());
  return view;
}

GPUTexture::~GPUTexture() {
  DissociateMailbox();
}

void GPUTexture::destroy() {
  if (destroyed_) {
    return;
  }

  if (destroy_callback_) {
    std::move(destroy_callback_).Run();
  }

  if (mailbox_texture_) {
    DissociateMailbox();
    device_->UntrackTextureWithMailbox(this);
  }
  GetHandle().Destroy();
  destroyed_ = true;
}

uint32_t GPUTexture::width() const {
  return GetHandle().GetWidth();
}

uint32_t GPUTexture::height() const {
  return GetHandle().GetHeight();
}

uint32_t GPUTexture::depthOrArrayLayers() const {
  return GetHandle().GetDepthOrArrayLayers();
}

uint32_t GPUTexture::mipLevelCount() const {
  return GetHandle().GetMipLevelCount();
}

uint32_t GPUTexture::sampleCount() const {
  return GetHandle().GetSampleCount();
}

V8GPUTextureDimension GPUTexture::dimension() const {
  return FromDawnEnum(GetHandle().GetDimension());
}

V8UnionGPUTextureViewDimensionOrUndefined::Ret
GPUTexture::textureBindingViewDimension(ScriptState* script_state) const {
  wgpu::TextureViewDimension viewDimension =
      GetHandle().GetTextureBindingViewDimension();
  if (viewDimension == wgpu::TextureViewDimension::Undefined) {
    return V8UnionGPUTextureViewDimensionOrUndefined::Ret(
        script_state, ToV8UndefinedGenerator());
  } else {
    return V8UnionGPUTextureViewDimensionOrUndefined::Ret(
        script_state, FromDawnEnum(viewDimension));
  }
}

V8GPUTextureFormat GPUTexture::format() const {
  return FromDawnEnum(GetHandle().GetFormat());
}

uint32_t GPUTexture::usage() const {
  return static_cast<uint32_t>(GetHandle().GetUsage());
}

gpu::SyncToken GPUTexture::DissociateMailbox() {
  gpu::SyncToken sync_token;
  if (mailbox_texture_) {
    sync_token = mailbox_texture_->Dissociate();
    mailbox_texture_ = nullptr;
  }
  return sync_token;
}

scoped_refptr<WebGPUMailboxTexture> GPUTexture::GetMailboxTexture() {
  return mailbox_texture_;
}

void GPUTexture::SetBeforeDestroyCallback(base::OnceClosure callback) {
  destroy_callback_ = std::move(callback);
}

void GPUTexture::ClearBeforeDestroyCallback() {
  destroy_callback_.Reset();
}

}  // namespace blink
