// Copyright 2018 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "src/dawn/native/vulkan/RenderPipelineVk.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "src/dawn/native/CreatePipelineAsyncEvent.h"
#include "src/dawn/native/vulkan/DeviceVk.h"
#include "src/dawn/native/vulkan/FencedDeleter.h"
#include "src/dawn/native/vulkan/ImmediatesLayoutVk.h"
#include "src/dawn/native/vulkan/PipelineCacheVk.h"
#include "src/dawn/native/vulkan/PipelineLayoutVk.h"
#include "src/dawn/native/vulkan/RenderPassCache.h"
#include "src/dawn/native/vulkan/ShaderModuleVk.h"
#include "src/dawn/native/vulkan/TextureVk.h"
#include "src/dawn/native/vulkan/UtilsVulkan.h"
#include "src/dawn/native/vulkan/VulkanError.h"
#include "src/dawn/platform/metrics/HistogramMacros.h"

namespace dawn::native::vulkan {

namespace {

VkVertexInputRate VulkanInputRate(wgpu::VertexStepMode stepMode) {
    switch (stepMode) {
        case wgpu::VertexStepMode::Vertex:
            return VK_VERTEX_INPUT_RATE_VERTEX;
        case wgpu::VertexStepMode::Instance:
            return VK_VERTEX_INPUT_RATE_INSTANCE;
        case wgpu::VertexStepMode::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

VkFormat VulkanVertexFormat(wgpu::VertexFormat format) {
    switch (format) {
        case wgpu::VertexFormat::Uint8:
            return VK_FORMAT_R8_UINT;
        case wgpu::VertexFormat::Uint8x2:
            return VK_FORMAT_R8G8_UINT;
        case wgpu::VertexFormat::Uint8x4:
            return VK_FORMAT_R8G8B8A8_UINT;
        case wgpu::VertexFormat::Sint8:
            return VK_FORMAT_R8_SINT;
        case wgpu::VertexFormat::Sint8x2:
            return VK_FORMAT_R8G8_SINT;
        case wgpu::VertexFormat::Sint8x4:
            return VK_FORMAT_R8G8B8A8_SINT;
        case wgpu::VertexFormat::Unorm8:
            return VK_FORMAT_R8_UNORM;
        case wgpu::VertexFormat::Unorm8x2:
            return VK_FORMAT_R8G8_UNORM;
        case wgpu::VertexFormat::Unorm8x4:
            return VK_FORMAT_R8G8B8A8_UNORM;
        case wgpu::VertexFormat::Snorm8:
            return VK_FORMAT_R8_SNORM;
        case wgpu::VertexFormat::Snorm8x2:
            return VK_FORMAT_R8G8_SNORM;
        case wgpu::VertexFormat::Snorm8x4:
            return VK_FORMAT_R8G8B8A8_SNORM;
        case wgpu::VertexFormat::Uint16:
            return VK_FORMAT_R16_UINT;
        case wgpu::VertexFormat::Uint16x2:
            return VK_FORMAT_R16G16_UINT;
        case wgpu::VertexFormat::Uint16x4:
            return VK_FORMAT_R16G16B16A16_UINT;
        case wgpu::VertexFormat::Sint16:
            return VK_FORMAT_R16_SINT;
        case wgpu::VertexFormat::Sint16x2:
            return VK_FORMAT_R16G16_SINT;
        case wgpu::VertexFormat::Sint16x4:
            return VK_FORMAT_R16G16B16A16_SINT;
        case wgpu::VertexFormat::Unorm16:
            return VK_FORMAT_R16_UNORM;
        case wgpu::VertexFormat::Unorm16x2:
            return VK_FORMAT_R16G16_UNORM;
        case wgpu::VertexFormat::Unorm16x4:
            return VK_FORMAT_R16G16B16A16_UNORM;
        case wgpu::VertexFormat::Snorm16:
            return VK_FORMAT_R16_SNORM;
        case wgpu::VertexFormat::Snorm16x2:
            return VK_FORMAT_R16G16_SNORM;
        case wgpu::VertexFormat::Snorm16x4:
            return VK_FORMAT_R16G16B16A16_SNORM;
        case wgpu::VertexFormat::Float16:
            return VK_FORMAT_R16_SFLOAT;
        case wgpu::VertexFormat::Float16x2:
            return VK_FORMAT_R16G16_SFLOAT;
        case wgpu::VertexFormat::Float16x4:
            return VK_FORMAT_R16G16B16A16_SFLOAT;
        case wgpu::VertexFormat::Float32:
            return VK_FORMAT_R32_SFLOAT;
        case wgpu::VertexFormat::Float32x2:
            return VK_FORMAT_R32G32_SFLOAT;
        case wgpu::VertexFormat::Float32x3:
            return VK_FORMAT_R32G32B32_SFLOAT;
        case wgpu::VertexFormat::Float32x4:
            return VK_FORMAT_R32G32B32A32_SFLOAT;
        case wgpu::VertexFormat::Uint32:
            return VK_FORMAT_R32_UINT;
        case wgpu::VertexFormat::Uint32x2:
            return VK_FORMAT_R32G32_UINT;
        case wgpu::VertexFormat::Uint32x3:
            return VK_FORMAT_R32G32B32_UINT;
        case wgpu::VertexFormat::Uint32x4:
            return VK_FORMAT_R32G32B32A32_UINT;
        case wgpu::VertexFormat::Sint32:
            return VK_FORMAT_R32_SINT;
        case wgpu::VertexFormat::Sint32x2:
            return VK_FORMAT_R32G32_SINT;
        case wgpu::VertexFormat::Sint32x3:
            return VK_FORMAT_R32G32B32_SINT;
        case wgpu::VertexFormat::Sint32x4:
            return VK_FORMAT_R32G32B32A32_SINT;
        case wgpu::VertexFormat::Unorm10_10_10_2:
            return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
        case wgpu::VertexFormat::Unorm8x4BGRA:
            return VK_FORMAT_B8G8R8A8_UNORM;
        default:
            DAWN_UNREACHABLE();
    }
}

VkPrimitiveTopology VulkanPrimitiveTopology(wgpu::PrimitiveTopology topology) {
    switch (topology) {
        case wgpu::PrimitiveTopology::PointList:
            return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
        case wgpu::PrimitiveTopology::LineList:
            return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
        case wgpu::PrimitiveTopology::LineStrip:
            return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
        case wgpu::PrimitiveTopology::TriangleList:
            return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
        case wgpu::PrimitiveTopology::TriangleStrip:
            return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
        case wgpu::PrimitiveTopology::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

bool ShouldEnablePrimitiveRestart(wgpu::PrimitiveTopology topology) {
    // Primitive restart is always enabled in WebGPU but Vulkan validation rules ask that
    // primitive restart be only enabled on primitive topologies that support restarting.
    switch (topology) {
        case wgpu::PrimitiveTopology::PointList:
        case wgpu::PrimitiveTopology::LineList:
        case wgpu::PrimitiveTopology::TriangleList:
            return false;
        case wgpu::PrimitiveTopology::LineStrip:
        case wgpu::PrimitiveTopology::TriangleStrip:
            return true;
        case wgpu::PrimitiveTopology::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

VkFrontFace VulkanFrontFace(wgpu::FrontFace face) {
    switch (face) {
        case wgpu::FrontFace::CCW:
            return VK_FRONT_FACE_COUNTER_CLOCKWISE;
        case wgpu::FrontFace::CW:
            return VK_FRONT_FACE_CLOCKWISE;
        case wgpu::FrontFace::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

VkCullModeFlagBits VulkanCullMode(wgpu::CullMode mode) {
    switch (mode) {
        case wgpu::CullMode::None:
            return VK_CULL_MODE_NONE;
        case wgpu::CullMode::Front:
            return VK_CULL_MODE_FRONT_BIT;
        case wgpu::CullMode::Back:
            return VK_CULL_MODE_BACK_BIT;
        case wgpu::CullMode::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

VkBlendFactor VulkanBlendFactor(wgpu::BlendFactor factor) {
    switch (factor) {
        case wgpu::BlendFactor::Zero:
            return VK_BLEND_FACTOR_ZERO;
        case wgpu::BlendFactor::One:
            return VK_BLEND_FACTOR_ONE;
        case wgpu::BlendFactor::Src:
            return VK_BLEND_FACTOR_SRC_COLOR;
        case wgpu::BlendFactor::OneMinusSrc:
            return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
        case wgpu::BlendFactor::SrcAlpha:
            return VK_BLEND_FACTOR_SRC_ALPHA;
        case wgpu::BlendFactor::OneMinusSrcAlpha:
            return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
        case wgpu::BlendFactor::Dst:
            return VK_BLEND_FACTOR_DST_COLOR;
        case wgpu::BlendFactor::OneMinusDst:
            return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
        case wgpu::BlendFactor::DstAlpha:
            return VK_BLEND_FACTOR_DST_ALPHA;
        case wgpu::BlendFactor::OneMinusDstAlpha:
            return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
        case wgpu::BlendFactor::SrcAlphaSaturated:
            return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE;
        case wgpu::BlendFactor::Constant:
            return VK_BLEND_FACTOR_CONSTANT_COLOR;
        case wgpu::BlendFactor::OneMinusConstant:
            return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR;
        case wgpu::BlendFactor::Src1:
            return VK_BLEND_FACTOR_SRC1_COLOR;
        case wgpu::BlendFactor::OneMinusSrc1:
            return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR;
        case wgpu::BlendFactor::Src1Alpha:
            return VK_BLEND_FACTOR_SRC1_ALPHA;
        case wgpu::BlendFactor::OneMinusSrc1Alpha:
            return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA;
        case wgpu::BlendFactor::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

VkBlendOp VulkanBlendOperation(wgpu::BlendOperation operation) {
    switch (operation) {
        case wgpu::BlendOperation::Add:
            return VK_BLEND_OP_ADD;
        case wgpu::BlendOperation::Subtract:
            return VK_BLEND_OP_SUBTRACT;
        case wgpu::BlendOperation::ReverseSubtract:
            return VK_BLEND_OP_REVERSE_SUBTRACT;
        case wgpu::BlendOperation::Min:
            return VK_BLEND_OP_MIN;
        case wgpu::BlendOperation::Max:
            return VK_BLEND_OP_MAX;
        case wgpu::BlendOperation::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

VkColorComponentFlags VulkanColorWriteMask(wgpu::ColorWriteMask mask,
                                           bool isDeclaredInFragmentShader) {
    // Vulkan and Dawn color write masks match, static assert it and return the mask
    static_assert(static_cast<VkColorComponentFlagBits>(wgpu::ColorWriteMask::Red) ==
                  VK_COLOR_COMPONENT_R_BIT);
    static_assert(static_cast<VkColorComponentFlagBits>(wgpu::ColorWriteMask::Green) ==
                  VK_COLOR_COMPONENT_G_BIT);
    static_assert(static_cast<VkColorComponentFlagBits>(wgpu::ColorWriteMask::Blue) ==
                  VK_COLOR_COMPONENT_B_BIT);
    static_assert(static_cast<VkColorComponentFlagBits>(wgpu::ColorWriteMask::Alpha) ==
                  VK_COLOR_COMPONENT_A_BIT);

    // According to Vulkan SPEC (Chapter 14.3): "The input values to blending or color
    // attachment writes are undefined for components which do not correspond to a fragment
    // shader outputs", we set the color write mask to 0 to prevent such undefined values
    // being written into the color attachments.
    return isDeclaredInFragmentShader ? static_cast<VkColorComponentFlags>(mask)
                                      : static_cast<VkColorComponentFlags>(0);
}

VkPipelineColorBlendAttachmentState ComputeColorDesc(const ColorTargetState* state,
                                                     bool isDeclaredInFragmentShader) {
    VkPipelineColorBlendAttachmentState attachment;
    attachment.blendEnable = state->blend != nullptr ? VK_TRUE : VK_FALSE;
    if (attachment.blendEnable) {
        attachment.srcColorBlendFactor = VulkanBlendFactor(state->blend->color.srcFactor);
        attachment.dstColorBlendFactor = VulkanBlendFactor(state->blend->color.dstFactor);
        attachment.colorBlendOp = VulkanBlendOperation(state->blend->color.operation);
        attachment.srcAlphaBlendFactor = VulkanBlendFactor(state->blend->alpha.srcFactor);
        attachment.dstAlphaBlendFactor = VulkanBlendFactor(state->blend->alpha.dstFactor);
        attachment.alphaBlendOp = VulkanBlendOperation(state->blend->alpha.operation);
    } else {
        // Swiftshader's Vulkan implementation appears to expect these values to be valid
        // even when blending is not enabled.
        attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
        attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
        attachment.colorBlendOp = VK_BLEND_OP_ADD;
        attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
        attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
        attachment.alphaBlendOp = VK_BLEND_OP_ADD;
    }
    attachment.colorWriteMask = VulkanColorWriteMask(state->writeMask, isDeclaredInFragmentShader);
    return attachment;
}

VkStencilOp VulkanStencilOp(wgpu::StencilOperation op) {
    switch (op) {
        case wgpu::StencilOperation::Keep:
            return VK_STENCIL_OP_KEEP;
        case wgpu::StencilOperation::Zero:
            return VK_STENCIL_OP_ZERO;
        case wgpu::StencilOperation::Replace:
            return VK_STENCIL_OP_REPLACE;
        case wgpu::StencilOperation::IncrementClamp:
            return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
        case wgpu::StencilOperation::DecrementClamp:
            return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
        case wgpu::StencilOperation::Invert:
            return VK_STENCIL_OP_INVERT;
        case wgpu::StencilOperation::IncrementWrap:
            return VK_STENCIL_OP_INCREMENT_AND_WRAP;
        case wgpu::StencilOperation::DecrementWrap:
            return VK_STENCIL_OP_DECREMENT_AND_WRAP;
        case wgpu::StencilOperation::Undefined:
            break;
    }
    DAWN_UNREACHABLE();
}

uint16_t PackStencilOpState(VkStencilOpState state, VkBool32 enabled) {
    // Both VkStencilOp and VkCompareOp have values ranging from 0-7, so they fit in 3 bits.
    // So we can encode the full dynamic stencil state in 12 bits.
    DAWN_ASSERT(static_cast<uint32_t>(state.failOp) < 8);
    DAWN_ASSERT(static_cast<uint32_t>(state.passOp) < 8);
    DAWN_ASSERT(static_cast<uint32_t>(state.depthFailOp) < 8);
    DAWN_ASSERT(static_cast<uint32_t>(state.compareOp) < 8);
    uint16_t packed = state.failOp | state.passOp << 3 | state.depthFailOp << 6 |
                      state.compareOp << 9 |
                      (enabled ? 0x8000 : 0);  // Set high bit if stencil is enabled.
    return packed;
}

VkStencilOpState UnpackStencilOpState(uint16_t packed) {
    VkStencilOpState state = {
        .failOp = static_cast<VkStencilOp>(packed & 0x0007),
        .passOp = static_cast<VkStencilOp>((packed >> 3) & 0x0007),
        .depthFailOp = static_cast<VkStencilOp>((packed >> 6) & 0x0007),
        .compareOp = static_cast<VkCompareOp>((packed >> 9) & 0x0007),
        .compareMask = 0,
        .writeMask = 0,
        .reference = 0,
    };
    return state;
}

VkPrimitiveTopology GetTopologyClass(VkPrimitiveTopology topology) {
    switch (topology) {
        case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
            return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
        case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
        case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
            return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
        case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
        case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
            return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
        default:
            DAWN_UNREACHABLE();
            return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
    }
}

}  // anonymous namespace

// static
Ref<RenderPipeline> RenderPipeline::CreateUninitialized(
    Device* device,
    const UnpackedPtr<RenderPipelineDescriptor>& descriptor) {
    return AcquireRef(new RenderPipeline(device, descriptor));
}

MaybeError RenderPipeline::InitializeImpl() {
    // Gather list of internal immediates used by this pipeline
    if ((NeedsPixelCenterPolyfill() || UsesFragDepth()) && !HasUnclippedDepth()) {
        mImmediateMask |= GetImmediateBlockBits(offsetof(RenderImmediates, clampFragDepth),
                                                sizeof(ClampFragDepthArgs));
    }

    if (GetDevice()->NeedsStaticSamplerForExternalTexture() && GetLayout()->HasExternalTextures()) {
        DAWN_ASSERT(!GetLayout()->HasAPIStaticSamplers());
        mRequiresSpecialization = true;
    }

    // The cache key is only used for storing VkPipelineCache objects in BlobStore. That's not
    // done with the monolithic pipeline cache so it's unnecessary work and memory usage.
    bool buildCacheKey =
        !GetDevice()->GetTogglesState().IsEnabled(Toggle::VulkanMonolithicPipelineCache);

    Specialization specialization = {.layout = {
                                         .pushConstantBytes = ToPushConstantBytes(mImmediateMask),
                                     }};
    if (UsesFramebufferFetch()) {
        specialization.layout.framebufferFetchAttachmentCount =
            AttachmentCount(GetColorAttachmentsMask());
    }

    SpecializationResult r;
    DAWN_TRY_ASSIGN(r, InitializeSpecialization(specialization, buildCacheKey));
    mHandles = {.pipeline = r.pipeline->Get(), .layout = r.layout->Get()};

    mSpecializations->emplace(std::move(specialization), std::move(r));

    return {};
}

ResultOrError<PipelineHandles> RenderPipeline::GetOrCreateSpecializedHandle(
    Specialization&& specializationIn) {
    Specialization specialization = specializationIn;
    specialization.layout.pushConstantBytes = ToPushConstantBytes(mImmediateMask);
    if (UsesFramebufferFetch()) {
        specialization.layout.framebufferFetchAttachmentCount =
            AttachmentCount(GetColorAttachmentsMask());
    }

    if (auto specialized =
            mSpecializations.ConstUse([&](auto specializations) -> std::optional<PipelineHandles> {
                if (auto it = specializations->find(specialization); it != specializations->end()) {
                    return PipelineHandles{.pipeline = it->second.pipeline->Get(),
                                           .layout = it->second.layout->Get()};
                }
                return std::nullopt;
            });
        specialized) {
        return *specialized;
    }

    // Do no make a new cache key, so that the VkPipelineCache from InitializeImpl is used for all
    // specializations.
    SpecializationResult r;
    DAWN_TRY_ASSIGN(r, InitializeSpecialization(specialization, /*buildCacheKey=*/false));

    return mSpecializations.Use([&](auto specializations) -> ResultOrError<PipelineHandles> {
        auto handles = PipelineHandles{.pipeline = r.pipeline->Get(), .layout = r.layout->Get()};

        auto [it, inserted] = specializations->insert({specialization, r});
        if (!inserted) {
            return PipelineHandles{.pipeline = it->second.pipeline->Get(),
                                   .layout = it->second.layout->Get()};
        }
        return handles;
    });
}

ResultOrError<RenderPipeline::SpecializationResult> RenderPipeline::InitializeSpecialization(
    const Specialization& specialization,
    bool buildCacheKey) {
    Device* device = ToBackend(GetDevice());
    PipelineLayout* layout = ToBackend(GetLayout());

    if (buildCacheKey) {
        // Vulkan devices need cache UUID field to be serialized into pipeline cache keys.
        StreamIn(&mCacheKey, device->GetDeviceInfo().properties.pipelineCacheUUID);
    }

    SpecializationResult result;
    DAWN_TRY_ASSIGN(result.layout,
                    layout->GetOrCreateVkLayoutObject(std::move(specialization.layout)));

    // There are at most 2 shader stages in render pipeline, i.e. vertex and fragment
    std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
    uint32_t stageCount = 0;

    auto AddShaderStage = [&](const ShaderModule::CompileParameters& compileParams) -> MaybeError {
        ShaderModule::ModuleAndSpirv moduleAndSpirv;
        DAWN_TRY_ASSIGN(moduleAndSpirv,
                        ToBackend(compileParams.stage->module)->GetHandleAndSpirv(compileParams));
        mHasInputAttachment = mHasInputAttachment || moduleAndSpirv.hasInputAttachment;
        if (buildCacheKey) {
            // Record cache key for each shader since it will become inaccessible later on.
            StreamIn(&mCacheKey, moduleAndSpirv.spirv);
        }

        VkPipelineShaderStageCreateInfo* shaderStage = &shaderStages[stageCount];
        shaderStage->module = moduleAndSpirv.module;
        shaderStage->sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
        shaderStage->pNext = nullptr;
        shaderStage->flags = 0;
        shaderStage->pSpecializationInfo = nullptr;
        shaderStage->stage = VulkanShaderStage(compileParams.stage->metadata->stage);
        // string_view returned by GetIsolatedEntryPointName() points to a null-terminated string.
        shaderStage->pName = device->GetIsolatedEntryPointName().data();

        stageCount++;
        return {};
    };

    // Add the vertex stage that's always present.
    DAWN_TRY(AddShaderStage({
        .stage = &GetStage(SingleShaderStage::Vertex),
        .layout = layout,
        .immediateMask = GetImmediateMask(),
        .ycbcrExternalTextures = &specialization.ycbcrExternalTextures,
        .emitPointSize = GetPrimitiveTopology() == wgpu::PrimitiveTopology::PointList,
        .polyfillPixelCenter = NeedsPixelCenterPolyfill(),
        .pipelineUsesFramebufferFetch = UsesFramebufferFetch(),
    }));

    // Add the fragment stage if present.
    if (GetStageMask() & wgpu::ShaderStage::Fragment) {
        DAWN_TRY(AddShaderStage({
            .stage = &GetStage(SingleShaderStage::Fragment),
            .layout = layout,
            .immediateMask = GetImmediateMask(),
            .ycbcrExternalTextures = &specialization.ycbcrExternalTextures,
            .polyfillPixelCenter = NeedsPixelCenterPolyfill(),
            .pipelineUsesFramebufferFetch = UsesFramebufferFetch(),
            .needsMultisampledFramebufferFetch = UseSampleRateShading() && UsesFramebufferFetch(),
        }));
    }

    PipelineVertexInputStateCreateInfoTemporaryAllocations tempAllocations{};
    VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo =
        ComputeVertexInputDesc(&tempAllocations);

    VkPipelineInputAssemblyStateCreateInfo inputAssembly;
    inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
    inputAssembly.pNext = nullptr;
    inputAssembly.flags = 0;
    inputAssembly.topology = VulkanPrimitiveTopology(GetPrimitiveTopology());
    inputAssembly.primitiveRestartEnable =
        ShouldEnablePrimitiveRestart(GetPrimitiveTopology()) ? VK_TRUE : VK_FALSE;

    // A placeholder viewport/scissor info. The validation layers force us to provide at least
    // one scissor and one viewport here, even if we choose to make them dynamic.
    VkViewport viewportDesc;
    viewportDesc.x = 0.0f;
    viewportDesc.y = 0.0f;
    viewportDesc.width = 1.0f;
    viewportDesc.height = 1.0f;
    viewportDesc.minDepth = 0.0f;
    viewportDesc.maxDepth = 1.0f;
    VkRect2D scissorRect;
    scissorRect.offset.x = 0;
    scissorRect.offset.y = 0;
    scissorRect.extent.width = 1;
    scissorRect.extent.height = 1;
    VkPipelineViewportStateCreateInfo viewport;
    viewport.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
    viewport.pNext = nullptr;
    viewport.flags = 0;
    viewport.viewportCount = 1;
    viewport.pViewports = &viewportDesc;
    viewport.scissorCount = 1;
    viewport.pScissors = &scissorRect;

    VkPipelineRasterizationStateCreateInfo rasterization;
    rasterization.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
    rasterization.pNext = nullptr;
    rasterization.flags = 0;
    rasterization.depthClampEnable = HasUnclippedDepth() ? VK_TRUE : VK_FALSE;
    rasterization.rasterizerDiscardEnable = VK_FALSE;
    rasterization.polygonMode = VK_POLYGON_MODE_FILL;
    rasterization.cullMode = VulkanCullMode(GetCullMode());
    rasterization.frontFace = VulkanFrontFace(GetFrontFace());
    rasterization.depthBiasEnable = IsDepthBiasEnabled() ? VK_TRUE : VK_FALSE;
    rasterization.depthBiasConstantFactor = GetDepthBias();
    rasterization.depthBiasClamp = GetDepthBiasClamp();
    rasterization.depthBiasSlopeFactor = GetDepthBiasSlopeScale();
    rasterization.lineWidth = 1.0f;

    VkPipelineMultisampleStateCreateInfo multisample;
    multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
    multisample.pNext = nullptr;
    multisample.flags = 0;
    multisample.rasterizationSamples = VulkanSampleCount(GetSampleCount());
    multisample.sampleShadingEnable = UsesFramebufferFetch() ? VK_TRUE : VK_FALSE;
    multisample.minSampleShading = 1.0f;
    // VkPipelineMultisampleStateCreateInfo.pSampleMask is an array of length
    // ceil(rasterizationSamples / 32) and since we're passing a single uint32_t
    // we have to assert that this length is indeed 1.
    DAWN_ASSERT(multisample.rasterizationSamples <= 32);
    VkSampleMask sampleMask = GetSampleMask();
    multisample.pSampleMask = &sampleMask;
    multisample.alphaToCoverageEnable = IsAlphaToCoverageEnabled() ? VK_TRUE : VK_FALSE;
    multisample.alphaToOneEnable = VK_FALSE;

    VkPipelineDepthStencilStateCreateInfo depthStencilState = ComputeDepthStencilDesc();

    VkPipelineColorBlendStateCreateInfo colorBlend;
    // colorBlend may hold pointers to elements in colorBlendAttachments, so it must have a
    // definition scope as same as colorBlend
    PerColorAttachment<VkPipelineColorBlendAttachmentState> colorBlendAttachments;
    if (GetStageMask() & wgpu::ShaderStage::Fragment) {
        // Initialize the "blend state info" that will be chained in the "create info" from the
        // data pre-computed in the ColorState
        for (auto& blend : colorBlendAttachments) {
            blend.blendEnable = VK_FALSE;
            blend.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
            blend.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
            blend.colorBlendOp = VK_BLEND_OP_ADD;
            blend.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
            blend.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
            blend.alphaBlendOp = VK_BLEND_OP_ADD;
            blend.colorWriteMask = 0;
        }

        const auto& fragmentOutputMask =
            GetStage(SingleShaderStage::Fragment).metadata->fragmentOutputMask;
        auto highestColorAttachmentIndexPlusOne =
            GetHighestBitIndexPlusOne(GetColorAttachmentsMask());
        for (auto i : GetColorAttachmentsMask()) {
            const ColorTargetState* target = GetColorTargetState(i);
            colorBlendAttachments[i] = ComputeColorDesc(target, fragmentOutputMask[i]);
        }

        colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
        colorBlend.pNext = nullptr;
        colorBlend.flags = 0;

        if (GetStage(SingleShaderStage::Fragment).metadata->fragmentInputMask.any()) {
            colorBlend.flags |=
                VK_PIPELINE_COLOR_BLEND_STATE_CREATE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_BIT_EXT;
        }

        // LogicOp isn't supported so we disable it.
        colorBlend.logicOpEnable = VK_FALSE;
        colorBlend.logicOp = VK_LOGIC_OP_CLEAR;
        colorBlend.attachmentCount = static_cast<uint8_t>(highestColorAttachmentIndexPlusOne);
        colorBlend.pAttachments = colorBlendAttachments.data();
        // The blend constant is always dynamic so we fill in a placeholder value
        colorBlend.blendConstants[0] = 0.0f;
        colorBlend.blendConstants[1] = 0.0f;
        colorBlend.blendConstants[2] = 0.0f;
        colorBlend.blendConstants[3] = 0.0f;
    }

    // Tag all state as dynamic but stencil masks and depth bias.
    const uint32_t kMaxDynamicStates = 14;
    absl::InlinedVector<VkDynamicState, kMaxDynamicStates> dynamicStates = {
        VK_DYNAMIC_STATE_VIEWPORT,     VK_DYNAMIC_STATE_SCISSOR,
        VK_DYNAMIC_STATE_LINE_WIDTH,   VK_DYNAMIC_STATE_BLEND_CONSTANTS,
        VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_REFERENCE,
    };

    // If ExtendedDynamicState is available tag several other states as dynamic.
    // The states will be ignored by CreateGraphicsPipelines, but they are reset to a constant value
    // here (0 where possible) to ensure the Vulkan cache key is compatible between pipelines.
    if (device->IsToggleEnabled(Toggle::VulkanUseExtendedDynamicState)) {
        dynamicStates.push_back(VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT);
        mDynamicState.primitiveTopology = inputAssembly.topology;
        // TODO(463893795): Look into using dynamicPrimitiveTopologyUnrestricted from
        // VK_EXT_extended_dynamic_state3 to remove the requirement that this needs to be set to the
        // same topology class (point/line/triangle) as the dynamic state it will be used with.
        inputAssembly.topology = GetTopologyClass(inputAssembly.topology);

        dynamicStates.push_back(VK_DYNAMIC_STATE_CULL_MODE_EXT);
        mDynamicState.cullMode = rasterization.cullMode;
        rasterization.cullMode = VK_CULL_MODE_NONE;

        dynamicStates.push_back(VK_DYNAMIC_STATE_FRONT_FACE_EXT);
        mDynamicState.frontFace = rasterization.frontFace;
        rasterization.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;

        dynamicStates.push_back(VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT);
        mDynamicState.depthTestEnable = depthStencilState.depthTestEnable;
        depthStencilState.depthTestEnable = VK_FALSE;

        dynamicStates.push_back(VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT);
        mDynamicState.depthWriteEnable = depthStencilState.depthWriteEnable;
        depthStencilState.depthWriteEnable = VK_FALSE;

        dynamicStates.push_back(VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT);
        mDynamicState.depthCompareOp = depthStencilState.depthCompareOp;
        depthStencilState.depthCompareOp = VK_COMPARE_OP_NEVER;

        dynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT);
        mDynamicState.stencilTestEnable = depthStencilState.stencilTestEnable;
        depthStencilState.stencilTestEnable = VK_FALSE;

        dynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_OP_EXT);
        mDynamicState.packedFrontStencil =
            PackStencilOpState(depthStencilState.front, depthStencilState.stencilTestEnable);
        depthStencilState.front.failOp = VK_STENCIL_OP_KEEP;
        depthStencilState.front.passOp = VK_STENCIL_OP_KEEP;
        depthStencilState.front.depthFailOp = VK_STENCIL_OP_KEEP;
        depthStencilState.front.compareOp = VK_COMPARE_OP_NEVER;

        mDynamicState.packedBackStencil =
            PackStencilOpState(depthStencilState.back, depthStencilState.stencilTestEnable);
        depthStencilState.back.failOp = VK_STENCIL_OP_KEEP;
        depthStencilState.back.passOp = VK_STENCIL_OP_KEEP;
        depthStencilState.back.depthFailOp = VK_STENCIL_OP_KEEP;
        depthStencilState.back.compareOp = VK_COMPARE_OP_NEVER;
    }

    VkPipelineDynamicStateCreateInfo dynamic;
    dynamic.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
    dynamic.pNext = nullptr;
    dynamic.flags = 0;
    dynamic.dynamicStateCount = dynamicStates.size();
    dynamic.pDynamicStates = dynamicStates.data();

    // The create info chains in a bunch of things created on the stack here or inside state
    // objects.
    VkGraphicsPipelineCreateInfo createInfo;
    createInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
    createInfo.pNext = nullptr;
    createInfo.flags = 0;
    createInfo.stageCount = stageCount;
    createInfo.pStages = shaderStages.data();
    createInfo.pVertexInputState = &vertexInputCreateInfo;
    createInfo.pInputAssemblyState = &inputAssembly;
    createInfo.pTessellationState = nullptr;
    createInfo.pViewportState = &viewport;
    createInfo.pRasterizationState = &rasterization;
    createInfo.pMultisampleState = &multisample;
    createInfo.pDepthStencilState = &depthStencilState;
    createInfo.pColorBlendState =
        (GetStageMask() & wgpu::ShaderStage::Fragment) ? &colorBlend : nullptr;
    createInfo.pDynamicState = &dynamic;
    createInfo.layout = result.layout->Get();
    createInfo.basePipelineHandle = VkPipeline{};
    createInfo.basePipelineIndex = -1;
    PNextChainBuilder createInfoChain(&createInfo);

    RenderPassCache::RenderPassInfo renderPassInfo;
    VkPipelineRenderingCreateInfoKHR pipelineRenderingCreateInfo;
    PerColorAttachment<VkFormat> colorAttachmentFormats;

    if (device->GetRenderPassType() == VulkanRenderPassType::DynamicRendering) {
        // Dynamic rendering doesn't need a VkRenderPass object, just a description of the
        // attachments formats that will be used with this pipeline.
        VkRenderPass nullRenderPass = VK_NULL_HANDLE;
        createInfo.renderPass = nullRenderPass;

        createInfoChain.Add(&pipelineRenderingCreateInfo,
                            VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR);

        pipelineRenderingCreateInfo.viewMask = 0;
        pipelineRenderingCreateInfo.colorAttachmentCount = 0;
        pipelineRenderingCreateInfo.depthAttachmentFormat = VK_FORMAT_UNDEFINED;
        pipelineRenderingCreateInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;

        // Initialize all potential color attachment formats to undefined, which allows the
        // attachments to be sparse.
        colorAttachmentFormats.fill(VK_FORMAT_UNDEFINED);

        // Set the formats of the color attachments in use.
        ColorAttachmentMask attachmentMask = GetColorAttachmentsMask();
        for (auto i : attachmentMask) {
            colorAttachmentFormats[i] = VulkanImageFormat(device, GetColorAttachmentFormat(i));
        }

        pipelineRenderingCreateInfo.colorAttachmentCount =
            static_cast<uint32_t>(GetHighestBitIndexPlusOne(attachmentMask));
        pipelineRenderingCreateInfo.pColorAttachmentFormats = colorAttachmentFormats.data();

        // Set the formats of the depth/stencil attachment.
        if (HasDepthStencilAttachment()) {
            wgpu::TextureFormat dsFormat = GetDepthStencilFormat();
            const Format& internalFormat = device->GetValidInternalFormat(dsFormat);
            VkFormat vkDsFormat = VulkanImageFormat(device, dsFormat);

            if (internalFormat.HasDepth()) {
                pipelineRenderingCreateInfo.depthAttachmentFormat = vkDsFormat;
            }
            if (internalFormat.HasStencil()) {
                pipelineRenderingCreateInfo.stencilAttachmentFormat = vkDsFormat;
            }
        }
    } else {
        // Get a VkRenderPass that matches the attachment formats for this pipeline.
        // VkRenderPass compatibility rules let us provide placeholder data for a bunch of
        // arguments. Load and store ops are all equivalent, though we still specify
        // ExpandResolveTexture as that controls the use of input attachments. Single subpass
        // VkRenderPasses are compatible irrespective of resolve attachments being used, but for
        // ExpandResolveTexture that uses two subpasses we need to specify which attachments will
        // be resolved.
        RenderPassCacheQuery query;
        ColorAttachmentMask resolveMask =
            GetAttachmentState()->GetExpandResolveInfo().resolveTargetsMask;
        ColorAttachmentMask expandResolveMask =
            GetAttachmentState()->GetExpandResolveInfo().attachmentsToExpandResolve;

        for (auto i : GetColorAttachmentsMask()) {
            wgpu::LoadOp colorLoadOp = wgpu::LoadOp::Load;
            bool hasResolveTarget = resolveMask.test(i);

            if (expandResolveMask.test(i)) {
                // ExpandResolveTexture will use 2 subpasses in a render pass so we have to create
                // an appropriate query.
                colorLoadOp = wgpu::LoadOp::ExpandResolveTexture;
            }
            // This bool should match the value used in the final RenderPass, but we don't have the
            // appropriate MSRTSS values exposed here. Issue can be safely ignored, though.
            // See comments on skipped message VUID-vkCmdDraw-renderPass-02684 in BackendVk.cpp.
            bool renderToSingleSample = false;
            query.SetColor(i, GetColorAttachmentFormat(i), colorLoadOp, wgpu::StoreOp::Store,
                           hasResolveTarget, renderToSingleSample);
        }

        if (HasDepthStencilAttachment()) {
            query.SetDepthStencil(GetDepthStencilFormat(), wgpu::LoadOp::Load, wgpu::StoreOp::Store,
                                  false, wgpu::LoadOp::Load, wgpu::StoreOp::Store, false);
        }

        query.SetSampleCount(GetSampleCount());

        if (buildCacheKey) {
            StreamIn(&mCacheKey, query);
        }
        DAWN_TRY_ASSIGN(renderPassInfo, device->GetRenderPassCache()->GetRenderPass(query));

        createInfo.renderPass = renderPassInfo.renderPass;
    }

    // - If the pipeline uses input attachments in shader, currently this is only used by
    //   ExpandResolveTexture subpass, hence we need to set the subpass to 0.
    // - Otherwise, the pipeline will operate on the main subpass.
    // - TODO(42240662): Add explicit way to specify subpass instead of implicitly deducing based on
    // mHasInputAttachment.
    //   That also means mHasInputAttachment would be removed in future.
    createInfo.subpass = mHasInputAttachment ? 0 : renderPassInfo.mainSubpass;

    // If possible, specify where exactly robustness should be added in the pipeline. This is
    // necessary because when bindless is enabled and robustBufferAccessUpdateAfterBind is false, we
    // must only set robust buffer access on vertexInputs.
    VkPipelineRobustnessCreateInfo robustnessCreateInfo;
    if (device->GetDeviceInfo().HasExt(DeviceExt::PipelineRobustness)) {
        createInfoChain.Add(&robustnessCreateInfo,
                            VK_STRUCTURE_TYPE_PIPELINE_ROBUSTNESS_CREATE_INFO);

        // Without any specific toggle only vertex inputs can be made robust enough for WebGPU.
        robustnessCreateInfo.vertexInputs =
            VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS;
        robustnessCreateInfo.uniformBuffers = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DISABLED;
        robustnessCreateInfo.storageBuffers = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DISABLED;
        robustnessCreateInfo.images = VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_DISABLED;

        if (device->IsToggleEnabled(Toggle::VulkanUseBufferRobustAccess2)) {
            // Uniform buffers are checked with WebGPU validation and don't need robustness.
            robustnessCreateInfo.storageBuffers =
                VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS_2;
        }
        if (device->IsToggleEnabled(Toggle::VulkanUseImageRobustAccess2)) {
            robustnessCreateInfo.images =
                VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_ROBUST_IMAGE_ACCESS_2;
        }
    }

    // Record cache key information now since createInfo is not stored. Only store for the noop
    // specialization created in InitializeImpl so that future specializations use the same pipeline
    // cache, and may reuse the VkPipeline when they happen to be the same on the driver side.
    if (buildCacheKey) {
        StreamIn(&mCacheKey, createInfo, layout->GetCacheKey());
    }

    // Try to see if we have anything in the blob cache.
    platform::metrics::DawnHistogramTimer cacheTimer(GetDevice()->GetPlatform());
    Ref<PipelineCache> cache = ToBackend(GetDevice()->GetOrCreatePipelineCache(GetCacheKey()));
    VkPipeline pipeline;
    DAWN_TRY(
        CheckVkSuccess(device->fn.CreateGraphicsPipelines(device->GetVkDevice(), cache->GetHandle(),
                                                          1, &createInfo, nullptr, &*pipeline),
                       "CreateGraphicsPipelines"));
    result.pipeline = AcquireRef(new RefCountedVkHandle<VkPipeline>(device, pipeline));
    cacheTimer.RecordMicroseconds(cache->CacheHit() ? "Vulkan.CreateGraphicsPipelines.CacheHit"
                                                    : "Vulkan.CreateGraphicsPipelines.CacheMiss");

    DAWN_TRY(cache->DidCompilePipeline());

    SetLabelImpl();

    for (uint32_t i = 0; i < stageCount; ++i) {
        device->fn.DestroyShaderModule(device->GetVkDevice(), shaderStages[i].module, nullptr);
    }

    return result;
}

#define SetDynamicState(function, state)                       \
    if (!prevState || prevState->state != mDynamicState.state) \
    device->fn.function(commands, mDynamicState.state)

void RenderPipeline::ApplyDynamicState(VkCommandBuffer& commands,
                                       const RenderPipeline* prevPipeline) const {
    Device* device = ToBackend(GetDevice());

    // If Dynamic state is enabled, apply state changes between this pipeline and the previous one.
    if (device->IsToggleEnabled(Toggle::VulkanUseExtendedDynamicState)) {
        const RenderPipeline::DynamicState* prevState = nullptr;
        if (prevPipeline) {
            prevState = &prevPipeline->mDynamicState;
        }

        SetDynamicState(CmdSetPrimitiveTopologyEXT, primitiveTopology);
        SetDynamicState(CmdSetCullModeEXT, cullMode);
        SetDynamicState(CmdSetFrontFaceEXT, frontFace);
        SetDynamicState(CmdSetDepthTestEnableEXT, depthTestEnable);
        SetDynamicState(CmdSetDepthWriteEnableEXT, depthWriteEnable);
        SetDynamicState(CmdSetDepthCompareOpEXT, depthCompareOp);
        SetDynamicState(CmdSetStencilTestEnableEXT, stencilTestEnable);

        if (mDynamicState.stencilTestEnable) {
            if (!prevState || prevState->packedFrontStencil != mDynamicState.packedFrontStencil) {
                VkStencilOpState stencilOp = UnpackStencilOpState(mDynamicState.packedFrontStencil);
                device->fn.CmdSetStencilOpEXT(commands, VK_STENCIL_FACE_FRONT_BIT, stencilOp.failOp,
                                              stencilOp.passOp, stencilOp.depthFailOp,
                                              stencilOp.compareOp);
            }

            if (!prevState || prevState->packedBackStencil != mDynamicState.packedBackStencil) {
                VkStencilOpState stencilOp = UnpackStencilOpState(mDynamicState.packedBackStencil);
                device->fn.CmdSetStencilOpEXT(commands, VK_STENCIL_FACE_BACK_BIT, stencilOp.failOp,
                                              stencilOp.passOp, stencilOp.depthFailOp,
                                              stencilOp.compareOp);
            }
        }
    }
}

#undef SetDynamicState

void RenderPipeline::SetLabelImpl() {
    SetDebugName(ToBackend(GetDevice()), mHandles.pipeline, "Dawn_RenderPipeline", GetLabel());
}

VkPipelineVertexInputStateCreateInfo RenderPipeline::ComputeVertexInputDesc(
    PipelineVertexInputStateCreateInfoTemporaryAllocations* tempAllocations) {
    // Fill in the "binding info" that will be chained in the create info
    uint32_t bindingCount = 0;
    for (VertexBufferSlot slot : GetVertexBuffersUsed()) {
        const VertexBufferInfo& bindingInfo = GetVertexBuffer(slot);

        VkVertexInputBindingDescription* bindingDesc = &tempAllocations->bindings[bindingCount];
        bindingDesc->binding = static_cast<uint8_t>(slot);
        bindingDesc->stride = bindingInfo.arrayStride;
        bindingDesc->inputRate = VulkanInputRate(bindingInfo.stepMode);

        bindingCount++;
    }

    // Fill in the "attribute info" that will be chained in the create info
    uint32_t attributeCount = 0;
    for (VertexAttributeLocation loc : GetAttributeLocationsUsed()) {
        const VertexAttributeInfo& attributeInfo = GetAttribute(loc);

        VkVertexInputAttributeDescription* attributeDesc =
            &tempAllocations->attributes[attributeCount];
        attributeDesc->location = static_cast<uint8_t>(loc);
        attributeDesc->binding = static_cast<uint8_t>(attributeInfo.vertexBufferSlot);
        attributeDesc->format = VulkanVertexFormat(attributeInfo.format);
        attributeDesc->offset = attributeInfo.offset;

        attributeCount++;
    }

    // Build the create info
    VkPipelineVertexInputStateCreateInfo createInfo;
    createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
    createInfo.pNext = nullptr;
    createInfo.flags = 0;
    createInfo.vertexBindingDescriptionCount = bindingCount;
    createInfo.pVertexBindingDescriptions = tempAllocations->bindings.data();
    createInfo.vertexAttributeDescriptionCount = attributeCount;
    createInfo.pVertexAttributeDescriptions = tempAllocations->attributes.data();
    return createInfo;
}

VkPipelineDepthStencilStateCreateInfo RenderPipeline::ComputeDepthStencilDesc() {
    const DepthStencilState* descriptor = GetDepthStencilState();

    VkPipelineDepthStencilStateCreateInfo depthStencilState;
    depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
    depthStencilState.pNext = nullptr;
    depthStencilState.flags = 0;

    // Depth writes only occur if depth is enabled
    depthStencilState.depthTestEnable =
        (descriptor->depthCompare == wgpu::CompareFunction::Always &&
         descriptor->depthWriteEnabled != wgpu::OptionalBool::True)
            ? VK_FALSE
            : VK_TRUE;
    depthStencilState.depthWriteEnable =
        descriptor->depthWriteEnabled == wgpu::OptionalBool::True ? VK_TRUE : VK_FALSE;
    depthStencilState.depthCompareOp = ToVulkanCompareOp(descriptor->depthCompare);
    depthStencilState.depthBoundsTestEnable = VK_FALSE;
    depthStencilState.minDepthBounds = 0.0f;
    depthStencilState.maxDepthBounds = 1.0f;

    depthStencilState.stencilTestEnable = UsesStencil() ? VK_TRUE : VK_FALSE;

    depthStencilState.front.failOp = VulkanStencilOp(descriptor->stencilFront.failOp);
    depthStencilState.front.passOp = VulkanStencilOp(descriptor->stencilFront.passOp);
    depthStencilState.front.depthFailOp = VulkanStencilOp(descriptor->stencilFront.depthFailOp);
    depthStencilState.front.compareOp = ToVulkanCompareOp(descriptor->stencilFront.compare);

    depthStencilState.back.failOp = VulkanStencilOp(descriptor->stencilBack.failOp);
    depthStencilState.back.passOp = VulkanStencilOp(descriptor->stencilBack.passOp);
    depthStencilState.back.depthFailOp = VulkanStencilOp(descriptor->stencilBack.depthFailOp);
    depthStencilState.back.compareOp = ToVulkanCompareOp(descriptor->stencilBack.compare);

    // Dawn doesn't have separate front and back stencil masks.
    depthStencilState.front.compareMask = descriptor->stencilReadMask;
    depthStencilState.back.compareMask = descriptor->stencilReadMask;
    depthStencilState.front.writeMask = descriptor->stencilWriteMask;
    depthStencilState.back.writeMask = descriptor->stencilWriteMask;

    // The stencil reference is always dynamic
    depthStencilState.front.reference = 0;
    depthStencilState.back.reference = 0;

    return depthStencilState;
}

RenderPipeline::~RenderPipeline() = default;

void RenderPipeline::DestroyImpl(DestroyReason reason) {
    RenderPipelineBase::DestroyImpl(reason);

    mSpecializations->clear();

    // Handles were owned by refs in mSpecializations that were just deleted.
    mHandles = {};
}

bool RenderPipeline::NeedsPixelCenterPolyfill() const {
    return UseSampleRateShading() && UsesFragPosition();
}

bool RenderPipeline::RequiresSpecialization() const {
    return mRequiresSpecialization;
}

VkPipeline RenderPipeline::GetHandle() const {
    DAWN_ASSERT(mHandles.pipeline != VK_NULL_HANDLE);
    return mHandles.pipeline;
}

VkPipelineLayout RenderPipeline::GetVkLayout() const {
    DAWN_ASSERT(mHandles.layout != nullptr);
    return mHandles.layout;
}

}  // namespace dawn::native::vulkan
