// Copyright 2018 The Amber Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "src/vulkan/graphics_pipeline.h"

#include <cassert>
#include <cmath>

#include "src/command.h"
#include "src/make_unique.h"
#include "src/vulkan/command_pool.h"
#include "src/vulkan/device.h"

namespace amber {
namespace vulkan {
namespace {

const VkAttachmentDescription kDefaultAttachmentDesc = {
    0,                                    /* flags */
    VK_FORMAT_UNDEFINED,                  /* format */
    VK_SAMPLE_COUNT_1_BIT,                /* samples */
    VK_ATTACHMENT_LOAD_OP_LOAD,           /* loadOp */
    VK_ATTACHMENT_STORE_OP_STORE,         /* storeOp */
    VK_ATTACHMENT_LOAD_OP_LOAD,           /* stencilLoadOp */
    VK_ATTACHMENT_STORE_OP_STORE,         /* stencilStoreOp */
    VK_IMAGE_LAYOUT_UNDEFINED,            /* initialLayout */
    VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, /* finalLayout */
};

const VkSampleMask kSampleMask = ~0U;

VkPrimitiveTopology ToVkTopology(Topology topology) {
  switch (topology) {
    case Topology::kPointList:
      return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
    case Topology::kLineList:
      return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
    case Topology::kLineStrip:
      return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
    case Topology::kTriangleList:
      return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
    case Topology::kTriangleStrip:
      return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
    case Topology::kTriangleFan:
      return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
    case Topology::kLineListWithAdjacency:
      return VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY;
    case Topology::kLineStripWithAdjacency:
      return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY;
    case Topology::kTriangleListWithAdjacency:
      return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY;
    case Topology::kTriangleStripWithAdjacency:
      return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY;
    case Topology::kPatchList:
      return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
    default:
      break;
  }

  assert(false && "Vulkan::Unknown topology");
  return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
}

VkStencilOp ToVkStencilOp(StencilOp op) {
  switch (op) {
    case StencilOp::kKeep:
      return VK_STENCIL_OP_KEEP;
    case StencilOp::kZero:
      return VK_STENCIL_OP_ZERO;
    case StencilOp::kReplace:
      return VK_STENCIL_OP_REPLACE;
    case StencilOp::kIncrementAndClamp:
      return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
    case StencilOp::kDecrementAndClamp:
      return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
    case StencilOp::kInvert:
      return VK_STENCIL_OP_INVERT;
    case StencilOp::kIncrementAndWrap:
      return VK_STENCIL_OP_INCREMENT_AND_WRAP;
    case StencilOp::kDecrementAndWrap:
      return VK_STENCIL_OP_DECREMENT_AND_WRAP;
    case StencilOp::kUnknown:
      break;
  }
  assert(false && "Vulkan::Unknown StencilOp");
  return VK_STENCIL_OP_KEEP;
}

VkCompareOp ToVkCompareOp(CompareOp op) {
  switch (op) {
    case CompareOp::kNever:
      return VK_COMPARE_OP_NEVER;
    case CompareOp::kLess:
      return VK_COMPARE_OP_LESS;
    case CompareOp::kEqual:
      return VK_COMPARE_OP_EQUAL;
    case CompareOp::kLessOrEqual:
      return VK_COMPARE_OP_LESS_OR_EQUAL;
    case CompareOp::kGreater:
      return VK_COMPARE_OP_GREATER;
    case CompareOp::kNotEqual:
      return VK_COMPARE_OP_NOT_EQUAL;
    case CompareOp::kGreaterOrEqual:
      return VK_COMPARE_OP_GREATER_OR_EQUAL;
    case CompareOp::kAlways:
      return VK_COMPARE_OP_ALWAYS;
    case CompareOp::kUnknown:
      break;
  }
  assert(false && "Vulkan::Unknown CompareOp");
  return VK_COMPARE_OP_NEVER;
}

VkPolygonMode ToVkPolygonMode(PolygonMode mode) {
  switch (mode) {
    case PolygonMode::kFill:
      return VK_POLYGON_MODE_FILL;
    case PolygonMode::kLine:
      return VK_POLYGON_MODE_LINE;
    case PolygonMode::kPoint:
      return VK_POLYGON_MODE_POINT;
  }
  assert(false && "Vulkan::Unknown PolygonMode");
  return VK_POLYGON_MODE_FILL;
}

VkCullModeFlags ToVkCullMode(CullMode mode) {
  switch (mode) {
    case CullMode::kNone:
      return VK_CULL_MODE_NONE;
    case CullMode::kFront:
      return VK_CULL_MODE_FRONT_BIT;
    case CullMode::kBack:
      return VK_CULL_MODE_BACK_BIT;
    case CullMode::kFrontAndBack:
      return VK_CULL_MODE_FRONT_AND_BACK;
  }
  assert(false && "Vulkan::Unknown CullMode");
  return VK_CULL_MODE_NONE;
}

VkFrontFace ToVkFrontFace(FrontFace front_face) {
  return front_face == FrontFace::kClockwise ? VK_FRONT_FACE_CLOCKWISE
                                             : VK_FRONT_FACE_COUNTER_CLOCKWISE;
}

VkLogicOp ToVkLogicOp(LogicOp op) {
  switch (op) {
    case LogicOp::kClear:
      return VK_LOGIC_OP_CLEAR;
    case LogicOp::kAnd:
      return VK_LOGIC_OP_AND;
    case LogicOp::kAndReverse:
      return VK_LOGIC_OP_AND_REVERSE;
    case LogicOp::kCopy:
      return VK_LOGIC_OP_COPY;
    case LogicOp::kAndInverted:
      return VK_LOGIC_OP_AND_INVERTED;
    case LogicOp::kNoOp:
      return VK_LOGIC_OP_NO_OP;
    case LogicOp::kXor:
      return VK_LOGIC_OP_XOR;
    case LogicOp::kOr:
      return VK_LOGIC_OP_OR;
    case LogicOp::kNor:
      return VK_LOGIC_OP_NOR;
    case LogicOp::kEquivalent:
      return VK_LOGIC_OP_EQUIVALENT;
    case LogicOp::kInvert:
      return VK_LOGIC_OP_INVERT;
    case LogicOp::kOrReverse:
      return VK_LOGIC_OP_OR_REVERSE;
    case LogicOp::kCopyInverted:
      return VK_LOGIC_OP_COPY_INVERTED;
    case LogicOp::kOrInverted:
      return VK_LOGIC_OP_OR_INVERTED;
    case LogicOp::kNand:
      return VK_LOGIC_OP_NAND;
    case LogicOp::kSet:
      return VK_LOGIC_OP_SET;
  }
  assert(false && "Vulkan::Unknown LogicOp");
  return VK_LOGIC_OP_CLEAR;
}

VkBlendFactor ToVkBlendFactor(BlendFactor factor) {
  switch (factor) {
    case BlendFactor::kZero:
      return VK_BLEND_FACTOR_ZERO;
    case BlendFactor::kOne:
      return VK_BLEND_FACTOR_ONE;
    case BlendFactor::kSrcColor:
      return VK_BLEND_FACTOR_SRC_COLOR;
    case BlendFactor::kOneMinusSrcColor:
      return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
    case BlendFactor::kDstColor:
      return VK_BLEND_FACTOR_DST_COLOR;
    case BlendFactor::kOneMinusDstColor:
      return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
    case BlendFactor::kSrcAlpha:
      return VK_BLEND_FACTOR_SRC_ALPHA;
    case BlendFactor::kOneMinusSrcAlpha:
      return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
    case BlendFactor::kDstAlpha:
      return VK_BLEND_FACTOR_DST_ALPHA;
    case BlendFactor::kOneMinusDstAlpha:
      return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
    case BlendFactor::kConstantColor:
      return VK_BLEND_FACTOR_CONSTANT_COLOR;
    case BlendFactor::kOneMinusConstantColor:
      return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR;
    case BlendFactor::kConstantAlpha:
      return VK_BLEND_FACTOR_CONSTANT_ALPHA;
    case BlendFactor::kOneMinusConstantAlpha:
      return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA;
    case BlendFactor::kSrcAlphaSaturate:
      return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE;
    case BlendFactor::kSrc1Color:
      return VK_BLEND_FACTOR_SRC1_COLOR;
    case BlendFactor::kOneMinusSrc1Color:
      return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR;
    case BlendFactor::kSrc1Alpha:
      return VK_BLEND_FACTOR_SRC1_ALPHA;
    case BlendFactor::kOneMinusSrc1Alpha:
      return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA;
    case BlendFactor::kUnknown:
      break;
  }
  assert(false && "Vulkan::Unknown BlendFactor");
  return VK_BLEND_FACTOR_ZERO;
}

VkBlendOp ToVkBlendOp(BlendOp op) {
  switch (op) {
    case BlendOp::kAdd:
      return VK_BLEND_OP_ADD;
    case BlendOp::kSubtract:
      return VK_BLEND_OP_SUBTRACT;
    case BlendOp::kReverseSubtract:
      return VK_BLEND_OP_REVERSE_SUBTRACT;
    case BlendOp::kMin:
      return VK_BLEND_OP_MIN;
    case BlendOp::kMax:
      return VK_BLEND_OP_MAX;
    case BlendOp::kZero:
      return VK_BLEND_OP_ZERO_EXT;
    case BlendOp::kSrc:
      return VK_BLEND_OP_SRC_EXT;
    case BlendOp::kDst:
      return VK_BLEND_OP_DST_EXT;
    case BlendOp::kSrcOver:
      return VK_BLEND_OP_SRC_OVER_EXT;
    case BlendOp::kDstOver:
      return VK_BLEND_OP_DST_OVER_EXT;
    case BlendOp::kSrcIn:
      return VK_BLEND_OP_SRC_IN_EXT;
    case BlendOp::kDstIn:
      return VK_BLEND_OP_DST_IN_EXT;
    case BlendOp::kSrcOut:
      return VK_BLEND_OP_SRC_OUT_EXT;
    case BlendOp::kDstOut:
      return VK_BLEND_OP_DST_OUT_EXT;
    case BlendOp::kSrcAtop:
      return VK_BLEND_OP_SRC_ATOP_EXT;
    case BlendOp::kDstAtop:
      return VK_BLEND_OP_DST_ATOP_EXT;
    case BlendOp::kXor:
      return VK_BLEND_OP_XOR_EXT;
    case BlendOp::kMultiply:
      return VK_BLEND_OP_MULTIPLY_EXT;
    case BlendOp::kScreen:
      return VK_BLEND_OP_SCREEN_EXT;
    case BlendOp::kOverlay:
      return VK_BLEND_OP_OVERLAY_EXT;
    case BlendOp::kDarken:
      return VK_BLEND_OP_DARKEN_EXT;
    case BlendOp::kLighten:
      return VK_BLEND_OP_LIGHTEN_EXT;
    case BlendOp::kColorDodge:
      return VK_BLEND_OP_COLORDODGE_EXT;
    case BlendOp::kColorBurn:
      return VK_BLEND_OP_COLORBURN_EXT;
    case BlendOp::kHardLight:
      return VK_BLEND_OP_HARDLIGHT_EXT;
    case BlendOp::kSoftLight:
      return VK_BLEND_OP_SOFTLIGHT_EXT;
    case BlendOp::kDifference:
      return VK_BLEND_OP_DIFFERENCE_EXT;
    case BlendOp::kExclusion:
      return VK_BLEND_OP_EXCLUSION_EXT;
    case BlendOp::kInvert:
      return VK_BLEND_OP_INVERT_EXT;
    case BlendOp::kInvertRGB:
      return VK_BLEND_OP_INVERT_RGB_EXT;
    case BlendOp::kLinearDodge:
      return VK_BLEND_OP_LINEARDODGE_EXT;
    case BlendOp::kLinearBurn:
      return VK_BLEND_OP_LINEARBURN_EXT;
    case BlendOp::kVividLight:
      return VK_BLEND_OP_VIVIDLIGHT_EXT;
    case BlendOp::kLinearLight:
      return VK_BLEND_OP_LINEARLIGHT_EXT;
    case BlendOp::kPinLight:
      return VK_BLEND_OP_PINLIGHT_EXT;
    case BlendOp::kHardMix:
      return VK_BLEND_OP_HARDMIX_EXT;
    case BlendOp::kHslHue:
      return VK_BLEND_OP_HSL_HUE_EXT;
    case BlendOp::kHslSaturation:
      return VK_BLEND_OP_HSL_SATURATION_EXT;
    case BlendOp::kHslColor:
      return VK_BLEND_OP_HSL_COLOR_EXT;
    case BlendOp::kHslLuminosity:
      return VK_BLEND_OP_HSL_LUMINOSITY_EXT;
    case BlendOp::kPlus:
      return VK_BLEND_OP_PLUS_EXT;
    case BlendOp::kPlusClamped:
      return VK_BLEND_OP_PLUS_CLAMPED_EXT;
    case BlendOp::kPlusClampedAlpha:
      return VK_BLEND_OP_PLUS_CLAMPED_ALPHA_EXT;
    case BlendOp::kPlusDarker:
      return VK_BLEND_OP_PLUS_DARKER_EXT;
    case BlendOp::kMinus:
      return VK_BLEND_OP_MINUS_EXT;
    case BlendOp::kMinusClamped:
      return VK_BLEND_OP_MINUS_CLAMPED_EXT;
    case BlendOp::kContrast:
      return VK_BLEND_OP_CONTRAST_EXT;
    case BlendOp::kInvertOvg:
      return VK_BLEND_OP_INVERT_OVG_EXT;
    case BlendOp::kRed:
      return VK_BLEND_OP_RED_EXT;
    case BlendOp::kGreen:
      return VK_BLEND_OP_GREEN_EXT;
    case BlendOp::kBlue:
      return VK_BLEND_OP_BLUE_EXT;
    case BlendOp::kUnknown:
      break;
  }
  assert(false && "Vulkan::Unknown BlendOp");
  return VK_BLEND_OP_ADD;
}

class RenderPassGuard {
 public:
  explicit RenderPassGuard(GraphicsPipeline* pipeline) : pipeline_(pipeline) {
    auto* frame = pipeline_->GetFrameBuffer();
    auto* cmd = pipeline_->GetCommandBuffer();
    frame->ChangeFrameToDrawLayout(cmd);

    VkRenderPassBeginInfo render_begin_info = VkRenderPassBeginInfo();
    render_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
    render_begin_info.renderPass = pipeline_->GetVkRenderPass();
    render_begin_info.framebuffer = frame->GetVkFrameBuffer();
    render_begin_info.renderArea = {{0, 0},
                                    {frame->GetWidth(), frame->GetHeight()}};
    pipeline_->GetDevice()->GetPtrs()->vkCmdBeginRenderPass(
        cmd->GetVkCommandBuffer(), &render_begin_info,
        VK_SUBPASS_CONTENTS_INLINE);
  }

