/*
 * Copyright (c) 2020-2026 The Khronos Group Inc.
 * Copyright (c) 2020-2026 Valve Corporation
 * Copyright (c) 2020-2026 LunarG, Inc.
 * Copyright (c) 2020-2026 Google, Inc.
 *
 * 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
 */

#include <vulkan/vulkan_core.h>
#include "layer_validation_tests.h"
#include "pipeline_helper.h"
#include "buffer_helper.h"
#include "shader_helper.h"

class PositiveGpuAVVertexAttributeFetch : public GpuAVTest {};

TEST_F(PositiveGpuAVVertexAttributeFetch, InstanceRateZeroDivisor) {
    TEST_DESCRIPTION("A zero vertex attribute divisor always fetches element zero and must not be treated as OOB.");
    AddRequiredExtensions(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
    AddRequiredFeature(vkt::Feature::vertexAttributeInstanceRateDivisor);
    AddRequiredFeature(vkt::Feature::vertexAttributeInstanceRateZeroDivisor);
    RETURN_IF_SKIP(InitGpuAvFramework());
    RETURN_IF_SKIP(InitState());
    InitRenderTarget();

    const char* vs_source = R"glsl(
        #version 450
        layout(location = 0) in vec2 pos;
        layout(location = 1) in float instance_value;
        void main() {
            gl_Position = vec4(pos, instance_value, 1.0);
        }
    )glsl";
    VkShaderObj vs(*m_device, vs_source, VK_SHADER_STAGE_VERTEX_BIT);

    VkVertexInputBindingDescription input_bindings[2];
    input_bindings[0].binding = 0u;
    input_bindings[0].stride = 8u;
    input_bindings[0].inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
    input_bindings[1].binding = 1u;
    input_bindings[1].stride = 4u;
    input_bindings[1].inputRate = VK_VERTEX_INPUT_RATE_INSTANCE;

    VkVertexInputAttributeDescription input_attributes[2];
    input_attributes[0].location = 0u;
    input_attributes[0].binding = 0u;
    input_attributes[0].format = VK_FORMAT_R32G32_SFLOAT;
    input_attributes[0].offset = 0u;
    input_attributes[1].location = 1u;
    input_attributes[1].binding = 1u;
    input_attributes[1].format = VK_FORMAT_R32_SFLOAT;
    input_attributes[1].offset = 0u;

    VkVertexInputBindingDivisorDescription input_divisor;
    input_divisor.binding = 1u;
    input_divisor.divisor = 0u;

    VkPipelineVertexInputDivisorStateCreateInfo input_divisor_state = vku::InitStructHelper();
    input_divisor_state.vertexBindingDivisorCount = 1u;
    input_divisor_state.pVertexBindingDivisors = &input_divisor;

    VkPipelineVertexInputStateCreateInfo input_state = vku::InitStructHelper(&input_divisor_state);
    input_state.vertexBindingDescriptionCount = 2u;
    input_state.pVertexBindingDescriptions = input_bindings;
    input_state.vertexAttributeDescriptionCount = 2u;
    input_state.pVertexAttributeDescriptions = input_attributes;

    CreatePipelineHelper pipe(*this);
    pipe.vi_ci_ = input_state;
    pipe.shader_stages_ = {vs.GetStageCreateInfo(), pipe.fs_->GetStageCreateInfo()};
    pipe.CreateGraphicsPipeline();

    vkt::Buffer vertex_buffer = vkt::VertexBuffer<float>(*m_device, {0.0f, 0.0f, 0.5f, 1.0f, 1.0f, 0.5f});
    vkt::Buffer instance_buffer = vkt::VertexBuffer<float>(*m_device, {0.0f});
    vkt::Buffer index_buffer = vkt::IndexBuffer<uint32_t>(*m_device, {0u, 1u, 2u});

    const VkBuffer vertex_buffers[2] = {vertex_buffer, instance_buffer};
    const VkDeviceSize vertex_buffer_offsets[2] = {0u, 0u};

    m_command_buffer.Begin();
    m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
    vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
    vk::CmdBindIndexBuffer(m_command_buffer, index_buffer, 0u, VK_INDEX_TYPE_UINT32);
    vk::CmdBindVertexBuffers(m_command_buffer, 0u, 2u, vertex_buffers, vertex_buffer_offsets);
    vk::CmdDrawIndexed(m_command_buffer, 3u, 1u, 0u, 0, 0u);
    m_command_buffer.EndRenderPass();
    m_command_buffer.End();

    m_default_queue->SubmitAndWait(m_command_buffer);
}

