//
// 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.
//

// ProgramPipeline.cpp: Implements the gl::ProgramPipeline class.
// Implements GL program pipeline objects and related functionality.
// [OpenGL ES 3.1] section 7.4 page 105.

#include "libANGLE/ProgramPipeline.h"

#include <algorithm>

#include "libANGLE/Context.h"
#include "libANGLE/Program.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/renderer/GLImplFactory.h"
#include "libANGLE/renderer/ProgramPipelineImpl.h"

namespace gl
{

enum SubjectIndexes : angle::SubjectIndex
{
    kExecutableSubjectIndex = 0
};

ProgramPipelineState::ProgramPipelineState()
    : mLabel(),
      mActiveShaderProgram(nullptr),
      mValid(false),
      mExecutable(new ProgramExecutable()),
      mIsLinked(false)
{
    for (const ShaderType shaderType : gl::AllShaderTypes())
    {
        mPrograms[shaderType] = nullptr;
    }
}

ProgramPipelineState::~ProgramPipelineState()
{
    SafeDelete(mExecutable);
}

const std::string &ProgramPipelineState::getLabel() const
{
    return mLabel;
}

void ProgramPipelineState::activeShaderProgram(Program *shaderProgram)
{
    mActiveShaderProgram = shaderProgram;
}

void ProgramPipelineState::useProgramStage(const Context *context,
                                           const ShaderType shaderType,
                                           Program *shaderProgram,
                                           angle::ObserverBinding *programObserverBindings)
{
    Program *oldProgram = mPrograms[shaderType];
    if (oldProgram)
    {
        oldProgram->release(context);
    }

    // If program refers to a program object with a valid shader attached for the indicated shader
    // stage, glUseProgramStages installs the executable code for that stage in the indicated
    // program pipeline object pipeline.
    if (shaderProgram && (shaderProgram->id().value != 0) &&
        shaderProgram->getExecutable().hasLinkedShaderStage(shaderType))
    {
        mPrograms[shaderType] = shaderProgram;
        shaderProgram->addRef();
    }
    else
    {
        // If program is zero, or refers to a program object with no valid shader executable for the
        // given stage, it is as if the pipeline object has no programmable stage configured for the
        // indicated shader stage.
        mPrograms[shaderType] = nullptr;
    }

    Program *program = mPrograms[shaderType];
    programObserverBindings->bind(program);
}

void ProgramPipelineState::useProgramStages(
    const Context *context,
    GLbitfield stages,
    Program *shaderProgram,
    std::vector<angle::ObserverBinding> *programObserverBindings)
{
    for (size_t singleShaderBit : angle::BitSet16<16>(static_cast<uint16_t>(stages)))
    {
        // Cast back to a bit after the iterator returns an index.
        ShaderType shaderType = GetShaderTypeFromBitfield(angle::Bit<size_t>(singleShaderBit));
        if (shaderType == ShaderType::InvalidEnum)
        {
            break;
        }
        useProgramStage(context, shaderType, shaderProgram,
                        &programObserverBindings->at(static_cast<size_t>(shaderType)));
    }
}

bool ProgramPipelineState::usesShaderProgram(ShaderProgramID programId) const
{
    for (const Program *program : mPrograms)
    {
        if (program && (program->id() == programId))
        {
            return true;
        }
    }

    return false;
}

void ProgramPipelineState::updateExecutableTextures()
{
    for (const ShaderType shaderType : mExecutable->getLinkedShaderStages())
    {
        const Program *program = getShaderProgram(shaderType);
        ASSERT(program);
        mExecutable->setActiveTextureMask(program->getExecutable().getActiveSamplersMask());
        mExecutable->setActiveImagesMask(program->getExecutable().getActiveImagesMask());
        // Updates mActiveSamplerRefCounts, mActiveSamplerTypes, and mActiveSamplerFormats
        mExecutable->updateActiveSamplers(program->getState());
    }
}

rx::SpecConstUsageBits ProgramPipelineState::getSpecConstUsageBits() const
{
    rx::SpecConstUsageBits specConstUsageBits;
    for (const ShaderType shaderType : mExecutable->getLinkedShaderStages())
    {
        const Program *program = getShaderProgram(shaderType);
        ASSERT(program);
        specConstUsageBits |= program->getState().getSpecConstUsageBits();
    }
    return specConstUsageBits;
}

ProgramPipeline::ProgramPipeline(rx::GLImplFactory *factory, ProgramPipelineID handle)
    : RefCountObject(factory->generateSerial(), handle),
      mProgramPipelineImpl(factory->createProgramPipeline(mState)),
      mExecutableObserverBinding(this, kExecutableSubjectIndex)
{
    ASSERT(mProgramPipelineImpl);

    for (const ShaderType shaderType : gl::AllShaderTypes())
    {
        mProgramObserverBindings.emplace_back(this, static_cast<angle::SubjectIndex>(shaderType));
    }
    mExecutableObserverBinding.bind(mState.mExecutable);
}

ProgramPipeline::~ProgramPipeline()
{
    mProgramPipelineImpl.release();
}

void ProgramPipeline::onDestroy(const Context *context)
{
    for (Program *program : mState.mPrograms)
    {
        if (program)
        {
            ASSERT(program->getRefCount());
            program->release(context);
        }
    }

    getImplementation()->destroy(context);
}

void ProgramPipeline::setLabel(const Context *context, const std::string &label)
{
    mState.mLabel = label;
}

const std::string &ProgramPipeline::getLabel() const
{
    return mState.mLabel;
}

rx::ProgramPipelineImpl *ProgramPipeline::getImplementation() const
{
    return mProgramPipelineImpl.get();
}

void ProgramPipeline::activeShaderProgram(Program *shaderProgram)
{
    mState.activeShaderProgram(shaderProgram);
}

void ProgramPipeline::useProgramStages(const Context *context,
                                       GLbitfield stages,
                                       Program *shaderProgram)
{
    mState.useProgramStages(context, stages, shaderProgram, &mProgramObserverBindings);
    updateLinkedShaderStages();
    updateExecutable();

    mState.mIsLinked = false;
}

void ProgramPipeline::updateLinkedShaderStages()
{
    mState.mExecutable->resetLinkedShaderStages();

    for (const ShaderType shaderType : gl::AllShaderTypes())
    {
        Program *program = mState.mPrograms[shaderType];
        if (program)
        {
            mState.mExecutable->setLinkedShaderStages(shaderType);
        }
    }

    mState.mExecutable->updateCanDrawWith();
}

void ProgramPipeline::updateExecutableAttributes()
{
    Program *vertexProgram = getShaderProgram(gl::ShaderType::Vertex);

    if (!vertexProgram)
    {
        return;
    }

    const ProgramExecutable &vertexExecutable      = vertexProgram->getExecutable();
    mState.mExecutable->mActiveAttribLocationsMask = vertexExecutable.mActiveAttribLocationsMask;
    mState.mExecutable->mMaxActiveAttribLocation   = vertexExecutable.mMaxActiveAttribLocation;
    mState.mExecutable->mAttributesTypeMask        = vertexExecutable.mAttributesTypeMask;
    mState.mExecutable->mAttributesMask            = vertexExecutable.mAttributesMask;
}

void ProgramPipeline::updateTransformFeedbackMembers()
{
    Program *vertexProgram = getShaderProgram(gl::ShaderType::Vertex);

    if (!vertexProgram)
    {
        return;
    }

    const ProgramExecutable &vertexExecutable     = vertexProgram->getExecutable();
    mState.mExecutable->mTransformFeedbackStrides = vertexExecutable.mTransformFeedbackStrides;
    mState.mExecutable->mLinkedTransformFeedbackVaryings =
        vertexExecutable.mLinkedTransformFeedbackVaryings;
}

void ProgramPipeline::updateShaderStorageBlocks()
{
    mState.mExecutable->mComputeShaderStorageBlocks.clear();
    mState.mExecutable->mGraphicsShaderStorageBlocks.clear();

    // Only copy the storage blocks from each Program in the PPO once, since each Program could
    // contain multiple shader stages.
    ShaderBitSet handledStages;

    for (const gl::ShaderType shaderType : kAllGraphicsShaderTypes)
    {
        const Program *shaderProgram = getShaderProgram(shaderType);
        if (shaderProgram && !handledStages.test(shaderType))
        {
            // Only add each Program's blocks once.
            handledStages |= shaderProgram->getExecutable().getLinkedShaderStages();

            for (const InterfaceBlock &block :
                 shaderProgram->getExecutable().getShaderStorageBlocks())
            {
                mState.mExecutable->mGraphicsShaderStorageBlocks.emplace_back(block);
            }
        }
    }

    const Program *computeProgram = getShaderProgram(ShaderType::Compute);
    if (computeProgram)
    {
        for (const InterfaceBlock &block : computeProgram->getExecutable().getShaderStorageBlocks())
        {
            mState.mExecutable->mComputeShaderStorageBlocks.emplace_back(block);
        }
    }
}

void ProgramPipeline::updateImageBindings()
{
    mState.mExecutable->mComputeImageBindings.clear();
    mState.mExecutable->mGraphicsImageBindings.clear();

    // Only copy the storage blocks from each Program in the PPO once, since each Program could
    // contain multiple shader stages.
    ShaderBitSet handledStages;

    for (const gl::ShaderType shaderType : kAllGraphicsShaderTypes)
    {
        const Program *shaderProgram = getShaderProgram(shaderType);
        if (shaderProgram && !handledStages.test(shaderType))
        {
            // Only add each Program's blocks once.
            handledStages |= shaderProgram->getExecutable().getLinkedShaderStages();

            for (const ImageBinding &imageBinding : shaderProgram->getState().getImageBindings())
            {
                mState.mExecutable->mGraphicsImageBindings.emplace_back(imageBinding);
            }
        }
    }

    const Program *computeProgram = getShaderProgram(ShaderType::Compute);
    if (computeProgram)
    {
        for (const ImageBinding &imageBinding : computeProgram->getState().getImageBindings())
        {
            mState.mExecutable->mComputeImageBindings.emplace_back(imageBinding);
        }
    }
}

void ProgramPipeline::updateHasBooleans()
{
    // Need to check all of the shader stages, not just linked, so we handle Compute correctly.
    for (const gl::ShaderType shaderType : kAllGraphicsShaderTypes)
    {
        const Program *shaderProgram = getShaderProgram(shaderType);
        if (shaderProgram)
        {
            const ProgramExecutable &executable = shaderProgram->getExecutable();

            if (executable.hasUniformBuffers())
            {
                mState.mExecutable->mPipelineHasGraphicsUniformBuffers = true;
            }
            if (executable.hasGraphicsStorageBuffers())
            {
                mState.mExecutable->mPipelineHasGraphicsStorageBuffers = true;
            }
            if (executable.hasAtomicCounterBuffers())
            {
                mState.mExecutable->mPipelineHasGraphicsAtomicCounterBuffers = true;
            }
            if (executable.hasDefaultUniforms())
            {
                mState.mExecutable->mPipelineHasGraphicsDefaultUniforms = true;
            }
            if (executable.hasTextures())
            {
                mState.mExecutable->mPipelineHasGraphicsTextures = true;
            }
            if (executable.hasGraphicsImages())
            {
                mState.mExecutable->mPipelineHasGraphicsImages = true;
            }
        }
    }

    const Program *computeProgram = getShaderProgram(ShaderType::Compute);
    if (computeProgram)
    {
        const ProgramExecutable &executable = computeProgram->getExecutable();

        if (executable.hasUniformBuffers())
        {
            mState.mExecutable->mPipelineHasComputeUniformBuffers = true;
        }
        if (executable.hasComputeStorageBuffers())
        {
            mState.mExecutable->mPipelineHasComputeStorageBuffers = true;
        }
        if (executable.hasAtomicCounterBuffers())
        {
            mState.mExecutable->mPipelineHasComputeAtomicCounterBuffers = true;
        }
        if (executable.hasDefaultUniforms())
        {
            mState.mExecutable->mPipelineHasComputeDefaultUniforms = true;
        }
        if (executable.hasTextures())
        {
            mState.mExecutable->mPipelineHasComputeTextures = true;
        }
        if (executable.hasComputeImages())
        {
            mState.mExecutable->mPipelineHasComputeImages = true;
        }
    }
}

void ProgramPipeline::updateExecutable()
{
    mState.mExecutable->reset();

    // Vertex Shader ProgramExecutable properties
    updateExecutableAttributes();
    updateTransformFeedbackMembers();
    updateShaderStorageBlocks();
    updateImageBindings();

    // All Shader ProgramExecutable properties
    mState.updateExecutableTextures();

    // Must be last, since it queries things updated by earlier functions
    updateHasBooleans();
}

// The attached shaders are checked for linking errors by matching up their variables.
// Uniform, input and output variables get collected.
// The code gets compiled into binaries.
angle::Result ProgramPipeline::link(const Context *context)
{
    if (mState.mIsLinked)
    {
        return angle::Result::Continue;
    }

    ProgramMergedVaryings mergedVaryings;
    VaryingPacking varyingPacking;

    if (!getExecutable().isCompute())
    {
        InfoLog &infoLog = mState.mExecutable->getInfoLog();
        infoLog.reset();

        if (!linkVaryings(infoLog))
        {
            return angle::Result::Stop;
        }

        if (!LinkValidateProgramGlobalNames(infoLog, *this))
        {
            return angle::Result::Stop;
        }

        mergedVaryings = GetMergedVaryingsFromShaders(*this);
        for (ShaderType shaderType : getExecutable().getLinkedShaderStages())
        {
            Program *program = mState.mPrograms[shaderType];
            ASSERT(program);
            if (!program->linkMergedVaryings(context, mergedVaryings, &varyingPacking))
            {
                return angle::Result::Stop;
            }
        }
    }

    ANGLE_TRY(getImplementation()->link(context, mergedVaryings, varyingPacking));

    mState.mIsLinked = true;

    return angle::Result::Continue;
}

bool ProgramPipeline::linkVaryings(InfoLog &infoLog) const
{
    ShaderType previousShaderType = ShaderType::InvalidEnum;
    for (ShaderType shaderType : getExecutable().getLinkedShaderStages())
    {
        Program *program = getShaderProgram(shaderType);
        ASSERT(program);
        ProgramExecutable &executable = program->getExecutable();

        if (previousShaderType != ShaderType::InvalidEnum)
        {
            Program *previousProgram = getShaderProgram(previousShaderType);
            ASSERT(previousProgram);
            ProgramExecutable &previousExecutable = previousProgram->getExecutable();

            if (!LinkValidateShaderInterfaceMatching(
                    previousExecutable.getLinkedOutputVaryings(previousShaderType),
                    executable.getLinkedInputVaryings(shaderType), previousShaderType, shaderType,
                    previousExecutable.getLinkedShaderVersion(previousShaderType),
                    executable.getLinkedShaderVersion(shaderType), true, infoLog))
            {
                return false;
            }
        }
        previousShaderType = shaderType;
    }

    Program *vertexProgram   = mState.mPrograms[ShaderType::Vertex];
    Program *fragmentProgram = mState.mPrograms[ShaderType::Fragment];
    if (!vertexProgram || !fragmentProgram)
    {
        return false;
    }
    ProgramExecutable &vertexExecutable   = vertexProgram->getExecutable();
    ProgramExecutable &fragmentExecutable = fragmentProgram->getExecutable();
    return LinkValidateBuiltInVaryings(
        vertexExecutable.getLinkedOutputVaryings(ShaderType::Vertex),
        fragmentExecutable.getLinkedInputVaryings(ShaderType::Fragment),
        vertexExecutable.getLinkedShaderVersion(ShaderType::Vertex), infoLog);
}

void ProgramPipeline::validate(const gl::Context *context)
{
    const Caps &caps = context->getCaps();
    mState.mValid    = true;
    InfoLog &infoLog = mState.mExecutable->getInfoLog();
    infoLog.reset();

    for (const ShaderType shaderType : mState.mExecutable->getLinkedShaderStages())
    {
        Program *shaderProgram = mState.mPrograms[shaderType];
        if (shaderProgram)
        {
            shaderProgram->resolveLink(context);
            shaderProgram->validate(caps);
            std::string shaderInfoString = shaderProgram->getExecutable().getInfoLogString();
            if (shaderInfoString.length())
            {
                mState.mValid = false;
                infoLog << shaderInfoString << "\n";
                return;
            }
            if (!shaderProgram->isSeparable())
            {
                mState.mValid = false;
                infoLog << GetShaderTypeString(shaderType) << " is not marked separable."
                        << "\n";
                return;
            }
        }
    }

    if (!linkVaryings(infoLog))
    {
        mState.mValid = false;

        for (const ShaderType shaderType : mState.mExecutable->getLinkedShaderStages())
        {
            Program *shaderProgram = mState.mPrograms[shaderType];
            ASSERT(shaderProgram);
            shaderProgram->validate(caps);
            std::string shaderInfoString = shaderProgram->getExecutable().getInfoLogString();
            if (shaderInfoString.length())
            {
                infoLog << shaderInfoString << "\n";
            }
        }
    }
}

bool ProgramPipeline::validateSamplers(InfoLog *infoLog, const Caps &caps)
{
    for (const ShaderType shaderType : gl::AllShaderTypes())
    {
        Program *shaderProgram = mState.mPrograms[shaderType];
        if (shaderProgram && !shaderProgram->validateSamplers(infoLog, caps))
        {
            return false;
        }
    }

    return true;
}

void ProgramPipeline::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message)
{
    switch (message)
    {
        case angle::SubjectMessage::SubjectChanged:
            mState.mIsLinked = false;
            mState.updateExecutableTextures();
            break;
        default:
            UNREACHABLE();
            break;
    }
}

Shader *ProgramPipeline::getAttachedShader(ShaderType shaderType) const
{
    const Program *program = mState.mPrograms[shaderType];
    return program ? program->getAttachedShader(shaderType) : nullptr;
}
}  // namespace gl
