// Copyright 2021 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "experimental/webgpu/builtins.h"

#include "experimental/webgpu/shaders/builtin_shaders.h"
#include "iree/base/api.h"

static const char* iree_hal_webgpu_builtins_find_code(const char* file_name) {
  const iree_file_toc_t* files = iree_hal_webgpu_builtin_shaders_create();
  for (size_t i = 0; i < iree_hal_webgpu_builtin_shaders_size(); ++i) {
    if (strcmp(file_name, files[i].name) == 0) {
      return files[i].data;
    }
  }
  IREE_ASSERT_TRUE(false, "builtin wgsl file not found");
  return NULL;
}

static iree_status_t iree_hal_webgpu_builtins_initialize_fill_buffer(
    WGPUDevice device, iree_hal_webgpu_staging_buffer_t* staging_buffer,
    iree_hal_webgpu_builtin_fill_buffer_t* out_fill_buffer) {
  const WGPUBindGroupLayoutEntry buffer_binding = {
      .nextInChain = NULL,
      .binding = 0,
      .visibility = WGPUShaderStage_Compute,
      .buffer =
          {
              .nextInChain = NULL,
              .type = WGPUBufferBindingType_Storage,
              .hasDynamicOffset = false,
              .minBindingSize = 0,  // variable
          },
  };

  const WGPUBindGroupLayoutDescriptor buffer_group_layout_descriptor = {
      .nextInChain = NULL,
      .label = WGPU_DEBUG_LABEL("_builtin_fill_buffer_buffer"),
      .entryCount = 1,
      .entries = &buffer_binding,
  };
  WGPUBindGroupLayout buffer_group_layout =
      wgpuDeviceCreateBindGroupLayout(device, &buffer_group_layout_descriptor);
  if (!buffer_group_layout) {
    return iree_make_status(
        IREE_STATUS_INTERNAL,
        "failed to create fill_buffer builtin bind group layout");
  }

  const WGPUBindGroupLayout group_layouts[] = {
      staging_buffer->bind_group_layout,
      buffer_group_layout,
  };
  const WGPUPipelineLayoutDescriptor pipeline_layout_descriptor = {
      .nextInChain = NULL,
      .label = WGPU_DEBUG_LABEL("_builtin_fill_buffer_layout"),
      .bindGroupLayoutCount = (uint32_t)IREE_ARRAYSIZE(group_layouts),
      .bindGroupLayouts = group_layouts,
  };
  WGPUPipelineLayout pipeline_layout =
      wgpuDeviceCreatePipelineLayout(device, &pipeline_layout_descriptor);
  iree_wgpuBindGroupLayoutDrop(buffer_group_layout);
  if (!pipeline_layout) {
    return iree_make_status(
        IREE_STATUS_INTERNAL,
        "failed to create fill_buffer builtin pipeline layout");
  }

  const char* code = iree_hal_webgpu_builtins_find_code("fill_buffer.wgsl");
  const WGPUShaderModuleWGSLDescriptor wgsl_descriptor = {
      .chain =
          {
              .next = NULL,
              .sType = WGPUSType_ShaderModuleWGSLDescriptor,
          },
      .code = code,
  };
  const WGPUShaderModuleDescriptor module_descriptor = {
      .nextInChain = &wgsl_descriptor.chain,
      .label = WGPU_DEBUG_LABEL("_builtin_fill_buffer_wgsl"),
  };
  WGPUShaderModule module =
      wgpuDeviceCreateShaderModule(device, &module_descriptor);
  if (!module) {
    return iree_make_status(
        IREE_STATUS_INTERNAL,
        "failed to create fill_buffer builtin shader module");
  }

  const WGPUComputePipelineDescriptor pipeline_descriptor = {
      .nextInChain = NULL,
      .label = WGPU_DEBUG_LABEL("_builtin_fill_buffer"),
      .layout = pipeline_layout,
      .compute =
          {
              .nextInChain = NULL,
              .module = module,
              .entryPoint = "main",
          },
  };
  WGPUComputePipeline pipeline =
      wgpuDeviceCreateComputePipeline(device, &pipeline_descriptor);
  if (!pipeline) {
    return iree_make_status(IREE_STATUS_INTERNAL,
                            "failed to create fill_buffer builtin pipeline");
  }
  out_fill_buffer->pipeline = pipeline;
  out_fill_buffer->buffer_group_layout = buffer_group_layout;
  return iree_ok_status();
}

iree_status_t iree_hal_webgpu_builtins_initialize(
    WGPUDevice device, iree_hal_webgpu_staging_buffer_t* staging_buffer,
    iree_hal_webgpu_builtins_t* out_builtins) {
  IREE_ASSERT_ARGUMENT(device);
  IREE_ASSERT_ARGUMENT(staging_buffer);
  IREE_ASSERT_ARGUMENT(out_builtins);
  IREE_TRACE_ZONE_BEGIN(z0);
  memset(out_builtins, 0, sizeof(*out_builtins));

  IREE_RETURN_AND_END_ZONE_IF_ERROR(
      z0, iree_hal_webgpu_builtins_initialize_fill_buffer(
              device, staging_buffer, &out_builtins->fill_buffer));

  IREE_TRACE_ZONE_END(z0);
  return iree_ok_status();
}

void iree_hal_webgpu_builtins_deinitialize(
    iree_hal_webgpu_builtins_t* builtins) {
  IREE_ASSERT_ARGUMENT(builtins);
  IREE_TRACE_ZONE_BEGIN(z0);

  iree_wgpuBindGroupLayoutDrop(builtins->fill_buffer.buffer_group_layout);
  iree_wgpuComputePipelineDrop(builtins->fill_buffer.pipeline);

  memset(builtins, 0, sizeof(*builtins));
  IREE_TRACE_ZONE_END(z0);
}