TEST_F(PositiveGpuAVVertexAttributeFetch, IndirectDrawZeroStride) {
    TEST_DESCRIPTION("Validation must take into account a VkVertexInputBindingDescription::stride of 0");
    RETURN_IF_SKIP(InitGpuAvFramework());
    RETURN_IF_SKIP(InitState());
    InitRenderTarget();

    const char* vsSource = R"glsl(
        #version 450
        layout(location=0) in vec3 pos;
        void main() {
            gl_Position = vec4(pos, 1.0);
        }
        )glsl";
    VkShaderObj vs(*m_device, vsSource, VK_SHADER_STAGE_VERTEX_BIT);

    CreatePipelineHelper pipe(*this);
    VkVertexInputBindingDescription input_binding = {0, 0, VK_VERTEX_INPUT_RATE_VERTEX};
    VkVertexInputAttributeDescription input_attrib = {0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0};
    pipe.vi_ci_.pVertexBindingDescriptions = &input_binding;
    pipe.vi_ci_.vertexBindingDescriptionCount = 1;
    pipe.vi_ci_.pVertexAttributeDescriptions = &input_attrib;
    pipe.vi_ci_.vertexAttributeDescriptionCount = 1;
    pipe.shader_stages_ = {vs.GetStageCreateInfo(), pipe.fs_->GetStageCreateInfo()};
    pipe.CreateGraphicsPipeline();

    VkDrawIndexedIndirectCommand draw_params{};
    draw_params.indexCount = 3;
    draw_params.instanceCount = 1;
    draw_params.firstIndex = 0;
    draw_params.vertexOffset = 0;
    draw_params.firstInstance = 0;
    vkt::Buffer draw_params_buffer = vkt::IndirectBuffer<VkDrawIndexedIndirectCommand>(*m_device, {draw_params});

    VkCommandBufferBeginInfo begin_info = vku::InitStructHelper();
    m_command_buffer.Begin(&begin_info);
    m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
    vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);

    vkt::Buffer index_buffer = vkt::IndexBuffer<uint32_t>(*m_device, {0, 666, 42});
    vkt::Buffer vertex_buffer = vkt::VertexBuffer<float>(*m_device, {1.0f, 1.0f, 1.0f});
    VkDeviceSize vertex_buffer_offset = 0;
    vk::CmdBindIndexBuffer(m_command_buffer, index_buffer, 0, VK_INDEX_TYPE_UINT32);
    vk::CmdBindVertexBuffers(m_command_buffer, 0, 1, &vertex_buffer.handle(), &vertex_buffer_offset);

    vk::CmdDrawIndexedIndirect(m_command_buffer, draw_params_buffer, 0, 1, sizeof(VkDrawIndexedIndirectCommand));

    m_command_buffer.EndRenderPass();
    m_command_buffer.End();

    m_default_queue->SubmitAndWait(m_command_buffer);
    m_errorMonitor->VerifyFound();
}

TEST_F(PositiveGpuAVVertexAttributeFetch, NoVertexInput) {
    TEST_DESCRIPTION("https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11475");
    RETURN_IF_SKIP(InitGpuAvFramework());
    RETURN_IF_SKIP(InitState());
    InitRenderTarget();

    const char* vs_source = R"glsl(
        #version 450
        layout (location = 0) out vec3 outEyespacePosition;
        layout (location = 1) out vec2 outTexcoords;

        void main() {
            outEyespacePosition = vec3(0);
            outTexcoords = vec2(0);
        }
    )glsl";
    VkShaderObj vs(*m_device, vs_source, VK_SHADER_STAGE_VERTEX_BIT);

    const char* fs_source = R"glsl(
        #version 450
        layout (location = 0) in vec3 inEyeSpacePosition;
        layout (location = 0) out vec4 outColour;

        void main() {
            outColour = vec4(0);
        }
    )glsl";
    VkShaderObj fs(*m_device, fs_source, VK_SHADER_STAGE_FRAGMENT_BIT);

    CreatePipelineHelper pipe(*this);
    VkVertexInputBindingDescription input_binding = {0, 0, VK_VERTEX_INPUT_RATE_VERTEX};
    VkVertexInputAttributeDescription input_attrib = {1, 0, VK_FORMAT_A2B10G10R10_UNORM_PACK32, 0};
    pipe.vi_ci_.pVertexBindingDescriptions = &input_binding;
    pipe.vi_ci_.vertexBindingDescriptionCount = 1;
    pipe.vi_ci_.pVertexAttributeDescriptions = &input_attrib;
    pipe.vi_ci_.vertexAttributeDescriptionCount = 1;
    pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
    pipe.CreateGraphicsPipeline();

    VkCommandBufferBeginInfo begin_info = vku::InitStructHelper();
    m_command_buffer.Begin(&begin_info);
    m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
    vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);

    vkt::Buffer index_buffer = vkt::IndexBuffer<uint32_t>(*m_device, {0, 666, 42});
    vkt::Buffer vertex_buffer = vkt::VertexBuffer<float>(*m_device, {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f});
    VkDeviceSize vertex_buffer_offset = 0;
    vk::CmdBindIndexBuffer(m_command_buffer, index_buffer, 0, VK_INDEX_TYPE_UINT32);
    vk::CmdBindVertexBuffers(m_command_buffer, 0, 1, &vertex_buffer.handle(), &vertex_buffer_offset);

    vk::CmdDrawIndexed(m_command_buffer, 3, 1, 0, 0, 0);

    m_command_buffer.EndRenderPass();
    m_command_buffer.End();

    m_default_queue->SubmitAndWait(m_command_buffer);
    m_errorMonitor->VerifyFound();
}

