/*
 * Copyright 2020 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/gpu/ganesh/vk/GrVkMSAALoadManager.h"

#include "include/gpu/GrDirectContext.h"
#include "src/core/SkTraceEvent.h"
#include "src/gpu/ganesh/GrDirectContextPriv.h"
#include "src/gpu/ganesh/GrResourceProvider.h"
#include "src/gpu/ganesh/vk/GrVkBuffer.h"
#include "src/gpu/ganesh/vk/GrVkCommandBuffer.h"
#include "src/gpu/ganesh/vk/GrVkDescriptorSet.h"
#include "src/gpu/ganesh/vk/GrVkGpu.h"
#include "src/gpu/ganesh/vk/GrVkImageView.h"
#include "src/gpu/ganesh/vk/GrVkPipeline.h"
#include "src/gpu/ganesh/vk/GrVkRenderTarget.h"
#include "src/gpu/ganesh/vk/GrVkResourceProvider.h"
#include "src/gpu/ganesh/vk/GrVkUtil.h"
#include "src/sksl/SkSLProgramSettings.h"

GrVkMSAALoadManager::GrVkMSAALoadManager()
        : fVertShaderModule(VK_NULL_HANDLE)
        , fFragShaderModule(VK_NULL_HANDLE)
        , fPipelineLayout(VK_NULL_HANDLE) {}

GrVkMSAALoadManager::~GrVkMSAALoadManager() {}

bool GrVkMSAALoadManager::createMSAALoadProgram(GrVkGpu* gpu) {
    TRACE_EVENT0("skia", TRACE_FUNC);

    std::string vertShaderText;
    vertShaderText.append(
            "layout(spirv, set=0, binding=0) uniform vertexUniformBuffer {"
            "half4 uPosXform;"
            "};"

            "// MSAA Load Program VS\n"
            "void main() {"
            "float2 position = float2(sk_VertexID >> 1, sk_VertexID & 1);"
            "sk_Position.xy = position * uPosXform.xy + uPosXform.zw;"
            "sk_Position.zw = half2(0, 1);"
            "}");

    std::string fragShaderText;
    fragShaderText.append(
            "layout(spirv, input_attachment_index=0, set=2, binding=0) uniform subpassInput uInput;"

            "// MSAA Load Program FS\n"
            "void main() {"
            "sk_FragColor = subpassLoad(uInput);"
            "}");

    SkSL::ProgramSettings settings;
    std::string spirv;
    SkSL::Program::Inputs inputs;
    if (!GrCompileVkShaderModule(gpu, vertShaderText, VK_SHADER_STAGE_VERTEX_BIT,
                                 &fVertShaderModule, &fShaderStageInfo[0], settings, &spirv,
                                 &inputs)) {
        this->destroyResources(gpu);
        return false;
    }
    SkASSERT(inputs == SkSL::Program::Inputs());

    if (!GrCompileVkShaderModule(gpu, fragShaderText, VK_SHADER_STAGE_FRAGMENT_BIT,
                                 &fFragShaderModule, &fShaderStageInfo[1], settings, &spirv,
                                 &inputs)) {
        this->destroyResources(gpu);
        return false;
    }
    SkASSERT(inputs == SkSL::Program::Inputs());

    VkDescriptorSetLayout dsLayout[GrVkUniformHandler::kDescSetCount];

    GrVkResourceProvider& resourceProvider = gpu->resourceProvider();

    dsLayout[GrVkUniformHandler::kUniformBufferDescSet] = resourceProvider.getUniformDSLayout();

    // Even though we don't have a sampler we need to put a valid handle here (of zero samplers)
    // since we set up our descriptor layout to be uniform, sampler, input.
    //
    // TODO: We should have a more general way for different pipelines to describe their descriptor
    // layouts so that we don't have to use the compile time constants for the sets.
    GrVkDescriptorSetManager::Handle samplerHandle;
    resourceProvider.getZeroSamplerDescriptorSetHandle(&samplerHandle);

    dsLayout[GrVkUniformHandler::kSamplerDescSet] =
            resourceProvider.getSamplerDSLayout(samplerHandle);

    dsLayout[GrVkUniformHandler::kInputDescSet] = resourceProvider.getInputDSLayout();

    // Create the VkPipelineLayout
    VkPipelineLayoutCreateInfo layoutCreateInfo;
    memset(&layoutCreateInfo, 0, sizeof(VkPipelineLayoutCreateFlags));
    layoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
    layoutCreateInfo.pNext = nullptr;
    layoutCreateInfo.flags = 0;
    layoutCreateInfo.setLayoutCount = GrVkUniformHandler::kDescSetCount;
    layoutCreateInfo.pSetLayouts = dsLayout;
    layoutCreateInfo.pushConstantRangeCount = 0;
    layoutCreateInfo.pPushConstantRanges = nullptr;

    VkResult err = GR_VK_CALL(
            gpu->vkInterface(),
            CreatePipelineLayout(gpu->device(), &layoutCreateInfo, nullptr, &fPipelineLayout));
    if (err) {
        this->destroyResources(gpu);
        return false;
    }

    return true;
}

bool GrVkMSAALoadManager::loadMSAAFromResolve(GrVkGpu* gpu,
                                              GrVkCommandBuffer* commandBuffer,
                                              const GrVkRenderPass& renderPass,
                                              GrAttachment* dst,
                                              GrVkImage* src,
                                              const SkIRect& rect) {
    if (!dst) {
        return false;
    }
    if (!src || !src->supportsInputAttachmentUsage()) {
        return false;
    }

    if (VK_NULL_HANDLE == fVertShaderModule) {
        SkASSERT(fFragShaderModule == VK_NULL_HANDLE && fPipelineLayout == VK_NULL_HANDLE);
        if (!this->createMSAALoadProgram(gpu)) {
            SkDebugf("Failed to create copy program.\n");
            return false;
        }
    }
    SkASSERT(fPipelineLayout != VK_NULL_HANDLE);

    GrVkResourceProvider& resourceProv = gpu->resourceProvider();

    sk_sp<const GrVkPipeline> pipeline =
            resourceProv.findOrCreateMSAALoadPipeline(renderPass, dst->numSamples(),
                                                      fShaderStageInfo, fPipelineLayout);
    if (!pipeline) {
        return false;
    }
    commandBuffer->bindPipeline(gpu, std::move(pipeline));

    // Set Dynamic viewport and stencil
    // We always use one viewport the size of the RT
    VkViewport viewport;
    viewport.x = 0.0f;
    viewport.y = 0.0f;
    viewport.width = SkIntToScalar(dst->width());
    viewport.height = SkIntToScalar(dst->height());
    viewport.minDepth = 0.0f;
    viewport.maxDepth = 1.0f;
    commandBuffer->setViewport(gpu, 0, 1, &viewport);

    // We assume the scissor is not enabled so just set it to the whole RT
    VkRect2D scissor;
    scissor.extent.width = dst->width();
    scissor.extent.height = dst->height();
    scissor.offset.x = 0;
    scissor.offset.y = 0;
    commandBuffer->setScissor(gpu, 0, 1, &scissor);

    // Update and bind uniform descriptor set
    int w = rect.width();
    int h = rect.height();

    // dst rect edges in NDC (-1 to 1)
    int dw = dst->width();
    int dh = dst->height();
    float dx0 = 2.f * rect.fLeft / dw - 1.f;
    float dx1 = 2.f * (rect.fLeft + w) / dw - 1.f;
    float dy0 = 2.f * rect.fTop / dh - 1.f;
    float dy1 = 2.f * (rect.fTop + h) / dh - 1.f;

    float uniData[] = {dx1 - dx0, dy1 - dy0, dx0, dy0};  // posXform

    GrResourceProvider* resourceProvider = gpu->getContext()->priv().resourceProvider();
    // TODO: Is it worth holding onto the last used uniform buffer and tracking the width, height,
    // dst width, and dst height so that we can use the buffer again without having to update the
    // data?
    sk_sp<GrGpuBuffer> uniformBuffer = resourceProvider->createBuffer(uniData,
                                                                      sizeof(uniData),
                                                                      GrGpuBufferType::kUniform,
                                                                      kDynamic_GrAccessPattern);
    if (!uniformBuffer) {
        return false;
    }
    GrVkBuffer* vkUniformBuffer = static_cast<GrVkBuffer*>(uniformBuffer.get());
    static_assert(GrVkUniformHandler::kUniformBufferDescSet < GrVkUniformHandler::kInputDescSet);
    commandBuffer->bindDescriptorSets(gpu, fPipelineLayout,
                                      GrVkUniformHandler::kUniformBufferDescSet,
                                      /*setCount=*/1, vkUniformBuffer->uniformDescriptorSet(),
                                      /*dynamicOffsetCount=*/0, /*dynamicOffsets=*/nullptr);
    commandBuffer->addGrBuffer(std::move(uniformBuffer));

    // Update the input descriptor set
    gr_rp<const GrVkDescriptorSet> inputDS = src->inputDescSetForMSAALoad(gpu);
    if (!inputDS) {
        return false;
    }
    commandBuffer->bindDescriptorSets(gpu, fPipelineLayout,
                                      GrVkUniformHandler::kInputDescSet, /*setCount=*/1,
                                      inputDS->descriptorSet(),
                                      /*dynamicOffsetCount=*/0, /*dynamicOffsets=*/nullptr);

    // We don't need to add the src and dst resources here since those are all tracked by the main
    // render pass code out in GrVkOpsRenderPass and GrVkRenderTarget::adResources.
    commandBuffer->addRecycledResource(std::move(inputDS));

    commandBuffer->draw(gpu, 4, 1, 0, 0);

    return true;
}

void GrVkMSAALoadManager::destroyResources(GrVkGpu* gpu) {
    if (fVertShaderModule != VK_NULL_HANDLE) {
        GR_VK_CALL(gpu->vkInterface(),
                   DestroyShaderModule(gpu->device(), fVertShaderModule, nullptr));
        fVertShaderModule = VK_NULL_HANDLE;
    }

    if (fFragShaderModule != VK_NULL_HANDLE) {
        GR_VK_CALL(gpu->vkInterface(),
                   DestroyShaderModule(gpu->device(), fFragShaderModule, nullptr));
        fFragShaderModule = VK_NULL_HANDLE;
    }

    if (fPipelineLayout != VK_NULL_HANDLE) {
        GR_VK_CALL(gpu->vkInterface(),
                   DestroyPipelineLayout(gpu->device(), fPipelineLayout, nullptr));
        fPipelineLayout = VK_NULL_HANDLE;
    }
}

