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

#include "base/compiler_specific.h"
#include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_blend_component.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_blend_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_color_state_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_color_target_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_depth_stencil_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_fragment_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_multisample_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_primitive_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_rasterization_state_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_render_pipeline_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_stencil_face_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_vertex_attribute.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_vertex_buffer_layout.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_vertex_state.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_bind_group_layout.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/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"

namespace blink {

namespace {

const char kGPUBlendComponentPartiallySpecifiedMessage[] =
    "fragment.targets[%u].blend.%s has a mix of explicit and defaulted "
    "members, which is unusual. Did you mean to specify other members?";

wgpu::BlendComponent AsDawnType(const GPUBlendComponent* webgpu_desc) {
  DCHECK(webgpu_desc);

  wgpu::BlendComponent dawn_desc = {
      .operation = AsDawnEnum(webgpu_desc->getOperationOr(
          V8GPUBlendOperation(V8GPUBlendOperation::Enum::kAdd))),
      .srcFactor = AsDawnEnum(webgpu_desc->getSrcFactorOr(
          V8GPUBlendFactor(V8GPUBlendFactor::Enum::kOne))),
      .dstFactor = AsDawnEnum(webgpu_desc->getDstFactorOr(
          V8GPUBlendFactor(V8GPUBlendFactor::Enum::kZero))),
  };
  return dawn_desc;
}

wgpu::BlendState AsDawnType(const GPUBlendState* webgpu_desc) {
  DCHECK(webgpu_desc);

  wgpu::BlendState dawn_desc = {
      .color = AsDawnType(webgpu_desc->color()),
      .alpha = AsDawnType(webgpu_desc->alpha()),
  };

  return dawn_desc;
}

bool ValidateBlendComponent(GPUDevice* device,
                            const GPUBlendComponent* webgpu_desc,
                            ExceptionState& exception_state) {
  DCHECK(webgpu_desc);

  return device->ValidateBlendFactor(
             webgpu_desc->getSrcFactorOr(
                 V8GPUBlendFactor(V8GPUBlendFactor::Enum::kOne)),
             exception_state) &&
         device->ValidateBlendFactor(
             webgpu_desc->getDstFactorOr(
                 V8GPUBlendFactor(V8GPUBlendFactor::Enum::kZero)),
             exception_state);
}

}  // anonymous namespace

wgpu::ColorTargetState AsDawnType(const GPUColorTargetState* webgpu_desc) {
  DCHECK(webgpu_desc);

  wgpu::ColorTargetState dawn_desc = {
      // .blend is handled in ConvertToDawnType
      .format = AsDawnEnum(webgpu_desc->format()),
      .writeMask = AsDawnFlags<wgpu::ColorWriteMask>(webgpu_desc->writeMask()),
  };
  return dawn_desc;
}

wgpu::VertexBufferLayout AsDawnType(const GPUVertexBufferLayout* webgpu_desc) {
  DCHECK(webgpu_desc);

  wgpu::VertexBufferLayout dawn_desc = {
      .stepMode = AsDawnEnum(webgpu_desc->stepMode()),
      .arrayStride = webgpu_desc->arrayStride(),
      .attributeCount = webgpu_desc->attributes().size(),
      // .attributes is handled outside separately
  };

  return dawn_desc;
}

wgpu::VertexAttribute AsDawnType(const GPUVertexAttribute* webgpu_desc) {
  DCHECK(webgpu_desc);

  wgpu::VertexAttribute dawn_desc = {
      .format = AsDawnEnum(webgpu_desc->format()),
      .offset = webgpu_desc->offset(),
      .shaderLocation = webgpu_desc->shaderLocation(),
  };

  return dawn_desc;
}

namespace {

wgpu::StencilFaceState AsDawnType(const GPUStencilFaceState* webgpu_desc) {
  DCHECK(webgpu_desc);

  wgpu::StencilFaceState dawn_desc = {
      .compare = AsDawnEnum(webgpu_desc->compare()),
      .failOp = AsDawnEnum(webgpu_desc->failOp()),
      .depthFailOp = AsDawnEnum(webgpu_desc->depthFailOp()),
      .passOp = AsDawnEnum(webgpu_desc->passOp()),
  };

  return dawn_desc;
}

wgpu::PrimitiveState AsDawnType(const GPUPrimitiveState* webgpu_desc) {
  DCHECK(webgpu_desc);

  wgpu::PrimitiveState dawn_desc = {};
  dawn_desc.topology = AsDawnEnum(webgpu_desc->topology());

  if (webgpu_desc->hasStripIndexFormat()) {
    dawn_desc.stripIndexFormat = AsDawnEnum(webgpu_desc->stripIndexFormat());
  }

  dawn_desc.frontFace = AsDawnEnum(webgpu_desc->frontFace());
  dawn_desc.cullMode = AsDawnEnum(webgpu_desc->cullMode());
  dawn_desc.unclippedDepth = webgpu_desc->unclippedDepth();

  return dawn_desc;
}

wgpu::DepthStencilState AsDawnType(GPUDevice* device,
                                   const GPUDepthStencilState* webgpu_desc,
                                   ExceptionState& exception_state) {
  DCHECK(webgpu_desc);

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

  wgpu::DepthStencilState dawn_desc = {};
  dawn_desc.format = AsDawnEnum(webgpu_desc->format());

  if (webgpu_desc->hasDepthWriteEnabled()) {
    dawn_desc.depthWriteEnabled = webgpu_desc->depthWriteEnabled()
                                      ? wgpu::OptionalBool::True
                                      : wgpu::OptionalBool::False;
  }

  if (webgpu_desc->hasDepthCompare()) {
    dawn_desc.depthCompare = AsDawnEnum(webgpu_desc->depthCompare());
  }

  dawn_desc.stencilFront = AsDawnType(webgpu_desc->stencilFront());
  dawn_desc.stencilBack = AsDawnType(webgpu_desc->stencilBack());
  dawn_desc.stencilReadMask = webgpu_desc->stencilReadMask();
  dawn_desc.stencilWriteMask = webgpu_desc->stencilWriteMask();
  dawn_desc.depthBias = webgpu_desc->depthBias();
  dawn_desc.depthBiasSlopeScale = webgpu_desc->depthBiasSlopeScale();
  dawn_desc.depthBiasClamp = webgpu_desc->depthBiasClamp();

  return dawn_desc;
}

wgpu::MultisampleState AsDawnType(const GPUMultisampleState* webgpu_desc) {
  DCHECK(webgpu_desc);

  wgpu::MultisampleState dawn_desc = {
      .count = webgpu_desc->count(),
      .mask = webgpu_desc->mask(),
      .alphaToCoverageEnabled = webgpu_desc->alphaToCoverageEnabled(),
  };

  return dawn_desc;
}

// TODO(crbug.com/351564777): should be UNSAFE_BUFFER_USAGE
// or avoid UNSAFE entirely (maybe possible using HeapArray)
void AsDawnVertexBufferLayouts(GPUDevice* device,
                               const GPUVertexState* descriptor,
                               OwnedVertexState* dawn_desc_info) {
  DCHECK(descriptor);
  DCHECK(dawn_desc_info);

  wgpu::VertexState* dawn_vertex = dawn_desc_info->dawn_desc;
  dawn_vertex->bufferCount = descriptor->buffers().size();

  if (dawn_vertex->bufferCount == 0) {
    dawn_vertex->buffers = nullptr;
    return;
  }

  // TODO(cwallez@chromium.org): Should we validate the Length() first so we
  // don't risk creating HUGE vectors of wgpu::VertexBufferLayout from
  // the sparse array?
  dawn_desc_info->buffers = AsDawnType(descriptor->buffers());
  dawn_vertex->buffers = dawn_desc_info->buffers.get();

  // Handle wgpu::VertexBufferLayout::attributes separately to guarantee the
  // lifetime.
  dawn_desc_info->attributes =
      std::make_unique<std::unique_ptr<wgpu::VertexAttribute[]>[]>(
          dawn_vertex->bufferCount);
  for (wtf_size_t i = 0; i < dawn_vertex->bufferCount; ++i) {
    if (const auto* buffer = descriptor->buffers()[i].Get()) {
      UNSAFE_TODO(dawn_desc_info->attributes.get()[i]) =
          AsDawnType(buffer->attributes());
      wgpu::VertexBufferLayout* dawn_buffer =
          UNSAFE_TODO(&dawn_desc_info->buffers[i]);
      dawn_buffer->attributes =
          UNSAFE_TODO(dawn_desc_info->attributes.get()[i].get());
    }
  }
}

void GPUVertexStateAsWGPUVertexState(GPUDevice* device,
                                     const GPUVertexState* descriptor,
                                     OwnedVertexState* dawn_vertex) {
  DCHECK(descriptor);
  DCHECK(dawn_vertex);

  *dawn_vertex->dawn_desc = {};

  GPUProgrammableStageAsWGPUProgrammableStage(descriptor, dawn_vertex);
  dawn_vertex->dawn_desc->constantCount = dawn_vertex->constantCount;
  dawn_vertex->dawn_desc->constants = dawn_vertex->constants.get();
  dawn_vertex->dawn_desc->module = descriptor->module()->GetHandle();
  dawn_vertex->dawn_desc->entryPoint =
      dawn_vertex->entry_point ? dawn_vertex->entry_point->c_str() : nullptr;

  if (descriptor->hasBuffers()) {
    AsDawnVertexBufferLayouts(device, descriptor, dawn_vertex);
  }
}

bool IsGPUBlendComponentPartiallySpecified(
    const GPUBlendComponent* webgpu_desc) {
  DCHECK(webgpu_desc);
  // GPUBlendComponent is considered partially specified when:
  // - srcFactor is missing but operation or dstFactor is provided
  // - dstFactor is missing but operation or srcFactor is provided
  return ((!webgpu_desc->hasSrcFactor() &&
           (webgpu_desc->hasDstFactor() || webgpu_desc->hasOperation())) ||
          (!webgpu_desc->hasDstFactor() &&
           (webgpu_desc->hasSrcFactor() || webgpu_desc->hasOperation())));
}

void GPUFragmentStateAsWGPUFragmentState(GPUDevice* device,
                                         const GPUFragmentState* descriptor,
                                         OwnedFragmentState* dawn_fragment,
                                         ExceptionState& exception_state) {
  DCHECK(descriptor);
  DCHECK(dawn_fragment);

  dawn_fragment->dawn_desc = {};

  GPUProgrammableStageAsWGPUProgrammableStage(descriptor, dawn_fragment);
  dawn_fragment->dawn_desc.constantCount = dawn_fragment->constantCount;
  dawn_fragment->dawn_desc.constants = dawn_fragment->constants.get();
  dawn_fragment->dawn_desc.module = descriptor->module()->GetHandle();
  dawn_fragment->dawn_desc.entryPoint =
      dawn_fragment->entry_point ? dawn_fragment->entry_point->c_str()
                                 : nullptr;

  dawn_fragment->dawn_desc.targets = nullptr;
  dawn_fragment->dawn_desc.targetCount = descriptor->targets().size();
  if (dawn_fragment->dawn_desc.targetCount > 0) {
    dawn_fragment->targets = AsDawnType(descriptor->targets());
    dawn_fragment->dawn_desc.targets = dawn_fragment->targets.get();
  }

  // In order to maintain proper ownership we have to process the blend states
  // for each target outside of AsDawnType().
  // ReserveCapacity beforehand to make sure our pointers within the vector
  // stay stable.
  dawn_fragment->blend_states.resize(descriptor->targets().size());
  for (wtf_size_t i = 0; i < descriptor->targets().size(); ++i) {
    const auto& maybe_color_target = descriptor->targets()[i];
    if (!maybe_color_target) {
      continue;
    }
    const GPUColorTargetState* color_target = maybe_color_target.Get();
    if (!device->ValidateTextureFormatUsage(color_target->format(),
                                            exception_state)) {
      return;
    }
    if (color_target->hasBlend()) {
      const GPUBlendState* blend_state = color_target->blend();
      if (IsGPUBlendComponentPartiallySpecified(blend_state->color())) {
        device->AddConsoleWarning(UNSAFE_TODO(String::Format(
            kGPUBlendComponentPartiallySpecifiedMessage, i, "color")));
      }
      if (IsGPUBlendComponentPartiallySpecified(blend_state->alpha())) {
        device->AddConsoleWarning(UNSAFE_TODO(String::Format(
            kGPUBlendComponentPartiallySpecifiedMessage, i, "alpha")));
      }

      if (!ValidateBlendComponent(device, blend_state->color(),
                                  exception_state) ||
          !ValidateBlendComponent(device, blend_state->alpha(),
                                  exception_state)) {
        return;
      }

      dawn_fragment->blend_states[i] = AsDawnType(blend_state);
      UNSAFE_TODO(dawn_fragment->targets[i].blend) =
          &dawn_fragment->blend_states[i];
    }
  }
}

}  // anonymous namespace

void ConvertToDawnType(v8::Isolate* isolate,
                       GPUDevice* device,
                       const GPURenderPipelineDescriptor* webgpu_desc,
                       OwnedRenderPipelineDescriptor* dawn_desc_info,
                       ExceptionState& exception_state) {
  DCHECK(isolate);
  DCHECK(webgpu_desc);
  DCHECK(dawn_desc_info);

  // Label
  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();
  }