TEST_F(PositiveGpuAVVertexAttributeFetch, NoVertexInputWithBuiltin) {
    TEST_DESCRIPTION("https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11475");
    SetTargetApiVersion(VK_API_VERSION_1_2);
    AddRequiredFeature(vkt::Feature::shaderDrawParameters);
    RETURN_IF_SKIP(InitGpuAvFramework());
    RETURN_IF_SKIP(InitState());
    InitRenderTarget();

    // Declares the variable for builtin [VertexIndex, InstanceIndex, BaseVertex] but never accesses them
    const char* vs_source = R"(
               OpCapability DrawParameters
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Vertex %main "main" %entryPointParam_main_outEyespacePosition %entryPointParam_main_outTexcoords
               OpDecorate %entryPointParam_main_outEyespacePosition Location 0
               OpDecorate %entryPointParam_main_outTexcoords Location 1
               OpDecorate %gl_Position BuiltIn Position
               OpDecorate %gl_VertexIndex BuiltIn VertexIndex
               OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
               OpDecorate %15 BuiltIn BaseVertex
      %float = OpTypeFloat 32
    %v3float = OpTypeVector %float 3
%_ptr_Output_v3float = OpTypePointer Output %v3float
    %v2float = OpTypeVector %float 2
%_ptr_Output_v2float = OpTypePointer Output %v2float
    %v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
        %int = OpTypeInt 32 1
%_ptr_Input_int = OpTypePointer Input %int
       %void = OpTypeVoid
         %18 = OpTypeFunction %void
    %float_0 = OpConstant %float 0
         %20 = OpConstantComposite %v3float %float_0 %float_0 %float_0
         %23 = OpConstantComposite %v2float %float_0 %float_0
%entryPointParam_main_outEyespacePosition = OpVariable %_ptr_Output_v3float Output
%entryPointParam_main_outTexcoords = OpVariable %_ptr_Output_v2float Output
%gl_Position = OpVariable %_ptr_Output_v4float Output
%gl_VertexIndex = OpVariable %_ptr_Input_int Input
%gl_InstanceIndex = OpVariable %_ptr_Input_int Input
         %15 = OpVariable %_ptr_Input_int Input
       %main = OpFunction %void None %18
         %19 = OpLabel
               OpStore %entryPointParam_main_outEyespacePosition %20
               OpStore %entryPointParam_main_outTexcoords %23
               OpReturn
               OpFunctionEnd
    )";
    VkShaderObj vs(*m_device, vs_source, VK_SHADER_STAGE_VERTEX_BIT, SPV_ENV_VULKAN_1_2, SPV_SOURCE_ASM);

    const char* fs_source = R"glsl(
        #version 450
        layout (location = 0) in vec3 inEyeSpacePosition;
        layout (location = 0) out vec4 outColour;

        void main() {
            outColour = vec4(0);
        }
    )glsl";
    VkShaderObj fs(*m_device, fs_source, VK_SHADER_STAGE_FRAGMENT_BIT, SPV_ENV_VULKAN_1_2);

    CreatePipelineHelper pipe(*this);
    VkVertexInputBindingDescription input_binding = {0, 0, VK_VERTEX_INPUT_RATE_VERTEX};
    VkVertexInputAttributeDescription input_attrib = {1, 0, VK_FORMAT_A2B10G10R10_UNORM_PACK32, 0};
    pipe.vi_ci_.pVertexBindingDescriptions = &input_binding;
    pipe.vi_ci_.vertexBindingDescriptionCount = 1;
    pipe.vi_ci_.pVertexAttributeDescriptions = &input_attrib;
    pipe.vi_ci_.vertexAttributeDescriptionCount = 1;
    pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
    pipe.CreateGraphicsPipeline();

    VkCommandBufferBeginInfo begin_info = vku::InitStructHelper();
    m_command_buffer.Begin(&begin_info);
    m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
    vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);

    vkt::Buffer index_buffer = vkt::IndexBuffer<uint32_t>(*m_device, {0, 666, 42});
    vkt::Buffer vertex_buffer = vkt::VertexBuffer<float>(*m_device, {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f});
    VkDeviceSize vertex_buffer_offset = 0;
    vk::CmdBindIndexBuffer(m_command_buffer, index_buffer, 0, VK_INDEX_TYPE_UINT32);
    vk::CmdBindVertexBuffers(m_command_buffer, 0, 1, &vertex_buffer.handle(), &vertex_buffer_offset);

    vk::CmdDrawIndexed(m_command_buffer, 3, 1, 0, 0, 0);

    m_command_buffer.EndRenderPass();
    m_command_buffer.End();

    m_default_queue->SubmitAndWait(m_command_buffer);
    m_errorMonitor->VerifyFound();
}
