/*
 * Copyright 2025 Rive
 */

#include "draw_pipeline_layout_vulkan.hpp"

#include "rive/renderer/stack_vector.hpp"
#include "rive/renderer/vulkan/vulkan_context.hpp"
#include "rive/renderer/vulkan/render_context_vulkan_impl.hpp"
#include "shaders/constants.glsl"
#include "pipeline_manager_vulkan.hpp"

namespace rive::gpu
{
DrawPipelineLayoutVulkan::DrawPipelineLayoutVulkan(
    PipelineManagerVulkan* pipelineManager,
    gpu::InterlockMode interlockMode,
    RenderPassOptionsVulkan renderPassOptions) :
    m_vk(ref_rcp(pipelineManager->vulkanContext())),
    m_interlockMode(interlockMode),
    m_renderPassOptions(renderPassOptions)
{
    const bool fixedFunctionColorOutput =
        m_renderPassOptions & RenderPassOptionsVulkan::fixedFunctionColorOutput;

    // PLS planes get bound per flush as input attachments or storage
    // textures.
    const VkDescriptorType plsDescriptorType =
        (pipelineManager->plsBackingType(interlockMode) ==
         PipelineManagerVulkan::PLSBackingType::storageTexture)
            ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE
            : VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;

    StackVector<VkDescriptorSetLayoutBinding, PLS_PLANE_COUNT>
        plsLayoutBindings;

    if (!fixedFunctionColorOutput)
    {
        plsLayoutBindings.push_back({
            .binding = COLOR_PLANE_IDX,
            .descriptorType = plsDescriptorType,
            .descriptorCount = 1,
            .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
        });
    }

    if (interlockMode == gpu::InterlockMode::rasterOrdering ||
        interlockMode == gpu::InterlockMode::atomics ||
        interlockMode == gpu::InterlockMode::clockwise)
    {
        plsLayoutBindings.push_back({
            .binding = CLIP_PLANE_IDX,
            .descriptorType = plsDescriptorType,
            .descriptorCount = 1,
            .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
        });
        if (interlockMode == gpu::InterlockMode::rasterOrdering ||
            (interlockMode == gpu::InterlockMode::clockwise &&
             !fixedFunctionColorOutput))
        {
            plsLayoutBindings.push_back({
                .binding = SCRATCH_COLOR_PLANE_IDX,
                .descriptorType = plsDescriptorType,
                .descriptorCount = 1,
                .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
            });
        }
        plsLayoutBindings.push_back({
            .binding = COVERAGE_PLANE_IDX,
            .descriptorType = m_interlockMode == gpu::InterlockMode::atomics
                                  ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE
                                  : plsDescriptorType,
            .descriptorCount = 1,
            .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
        });
    }
    else if (interlockMode == gpu::InterlockMode::msaa)
    {
        // TODO: pipeline layouts aren't currently keyed by loadAction, but if
        // they were, we could only include this binding with
        // preserveRenderTarget.
        plsLayoutBindings.push_back({
            .binding = MSAA_COLOR_SEED_IDX,
            .descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
            .descriptorCount = 1,
            .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
        });
    }

    if (plsLayoutBindings.size() > 0)
    {
        VkDescriptorSetLayoutCreateInfo plsLayoutInfo = {
            .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
            .bindingCount = plsLayoutBindings.size(),
            .pBindings = plsLayoutBindings.data(),
        };

        VK_CHECK(
            m_vk->CreateDescriptorSetLayout(m_vk->device,
                                            &plsLayoutInfo,
                                            nullptr,
                                            &m_plsTextureDescriptorSetLayout));
    }

    VkDescriptorSetLayout pipelineDescriptorSetLayouts[] = {
        pipelineManager->perFlushDescriptorSetLayout(),
        pipelineManager->perDrawDescriptorSetLayout(),
        pipelineManager->immutableSamplerDescriptorSetLayout(),
        m_plsTextureDescriptorSetLayout,
    };
    static_assert(COLOR_PLANE_IDX == 0);
    static_assert(CLIP_PLANE_IDX == 1);
    static_assert(SCRATCH_COLOR_PLANE_IDX == 2);
    static_assert(COVERAGE_PLANE_IDX == 3);
    static_assert(BINDINGS_SET_COUNT == 4);

    VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
        .setLayoutCount = m_plsTextureDescriptorSetLayout != VK_NULL_HANDLE
                              ? BINDINGS_SET_COUNT
                              : BINDINGS_SET_COUNT - 1u,
        .pSetLayouts = pipelineDescriptorSetLayouts,
    };

    VK_CHECK(m_vk->CreatePipelineLayout(m_vk->device,
                                        &pipelineLayoutCreateInfo,
                                        nullptr,
                                        &m_pipelineLayout));
}

DrawPipelineLayoutVulkan::~DrawPipelineLayoutVulkan()
{
    m_vk->DestroyDescriptorSetLayout(m_vk->device,
                                     m_plsTextureDescriptorSetLayout,
                                     nullptr);
    m_vk->DestroyPipelineLayout(m_vk->device, m_pipelineLayout, nullptr);
}

uint32_t DrawPipelineLayoutVulkan::colorAttachmentCount(
    uint32_t subpassIndex,
    RenderPassOptionsVulkan renderPassOptions) const
{
    switch (m_interlockMode)
    {
        case gpu::InterlockMode::rasterOrdering:
            assert(subpassIndex == 0 || subpassIndex == 1);
            return (subpassIndex == 0) ? 4u : 1u;
        case gpu::InterlockMode::atomics:
            assert(subpassIndex <= 1);
            return 2u - subpassIndex; // Subpass 0 -> 2, subpass 1 -> 1.
        case gpu::InterlockMode::clockwise:
            assert(subpassIndex == 0);
            return (renderPassOptions &
                    RenderPassOptionsVulkan::fixedFunctionColorOutput)
                       ? 1u
                       : 0u;
        case gpu::InterlockMode::clockwiseAtomic:
            assert(subpassIndex == 0);
            return 1u;
        case gpu::InterlockMode::msaa:
            assert(0 <= subpassIndex && subpassIndex <= 2);
            return 1u;
    }
    RIVE_UNREACHABLE();
}
} // namespace rive::gpu