  ~RenderPassGuard() {
    auto* cmd = pipeline_->GetCommandBuffer();

    pipeline_->GetDevice()->GetPtrs()->vkCmdEndRenderPass(
        cmd->GetVkCommandBuffer());

    auto* frame = pipeline_->GetFrameBuffer();
    frame->ChangeFrameToProbeLayout(cmd);
  }

 private:
  GraphicsPipeline* pipeline_;
};

}  // namespace

GraphicsPipeline::GraphicsPipeline(
    Device* device,
    const std::vector<amber::Pipeline::BufferInfo>& color_buffers,
    amber::Pipeline::BufferInfo depth_stencil_buffer,
    const std::vector<amber::Pipeline::BufferInfo>& resolve_targets,
    uint32_t fence_timeout_ms,
    bool pipeline_runtime_layer_enabled,
    const std::vector<VkPipelineShaderStageCreateInfo>& shader_stage_info)
    : Pipeline(PipelineType::kGraphics,
               device,
               fence_timeout_ms,
               pipeline_runtime_layer_enabled,
               shader_stage_info),
      depth_stencil_buffer_(depth_stencil_buffer) {
  for (const auto& info : color_buffers)
    color_buffers_.push_back(&info);
  for (const auto& info : resolve_targets)
    resolve_targets_.push_back(&info);
}

GraphicsPipeline::~GraphicsPipeline() {
  if (render_pass_) {
    device_->GetPtrs()->vkDestroyRenderPass(device_->GetVkDevice(),
                                            render_pass_, nullptr);
  }
}

Result GraphicsPipeline::CreateRenderPass() {
  VkSubpassDescription subpass_desc = VkSubpassDescription();
  subpass_desc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;

  std::vector<VkAttachmentDescription> attachment_desc;

  std::vector<VkAttachmentReference> color_refer;
  VkAttachmentReference depth_refer = VkAttachmentReference();
  std::vector<VkAttachmentReference> resolve_refer;

  for (const auto* info : color_buffers_) {
    attachment_desc.push_back(kDefaultAttachmentDesc);
    attachment_desc.back().format =
        device_->GetVkFormat(*info->buffer->GetFormat());
    attachment_desc.back().initialLayout =
        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
    attachment_desc.back().finalLayout =
        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
    attachment_desc.back().samples =
        static_cast<VkSampleCountFlagBits>(info->buffer->GetSamples());

    VkAttachmentReference ref = VkAttachmentReference();
    ref.attachment = static_cast<uint32_t>(attachment_desc.size() - 1);
    ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
    color_refer.push_back(ref);
  }
  subpass_desc.colorAttachmentCount = static_cast<uint32_t>(color_refer.size());
  subpass_desc.pColorAttachments = color_refer.data();

  if (depth_stencil_buffer_.buffer &&
      depth_stencil_buffer_.buffer->GetFormat()->IsFormatKnown()) {
    attachment_desc.push_back(kDefaultAttachmentDesc);
    attachment_desc.back().format =
        device_->GetVkFormat(*depth_stencil_buffer_.buffer->GetFormat());
    attachment_desc.back().initialLayout =
        VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
    attachment_desc.back().finalLayout =
        VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

    depth_refer.attachment = static_cast<uint32_t>(attachment_desc.size() - 1);
    depth_refer.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

    subpass_desc.pDepthStencilAttachment = &depth_refer;
  }

  for (const auto* info : resolve_targets_) {
    attachment_desc.push_back(kDefaultAttachmentDesc);
    attachment_desc.back().format =
        device_->GetVkFormat(*info->buffer->GetFormat());
    attachment_desc.back().initialLayout =
        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
    attachment_desc.back().finalLayout =
        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

    VkAttachmentReference ref = VkAttachmentReference();
    ref.attachment = static_cast<uint32_t>(attachment_desc.size() - 1);
    ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
    resolve_refer.push_back(ref);
  }

  subpass_desc.pResolveAttachments = resolve_refer.data();

  VkRenderPassCreateInfo render_pass_info = VkRenderPassCreateInfo();
  render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
  render_pass_info.attachmentCount =
      static_cast<uint32_t>(attachment_desc.size());
  render_pass_info.pAttachments = attachment_desc.data();
  render_pass_info.subpassCount = 1;
  render_pass_info.pSubpasses = &subpass_desc;

  if (device_->GetPtrs()->vkCreateRenderPass(device_->GetVkDevice(),
                                             &render_pass_info, nullptr,
                                             &render_pass_) != VK_SUCCESS) {
    return Result("Vulkan::Calling vkCreateRenderPass Fail");
  }

  return {};
}

VkPipelineDepthStencilStateCreateInfo
GraphicsPipeline::GetVkPipelineDepthStencilInfo(
    const PipelineData* pipeline_data) {
  VkPipelineDepthStencilStateCreateInfo depthstencil_info =
      VkPipelineDepthStencilStateCreateInfo();
  depthstencil_info.sType =
      VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;

  depthstencil_info.depthTestEnable = pipeline_data->GetEnableDepthTest();
  depthstencil_info.depthWriteEnable = pipeline_data->GetEnableDepthWrite();
  depthstencil_info.depthCompareOp =
      ToVkCompareOp(pipeline_data->GetDepthCompareOp());
  depthstencil_info.depthBoundsTestEnable =
      pipeline_data->GetEnableDepthBoundsTest();
  depthstencil_info.stencilTestEnable = pipeline_data->GetEnableStencilTest();

  depthstencil_info.front.failOp =
      ToVkStencilOp(pipeline_data->GetFrontFailOp());
  depthstencil_info.front.passOp =
      ToVkStencilOp(pipeline_data->GetFrontPassOp());
  depthstencil_info.front.depthFailOp =
      ToVkStencilOp(pipeline_data->GetFrontDepthFailOp());
  depthstencil_info.front.compareOp =
      ToVkCompareOp(pipeline_data->GetFrontCompareOp());
  depthstencil_info.front.compareMask = pipeline_data->GetFrontCompareMask();
  depthstencil_info.front.writeMask = pipeline_data->GetFrontWriteMask();
  depthstencil_info.front.reference = pipeline_data->GetFrontReference();

  depthstencil_info.back.failOp = ToVkStencilOp(pipeline_data->GetBackFailOp());
  depthstencil_info.back.passOp = ToVkStencilOp(pipeline_data->GetBackPassOp());
  depthstencil_info.back.depthFailOp =
      ToVkStencilOp(pipeline_data->GetBackDepthFailOp());
  depthstencil_info.back.compareOp =
      ToVkCompareOp(pipeline_data->GetBackCompareOp());
  depthstencil_info.back.compareMask = pipeline_data->GetBackCompareMask();
  depthstencil_info.back.writeMask = pipeline_data->GetBackWriteMask();
  depthstencil_info.back.reference = pipeline_data->GetBackReference();

  depthstencil_info.minDepthBounds = pipeline_data->GetMinDepthBounds();
  depthstencil_info.maxDepthBounds = pipeline_data->GetMaxDepthBounds();

  return depthstencil_info;
}

std::vector<VkPipelineColorBlendAttachmentState>
GraphicsPipeline::GetVkPipelineColorBlendAttachmentState(
    const PipelineData* pipeline_data) {
  std::vector<VkPipelineColorBlendAttachmentState> states;

  for (size_t i = 0; i < color_buffers_.size(); ++i) {
    VkPipelineColorBlendAttachmentState colorblend_attachment =
        VkPipelineColorBlendAttachmentState();
    colorblend_attachment.blendEnable = pipeline_data->GetEnableBlend();
    colorblend_attachment.srcColorBlendFactor =
        ToVkBlendFactor(pipeline_data->GetSrcColorBlendFactor());
    colorblend_attachment.dstColorBlendFactor =
        ToVkBlendFactor(pipeline_data->GetDstColorBlendFactor());
    colorblend_attachment.colorBlendOp =
        ToVkBlendOp(pipeline_data->GetColorBlendOp());
    colorblend_attachment.srcAlphaBlendFactor =
        ToVkBlendFactor(pipeline_data->GetSrcAlphaBlendFactor());
    colorblend_attachment.dstAlphaBlendFactor =
        ToVkBlendFactor(pipeline_data->GetDstAlphaBlendFactor());
    colorblend_attachment.alphaBlendOp =
        ToVkBlendOp(pipeline_data->GetAlphaBlendOp());
    colorblend_attachment.colorWriteMask = pipeline_data->GetColorWriteMask();
    states.push_back(colorblend_attachment);
  }
  return states;
}

Result GraphicsPipeline::CreateVkGraphicsPipeline(
    const PipelineData* pipeline_data,
    VkPrimitiveTopology topology,
    const VertexBuffer* vertex_buffer,
    const VkPipelineLayout& pipeline_layout,
    VkPipeline* pipeline) {
  if (!pipeline_data) {
    return Result(
        "Vulkan: GraphicsPipeline::CreateVkGraphicsPipeline PipelineData is "
        "null");
  }

  std::vector<VkVertexInputBindingDescription> vertex_bindings;
  std::vector<VkVertexInputAttributeDescription> vertex_attribs;
  if (vertex_buffer != nullptr) {
    vertex_bindings = vertex_buffer->GetVkVertexInputBinding();
    vertex_attribs = vertex_buffer->GetVkVertexInputAttr();
  }

  VkPipelineVertexInputStateCreateInfo vertex_input_info =
      VkPipelineVertexInputStateCreateInfo();
  vertex_input_info.sType =
      VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
  vertex_input_info.vertexBindingDescriptionCount =
      static_cast<uint32_t>(vertex_bindings.size());
  vertex_input_info.pVertexBindingDescriptions =
      vertex_bindings.empty() ? nullptr : vertex_bindings.data();
  vertex_input_info.vertexAttributeDescriptionCount =
      static_cast<uint32_t>(vertex_attribs.size());
  vertex_input_info.pVertexAttributeDescriptions =
      vertex_attribs.empty() ? nullptr : vertex_attribs.data();

  VkPipelineInputAssemblyStateCreateInfo input_assembly_info =
      VkPipelineInputAssemblyStateCreateInfo();
  input_assembly_info.sType =
      VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
  // TODO(jaebaek): Handle the given index if exists.
  input_assembly_info.topology = topology;
  input_assembly_info.primitiveRestartEnable =
      pipeline_data->GetEnablePrimitiveRestart();

  VkViewport viewport = {
      0, 0, static_cast<float>(frame_width_), static_cast<float>(frame_height_),
      0, 1};

  if (pipeline_data->HasViewportData()) {
    Viewport vp = pipeline_data->GetViewport();
    viewport.x = vp.x;
    viewport.y = vp.y;
    viewport.width = vp.w;
    viewport.height = vp.h;
    viewport.minDepth = vp.mind;
    viewport.maxDepth = vp.maxd;
  }

  VkRect2D scissor = {{0, 0}, {frame_width_, frame_height_}};

  VkPipelineViewportStateCreateInfo viewport_info =
      VkPipelineViewportStateCreateInfo();
  viewport_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
  viewport_info.viewportCount = 1;
  viewport_info.pViewports = &viewport;
  viewport_info.scissorCount = 1;
  viewport_info.pScissors = &scissor;

  auto shader_stage_info = GetVkShaderStageInfo();
  bool is_tessellation_needed = false;
  for (auto& info : shader_stage_info) {
    info.pName = GetEntryPointName(info.stage);
    if (info.stage == VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT ||
        info.stage == VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) {
      is_tessellation_needed = true;
    }
  }

  VkPipelineMultisampleStateCreateInfo multisampleInfo = {
      VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, /* sType */
      nullptr,                                                  /* pNext */
      0,                                                        /* flags */
      kDefaultAttachmentDesc.samples, /* rasterizationSamples */
      VK_FALSE,                       /* sampleShadingEnable */
      0,                              /* minSampleShading */
      &kSampleMask,                   /* pSampleMask */
      VK_FALSE,                       /* alphaToCoverageEnable */
      VK_FALSE,                       /* alphaToOneEnable */
  };

  // Search for multisampled color buffers and adjust the rasterization samples
  // to match.
  for (const auto& cb : color_buffers_) {
    uint32_t samples = cb->buffer->GetSamples();
    assert(static_cast<VkSampleCountFlagBits>(samples) >=
           multisampleInfo.rasterizationSamples);
    multisampleInfo.rasterizationSamples =
        static_cast<VkSampleCountFlagBits>(samples);
  }

  VkGraphicsPipelineCreateInfo pipeline_info = VkGraphicsPipelineCreateInfo();
  pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
  pipeline_info.stageCount = static_cast<uint32_t>(shader_stage_info.size());
  pipeline_info.pStages = shader_stage_info.data();
  pipeline_info.pVertexInputState = &vertex_input_info;
  pipeline_info.pInputAssemblyState = &input_assembly_info;
  pipeline_info.pViewportState = &viewport_info;
  pipeline_info.pMultisampleState = &multisampleInfo;

  VkPipelineRasterizationStateCreateInfo rasterization_info =
      VkPipelineRasterizationStateCreateInfo();
  rasterization_info.sType =
      VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
  rasterization_info.depthClampEnable = pipeline_data->GetEnableDepthClamp();
  rasterization_info.rasterizerDiscardEnable =
      pipeline_data->GetEnableRasterizerDiscard();
  rasterization_info.polygonMode =
      ToVkPolygonMode(pipeline_data->GetPolygonMode());
  rasterization_info.cullMode = ToVkCullMode(pipeline_data->GetCullMode());
  rasterization_info.frontFace = ToVkFrontFace(pipeline_data->GetFrontFace());
  rasterization_info.depthBiasEnable = pipeline_data->GetEnableDepthBias();
  rasterization_info.depthBiasConstantFactor =
      pipeline_data->GetDepthBiasConstantFactor();
  rasterization_info.depthBiasClamp = pipeline_data->GetDepthBiasClamp();
  rasterization_info.depthBiasSlopeFactor =
      pipeline_data->GetDepthBiasSlopeFactor();
  rasterization_info.lineWidth = pipeline_data->GetLineWidth();
  pipeline_info.pRasterizationState = &rasterization_info;

  VkPipelineTessellationStateCreateInfo tess_info =
      VkPipelineTessellationStateCreateInfo();
  if (is_tessellation_needed) {
    tess_info.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
    tess_info.patchControlPoints = patch_control_points_;
    pipeline_info.pTessellationState = &tess_info;
  }

  VkPipelineDepthStencilStateCreateInfo depthstencil_info;
  if (depth_stencil_buffer_.buffer &&
      depth_stencil_buffer_.buffer->GetFormat()->IsFormatKnown()) {
    depthstencil_info = GetVkPipelineDepthStencilInfo(pipeline_data);
    pipeline_info.pDepthStencilState = &depthstencil_info;
  }

  VkPipelineColorBlendStateCreateInfo colorblend_info =
      VkPipelineColorBlendStateCreateInfo();

  auto colorblend_attachment =
      GetVkPipelineColorBlendAttachmentState(pipeline_data);

  colorblend_info.sType =
      VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
  colorblend_info.logicOpEnable = pipeline_data->GetEnableLogicOp();
  colorblend_info.logicOp = ToVkLogicOp(pipeline_data->GetLogicOp());
  colorblend_info.attachmentCount =
      static_cast<uint32_t>(colorblend_attachment.size());
  colorblend_info.pAttachments = colorblend_attachment.data();
  pipeline_info.pColorBlendState = &colorblend_info;

  pipeline_info.layout = pipeline_layout;
  pipeline_info.renderPass = render_pass_;
  pipeline_info.subpass = 0;

  if (device_->GetPtrs()->vkCreateGraphicsPipelines(
          device_->GetVkDevice(), VK_NULL_HANDLE, 1, &pipeline_info, nullptr,
          pipeline) != VK_SUCCESS) {
    return Result("Vulkan::Calling vkCreateGraphicsPipelines Fail");
  }

  return {};
}

Result GraphicsPipeline::Initialize(uint32_t width,
                                    uint32_t height,
                                    CommandPool* pool) {
  Result r = Pipeline::Initialize(pool);
  if (!r.IsSuccess())
    return r;

  r = CreateRenderPass();
  if (!r.IsSuccess())
    return r;

  frame_ =
      MakeUnique<FrameBuffer>(device_, color_buffers_, depth_stencil_buffer_,
                              resolve_targets_, width, height);
  r = frame_->Initialize(render_pass_);
  if (!r.IsSuccess())
    return r;

  frame_width_ = width;
  frame_height_ = height;

  return {};
}

Result GraphicsPipeline::SendVertexBufferDataIfNeeded(
    VertexBuffer* vertex_buffer) {
  if (!vertex_buffer || vertex_buffer->VertexDataSent())
    return {};
  return vertex_buffer->SendVertexData(command_.get());
}

Result GraphicsPipeline::SetIndexBuffer(Buffer* buffer) {
  if (index_buffer_) {
    return Result(
        "GraphicsPipeline::SetIndexBuffer must be called once when "
        "index_buffer_ is created");
  }

  index_buffer_ = MakeUnique<IndexBuffer>(device_);

  CommandBufferGuard guard(GetCommandBuffer());
  if (!guard.IsRecording())
    return guard.GetResult();

  Result r = index_buffer_->SendIndexData(command_.get(), buffer);
  if (!r.IsSuccess())
    return r;

  return guard.Submit(GetFenceTimeout(), GetPipelineRuntimeLayerEnabled());
}

Result GraphicsPipeline::SetClearColor(float r, float g, float b, float a) {
  clear_color_r_ = r;
  clear_color_g_ = g;
  clear_color_b_ = b;
  clear_color_a_ = a;
  return {};
}

Result GraphicsPipeline::SetClearStencil(uint32_t stencil) {
  if (!depth_stencil_buffer_.buffer ||
      !depth_stencil_buffer_.buffer->GetFormat()->IsFormatKnown()) {
    return Result(
        "Vulkan::ClearStencilCommand No DepthStencil Buffer for FrameBuffer "
        "Exists");
  }

  clear_stencil_ = stencil;
  return {};
}

Result GraphicsPipeline::SetClearDepth(float depth) {
  if (!depth_stencil_buffer_.buffer ||
      !depth_stencil_buffer_.buffer->GetFormat()->IsFormatKnown()) {
    return Result(
        "Vulkan::ClearStencilCommand No DepthStencil Buffer for FrameBuffer "
        "Exists");
  }

  clear_depth_ = depth;
  return {};
}

Result GraphicsPipeline::Clear() {
  VkClearValue colour_clear;
  colour_clear.color = {
      {clear_color_r_, clear_color_g_, clear_color_b_, clear_color_a_}};

  CommandBufferGuard cmd_buf_guard(GetCommandBuffer());
  if (!cmd_buf_guard.IsRecording())
    return cmd_buf_guard.GetResult();

  frame_->ChangeFrameToWriteLayout(GetCommandBuffer());
  frame_->CopyBuffersToImages();
  frame_->TransferImagesToDevice(GetCommandBuffer());

  {
    RenderPassGuard render_pass_guard(this);

    std::vector<VkClearAttachment> clears;
    for (size_t i = 0; i < color_buffers_.size(); ++i) {
      VkClearAttachment clear_attachment = VkClearAttachment();
      clear_attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
      clear_attachment.colorAttachment = static_cast<uint32_t>(i);
      clear_attachment.clearValue = colour_clear;

      clears.push_back(clear_attachment);
    }

    if (depth_stencil_buffer_.buffer &&
        depth_stencil_buffer_.buffer->GetFormat()->IsFormatKnown()) {
      VkClearValue depth_stencil_clear;
      depth_stencil_clear.depthStencil = {clear_depth_, clear_stencil_};

      VkImageAspectFlags aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
      if (depth_stencil_buffer_.buffer->GetFormat()->HasStencilComponent())
        aspect |= VK_IMAGE_ASPECT_STENCIL_BIT;

      VkClearAttachment clear_attachment = VkClearAttachment();
      clear_attachment.aspectMask = aspect;
      clear_attachment.colorAttachment =
          static_cast<uint32_t>(color_buffers_.size());
      clear_attachment.clearValue = depth_stencil_clear;

      clears.push_back(clear_attachment);
    }

    VkClearRect clear_rect;
    clear_rect.rect = {{0, 0}, {frame_width_, frame_height_}};
    clear_rect.baseArrayLayer = 0;
    clear_rect.layerCount = 1;

    device_->GetPtrs()->vkCmdClearAttachments(
        command_->GetVkCommandBuffer(), static_cast<uint32_t>(clears.size()),
        clears.data(), 1, &clear_rect);
  }

  frame_->TransferImagesToHost(command_.get());

  Result r = cmd_buf_guard.Submit(GetFenceTimeout(),
                                  GetPipelineRuntimeLayerEnabled());
  if (!r.IsSuccess())
    return r;

  frame_->CopyImagesToBuffers();
  return {};
}

Result GraphicsPipeline::Draw(const DrawArraysCommand* command,
                              VertexBuffer* vertex_buffer) {
  Result r = SendDescriptorDataToDeviceIfNeeded();
  if (!r.IsSuccess())
    return r;

  VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
  r = CreateVkPipelineLayout(&pipeline_layout);
  if (!r.IsSuccess())
    return r;

  VkPipeline pipeline = VK_NULL_HANDLE;
  r = CreateVkGraphicsPipeline(command->GetPipelineData(),
                               ToVkTopology(command->GetTopology()),
                               vertex_buffer, pipeline_layout, &pipeline);
  if (!r.IsSuccess())
    return r;

  // Note that a command updating a descriptor set and a command using
  // it must be submitted separately, because using a descriptor set
  // while updating it is not safe.
  UpdateDescriptorSetsIfNeeded();

  {
    CommandBufferGuard cmd_buf_guard(GetCommandBuffer());
    if (!cmd_buf_guard.IsRecording())
      return cmd_buf_guard.GetResult();

    r = SendVertexBufferDataIfNeeded(vertex_buffer);
    if (!r.IsSuccess())
      return r;

    frame_->ChangeFrameToWriteLayout(GetCommandBuffer());
    frame_->CopyBuffersToImages();
    frame_->TransferImagesToDevice(GetCommandBuffer());

    {
      RenderPassGuard render_pass_guard(this);

      BindVkDescriptorSets(pipeline_layout);

      r = RecordPushConstant(pipeline_layout);
      if (!r.IsSuccess())
        return r;

      device_->GetPtrs()->vkCmdBindPipeline(command_->GetVkCommandBuffer(),
                                            VK_PIPELINE_BIND_POINT_GRAPHICS,
                                            pipeline);

      if (vertex_buffer != nullptr)
        vertex_buffer->BindToCommandBuffer(command_.get());

      if (command->IsIndexed()) {
        if (!index_buffer_)
          return Result("Vulkan: Draw indexed is used without given indices");

        r = index_buffer_->BindToCommandBuffer(command_.get());
        if (!r.IsSuccess())
          return r;

        // VkRunner spec says
        //   "vertexCount will be used as the index count, firstVertex
        //    becomes the vertex offset and firstIndex will always be zero."
        device_->GetPtrs()->vkCmdDrawIndexed(
            command_->GetVkCommandBuffer(),
            command->GetVertexCount(),   /* indexCount */
            command->GetInstanceCount(), /* instanceCount */
            0,                           /* firstIndex */
            static_cast<int32_t>(
                command->GetFirstVertexIndex()), /* vertexOffset */
            command->GetFirstInstance());        /* firstInstance */
      } else {
        device_->GetPtrs()->vkCmdDraw(
            command_->GetVkCommandBuffer(), command->GetVertexCount(),
            command->GetInstanceCount(), command->GetFirstVertexIndex(),
            command->GetFirstInstance());
      }
    }

    frame_->TransferImagesToHost(command_.get());

    r = cmd_buf_guard.Submit(GetFenceTimeout(),
                             GetPipelineRuntimeLayerEnabled());
    if (!r.IsSuccess())
      return r;
  }

  r = ReadbackDescriptorsToHostDataQueue();
  if (!r.IsSuccess())
    return r;

  frame_->CopyImagesToBuffers();

  device_->GetPtrs()->vkDestroyPipeline(device_->GetVkDevice(), pipeline,
                                        nullptr);
  device_->GetPtrs()->vkDestroyPipelineLayout(device_->GetVkDevice(),
                                              pipeline_layout, nullptr);
  return {};
}

}  // namespace vulkan
}  // namespace amber
