//
// Copyright 2017 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// ProgramPipelineVk.cpp:
//    Implements the class methods for ProgramPipelineVk.
//

#include "libANGLE/renderer/vulkan/ProgramPipelineVk.h"

namespace rx
{

ProgramPipelineVk::ProgramPipelineVk(const gl::ProgramPipelineState &state)
    : ProgramPipelineImpl(state)
{}

ProgramPipelineVk::~ProgramPipelineVk() {}

void ProgramPipelineVk::destroy(const gl::Context *context)
{
    ContextVk *contextVk = vk::GetImpl(context);
    reset(contextVk);
}

void ProgramPipelineVk::reset(ContextVk *contextVk)
{
    mExecutable.reset(contextVk);
}

angle::Result ProgramPipelineVk::link(const gl::Context *glContext,
                                      const gl::ProgramMergedVaryings &mergedVaryings,
                                      const gl::ProgramVaryingPacking &varyingPacking)
{
    ContextVk *contextVk                      = vk::GetImpl(glContext);
    const gl::ProgramExecutable &glExecutable = mState.getExecutable();
    SpvSourceOptions options                  = SpvCreateSourceOptions(contextVk->getFeatures());
    SpvProgramInterfaceInfo spvProgramInterfaceInfo;
    spvProgramInterfaceInfo = {};

    reset(contextVk);
    mExecutable.clearVariableInfoMap();

    // Now that the program pipeline has all of the programs attached, the various descriptor
    // set/binding locations need to be re-assigned to their correct values.
    const gl::ShaderType linkedTransformFeedbackStage =
        glExecutable.getLinkedTransformFeedbackStage();

    // This should be done before assigning varying locations. Otherwise, we can encounter shader
    // interface mismatching problems when the transform feedback stage is not the vertex stage.
    if (options.supportsTransformFeedbackExtension)
    {
        for (const gl::ShaderType shaderType : glExecutable.getLinkedShaderStages())
        {
            const gl::Program *glProgram = mState.getShaderProgram(shaderType);
            if (glProgram && gl::ShaderTypeSupportsTransformFeedback(shaderType))
            {
                const bool isTransformFeedbackStage =
                    shaderType == linkedTransformFeedbackStage &&
                    !glProgram->getState().getLinkedTransformFeedbackVaryings().empty();

                SpvAssignTransformFeedbackLocations(
                    shaderType, glProgram->getExecutable(), isTransformFeedbackStage,
                    &spvProgramInterfaceInfo, &mExecutable.mVariableInfoMap);
            }
        }
    }

    mExecutable.mOriginalShaderInfo.clear();

    SpvAssignLocations(options, glExecutable, varyingPacking, linkedTransformFeedbackStage,
                       &spvProgramInterfaceInfo, &mExecutable.mVariableInfoMap);

    for (const gl::ShaderType shaderType : glExecutable.getLinkedShaderStages())
    {
        const gl::Program *program               = mState.getShaderProgram(shaderType);
        ProgramVk *programVk                     = vk::GetImpl(program);
        ProgramExecutableVk &programExecutableVk = programVk->getExecutable();
        mExecutable.mDefaultUniformBlocks[shaderType] =
            programExecutableVk.getSharedDefaultUniformBlock(shaderType);

        mExecutable.mOriginalShaderInfo.initShaderFromProgram(
            shaderType, programExecutableVk.mOriginalShaderInfo);
    }

    mExecutable.setAllDefaultUniformsDirty(glExecutable);

    if (contextVk->getFeatures().varyingsRequireMatchingPrecisionInSpirv.enabled &&
        contextVk->getFeatures().enablePrecisionQualifiers.enabled)
    {
        mExecutable.resolvePrecisionMismatch(mergedVaryings);
    }

    ANGLE_TRY(mExecutable.createPipelineLayout(contextVk, mState.getExecutable(), nullptr));

    return mExecutable.warmUpPipelineCache(contextVk, mState.getExecutable());
}  // namespace rx

angle::Result ProgramPipelineVk::syncState(const gl::Context *context,
                                           const gl::Program::DirtyBits &dirtyBits)
{
    ASSERT(dirtyBits.any());
    // Push dirty bits to executable so that they can be used later.
    mExecutable.mDirtyBits |= dirtyBits;
    return angle::Result::Continue;
}

void ProgramPipelineVk::onProgramUniformUpdate(gl::ShaderType shaderType)
{
    mExecutable.mDefaultUniformBlocksDirty.set(shaderType);
}
}  // namespace rx