  // Layout
  dawn_desc_info->dawn_desc.layout = AsDawnType(webgpu_desc->layout());

  // Vertex
  const GPUVertexState* vertex = webgpu_desc->vertex();
  OwnedVertexState* dawn_vertex = &dawn_desc_info->vertex;
  dawn_vertex->dawn_desc = &dawn_desc_info->dawn_desc.vertex;
  GPUVertexStateAsWGPUVertexState(device, vertex, dawn_vertex);

  // Primitive
  dawn_desc_info->dawn_desc.primitive = AsDawnType(webgpu_desc->primitive());

  // DepthStencil
  if (webgpu_desc->hasDepthStencil()) {
    dawn_desc_info->depth_stencil =
        AsDawnType(device, webgpu_desc->depthStencil(), exception_state);
    dawn_desc_info->dawn_desc.depthStencil = &dawn_desc_info->depth_stencil;
  }

  // Multisample
  dawn_desc_info->dawn_desc.multisample =
      AsDawnType(webgpu_desc->multisample());

  // Fragment
  if (webgpu_desc->hasFragment()) {
    GPUFragmentStateAsWGPUFragmentState(device, webgpu_desc->fragment(),
                                        &dawn_desc_info->fragment,
                                        exception_state);
    dawn_desc_info->dawn_desc.fragment = &dawn_desc_info->fragment.dawn_desc;
  }
}

