// 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/dawn_conversions.h"

#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_color_dict.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_extent_3d_dict.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_index_format.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_origin_2d_dict.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_origin_3d_dict.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_programmable_stage.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_texel_copy_buffer_layout.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_texel_copy_texture_info.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_doublesequence_gpucolordict.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_gpuautolayoutmode_gpupipelinelayout.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_gpuextent3ddict_unsignedlongenforcerangesequence.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_gpuorigin2ddict_unsignedlongenforcerangesequence.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_gpuorigin3ddict_unsignedlongenforcerangesequence.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_device.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_pipeline_layout.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_shader_module.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_texture.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"

namespace blink {

bool ConvertToDawn(const V8GPUColor* in,
                   wgpu::Color* out,
                   ExceptionState& exception_state) {
  switch (in->GetContentType()) {
    case V8GPUColor::ContentType::kGPUColorDict: {
      const GPUColorDict* dict = in->GetAsGPUColorDict();
      *out = {dict->r(), dict->g(), dict->b(), dict->a()};
      return true;
    }

    case V8GPUColor::ContentType::kDoubleSequence: {
      const Vector<double>& sequence = in->GetAsDoubleSequence();
      if (sequence.size() != 4) {
        exception_state.ThrowTypeError(
            "A sequence of number used as a GPUColor must have exactly 4 "
            "elements.");
        return false;
      }
      *out = {sequence[0], sequence[1], sequence[2], sequence[3]};
      return true;
    }
  }
}

bool ConvertToDawn(const V8GPUExtent3D* in,
                   wgpu::Extent3D* out,
                   GPUDevice* device,
                   ExceptionState& exception_state) {
  switch (in->GetContentType()) {
    case V8GPUExtent3D::ContentType::kGPUExtent3DDict: {
      const GPUExtent3DDict* dict = in->GetAsGPUExtent3DDict();
      *out = {dict->width(), dict->height(), dict->depthOrArrayLayers()};
      if (dict->hasDepth()) {
        device->AddSingletonWarning(GPUSingletonWarning::kDepthKey);
      }
      return true;
    }

    case V8GPUExtent3D::ContentType::kUnsignedLongEnforceRangeSequence: {
      const Vector<uint32_t>& sequence =
          in->GetAsUnsignedLongEnforceRangeSequence();
      // The WebGPU spec states that height and depthOrArrayLayers default to 1
      // when the sequence isn't big enough.
      switch (sequence.size()) {
        case 1:
          *out = {sequence[0], 1, 1};
          return true;
        case 2:
          *out = {sequence[0], sequence[1], 1};
          return true;
        case 3:
          *out = {sequence[0], sequence[1], sequence[2]};
          return true;
        default:
          exception_state.ThrowTypeError(
              "A sequence of number used as a GPUExtent3D must have between 1 "
              "and 3 elements.");
          return false;
      }
    }
  }
}

bool ConvertToDawn(const V8GPUOrigin3D* in,
                   wgpu::Origin3D* out,
                   ExceptionState& exception_state) {
  switch (in->GetContentType()) {
    case V8GPUOrigin3D::ContentType::kGPUOrigin3DDict: {
      const GPUOrigin3DDict* dict = in->GetAsGPUOrigin3DDict();
      *out = {dict->x(), dict->y(), dict->z()};
      return true;
    }

    case V8GPUOrigin3D::ContentType::kUnsignedLongEnforceRangeSequence: {
      const Vector<uint32_t>& sequence =
          in->GetAsUnsignedLongEnforceRangeSequence();
      // The WebGPU spec states that coordinates default to 0 when the sequence
      // isn't big enough.
      switch (sequence.size()) {
        case 0:
          *out = {0, 0, 0};
          return true;
        case 1:
          *out = {sequence[0], 0, 0};
          return true;
        case 2:
          *out = {sequence[0], sequence[1], 0};
          return true;
        case 3:
          *out = {sequence[0], sequence[1], sequence[2]};
          return true;
        default:
          exception_state.ThrowTypeError(
              "A sequence of number used as a GPUOrigin3D must have at most 3 "
              "elements.");
          return false;
      }
    }
  }
}

bool ConvertToDawn(const V8GPUOrigin2D* in,
                   wgpu::Origin2D* out,
                   ExceptionState& exception_state) {
  switch (in->GetContentType()) {
    case V8GPUOrigin2D::ContentType::kGPUOrigin2DDict: {
      const GPUOrigin2DDict* dict = in->GetAsGPUOrigin2DDict();
      *out = {dict->x(), dict->y()};
      return true;
    }

    case V8GPUOrigin2D::ContentType::kUnsignedLongEnforceRangeSequence: {
      const Vector<uint32_t>& sequence =
          in->GetAsUnsignedLongEnforceRangeSequence();
      // The WebGPU spec states that coordinates default to 0 when the sequence
      // isn't big enough.
      switch (sequence.size()) {
        case 0:
          *out = {0, 0};
          return true;
        case 1:
          *out = {sequence[0], 0};
          return true;
        case 2:
          *out = {sequence[0], sequence[1]};
          return true;
        default:
          exception_state.ThrowTypeError(
              "A sequence of number used as a GPUOrigin2D must have at most 2 "
              "elements.");
          return false;
      }
    }
  }
}

bool ConvertToDawn(const GPUTexelCopyTextureInfo* in,
                   wgpu::TexelCopyTextureInfo* out,
                   ExceptionState& exception_state) {
  DCHECK(in);
  DCHECK(in->texture());

  *out = {
      .texture = in->texture()->GetHandle(),
      .mipLevel = in->mipLevel(),
      .aspect = AsDawnEnum(in->aspect()),
  };
  return ConvertToDawn(in->origin(), &out->origin, exception_state);
}

// Dawn represents `undefined` as the special uint32_t value
// wgpu::kCopyStrideUndefined (0xFFFF'FFFF). Blink must make sure that an
// actual value of 0xFFFF'FFFF coming in from JS is not treated as
// wgpu::kCopyStrideUndefined, so it injects an error in that case.
const char* ValidateTexelCopyBufferLayout(
    const GPUTexelCopyBufferLayout* webgpu_layout,
    wgpu::TexelCopyBufferLayout* dawn_layout) {
  DCHECK(webgpu_layout);

  uint32_t bytesPerRow = 0;
  if (webgpu_layout->hasBytesPerRow()) {
    bytesPerRow = webgpu_layout->bytesPerRow();
    if (bytesPerRow == wgpu::kCopyStrideUndefined) {
      return "bytesPerRow must be a multiple of 256";
    }
  } else {
    bytesPerRow = wgpu::kCopyStrideUndefined;
  }

  uint32_t rowsPerImage = 0;
  if (webgpu_layout->hasRowsPerImage()) {
    rowsPerImage = webgpu_layout->rowsPerImage();
    if (rowsPerImage == wgpu::kCopyStrideUndefined) {
      return "rowsPerImage is too large";
    }
  } else {
    rowsPerImage = wgpu::kCopyStrideUndefined;
  }

  *dawn_layout = {
      .offset = webgpu_layout->offset(),
      .bytesPerRow = bytesPerRow,
      .rowsPerImage = rowsPerImage,
  };
  return nullptr;
}

wgpu::PipelineLayout AsDawnType(
    V8UnionGPUAutoLayoutModeOrGPUPipelineLayout* webgpu_layout) {
  DCHECK(webgpu_layout);

  switch (webgpu_layout->GetContentType()) {
    case V8UnionGPUAutoLayoutModeOrGPUPipelineLayout::ContentType::
        kGPUPipelineLayout:
      return AsDawnType(webgpu_layout->GetAsGPUPipelineLayout());
    case V8UnionGPUAutoLayoutModeOrGPUPipelineLayout::ContentType::
        kGPUAutoLayoutMode:
      return nullptr;
  }

  NOTREACHED();
}

}  // namespace blink