// static
GPURenderPipeline* GPURenderPipeline::Create(
    ScriptState* script_state,
    GPUDevice* device,
    const GPURenderPipelineDescriptor* webgpu_desc) {
  DCHECK(device);
  DCHECK(webgpu_desc);

  v8::Isolate* isolate = script_state->GetIsolate();
  GPURenderPipeline* pipeline;
  OwnedRenderPipelineDescriptor dawn_desc_info;
  ConvertToDawnType(isolate, device, webgpu_desc, &dawn_desc_info,
                    PassThroughException(isolate));

  if (isolate->HasPendingException()) {
    return nullptr;
  }

  pipeline = MakeGarbageCollected<GPURenderPipeline>(
      device,
      device->GetHandle().CreateRenderPipeline(&dawn_desc_info.dawn_desc),
      webgpu_desc->label());
  return pipeline;
}

GPURenderPipeline::GPURenderPipeline(GPUDevice* device,
                                     wgpu::RenderPipeline render_pipeline,
                                     const String& label)
    : DawnObject<wgpu::RenderPipeline>(device,
                                       std::move(render_pipeline),
                                       label) {}

GPUBindGroupLayout* GPURenderPipeline::getBindGroupLayout(uint32_t index) {
  return MakeGarbageCollected<GPUBindGroupLayout>(
      device_, GetHandle().GetBindGroupLayout(index), String());
}

}  // namespace blink
