//
// Copyright 2020 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.
//
// ProgramExecutable.cpp: Collects the interfaces common to both Programs and
// ProgramPipelines in order to execute/draw with either.

#include "libANGLE/ProgramExecutable.h"

#include "libANGLE/Context.h"
#include "libANGLE/Program.h"
#include "libANGLE/ProgramPipeline.h"
#include "libANGLE/Shader.h"

namespace gl
{

ProgramExecutable::ProgramExecutable()
    : mProgramState(nullptr),
      mProgramPipelineState(nullptr),
      mMaxActiveAttribLocation(0),
      mAttributesTypeMask(0),
      mAttributesMask(0),
      mActiveSamplersMask(0),
      mActiveSamplerRefCounts{},
      mActiveImagesMask(0)
{
    mActiveSamplerTypes.fill(TextureType::InvalidEnum);
    mActiveSamplerFormats.fill(SamplerFormat::InvalidEnum);
}

ProgramExecutable::~ProgramExecutable() = default;

void ProgramExecutable::reset()
{
    resetInfoLog();
    mActiveAttribLocationsMask.reset();
    mAttributesTypeMask.reset();
    mAttributesMask.reset();
    mMaxActiveAttribLocation = 0;

    mActiveSamplersMask.reset();
    mActiveSamplerRefCounts = {};
    mActiveSamplerTypes.fill(TextureType::InvalidEnum);
    mActiveSamplerFormats.fill(SamplerFormat::InvalidEnum);

    mActiveImagesMask.reset();
}

const ProgramState *ProgramExecutable::getProgramState(ShaderType shaderType) const
{
    if (mProgramState &&
        (hasLinkedShaderStage(shaderType) || mProgramState->getAttachedShader(shaderType)))
    {
        return mProgramState;
    }
    else if (mProgramPipelineState && (hasLinkedShaderStage(shaderType) ||
                                       mProgramPipelineState->getShaderProgram(shaderType)))
    {
        return &mProgramPipelineState->getShaderProgram(shaderType)->getState();
    }

    return nullptr;
}

int ProgramExecutable::getInfoLogLength() const
{
    return static_cast<int>(mInfoLog.getLength());
}

void ProgramExecutable::getInfoLog(GLsizei bufSize, GLsizei *length, char *infoLog) const
{
    return mInfoLog.getLog(bufSize, length, infoLog);
}

std::string ProgramExecutable::getInfoLogString() const
{
    return mInfoLog.str();
}

bool ProgramExecutable::isAttribLocationActive(size_t attribLocation) const
{
    // TODO(timvp): http://anglebug.com/3570: Enable this assert here somehow.
    //    ASSERT(mLinkResolved);
    ASSERT(attribLocation < mActiveAttribLocationsMask.size());
    return mActiveAttribLocationsMask[attribLocation];
}

AttributesMask ProgramExecutable::getAttributesMask() const
{
    // TODO(timvp): http://anglebug.com/3570: Enable this assert here somehow.
    //    ASSERT(mLinkResolved);
    return mAttributesMask;
}

bool ProgramExecutable::hasDefaultUniforms() const
{
    ASSERT(mProgramState || mProgramPipelineState);
    if (mProgramState)
    {
        return mProgramState->hasDefaultUniforms();
    }

    return mProgramPipelineState->hasDefaultUniforms();
}

bool ProgramExecutable::hasTextures() const
{
    ASSERT(mProgramState || mProgramPipelineState);
    if (mProgramState)
    {
        return mProgramState->hasTextures();
    }

    return mProgramPipelineState->hasTextures();
}

bool ProgramExecutable::hasUniformBuffers() const
{
    ASSERT(mProgramState || mProgramPipelineState);
    if (mProgramState)
    {
        return mProgramState->hasUniformBuffers();
    }

    return mProgramPipelineState->hasUniformBuffers();
}

bool ProgramExecutable::hasStorageBuffers() const
{
    ASSERT(mProgramState || mProgramPipelineState);
    if (mProgramState)
    {
        return mProgramState->hasStorageBuffers();
    }

    return mProgramPipelineState->hasStorageBuffers();
}

bool ProgramExecutable::hasAtomicCounterBuffers() const
{
    ASSERT(mProgramState || mProgramPipelineState);
    if (mProgramState)
    {
        return mProgramState->hasAtomicCounterBuffers();
    }

    return mProgramPipelineState->hasAtomicCounterBuffers();
}

bool ProgramExecutable::hasImages() const
{
    ASSERT(mProgramState || mProgramPipelineState);
    if (mProgramState)
    {
        return mProgramState->hasImages();
    }

    return mProgramPipelineState->hasImages();
}

bool ProgramExecutable::hasTransformFeedbackOutput() const
{
    ASSERT(mProgramState || mProgramPipelineState);
    if (mProgramState)
    {
        return mProgramState->hasTransformFeedbackOutput();
    }

    return mProgramPipelineState->hasTransformFeedbackOutput();
}

size_t ProgramExecutable::getTransformFeedbackBufferCount(const gl::State &glState) const
{
    ASSERT(mProgramState || mProgramPipelineState);
    if (mProgramState)
    {
        return mProgramState->getTransformFeedbackBufferCount();
    }

    // TODO(timvp): http://anglebug.com/3570: Support program pipelines

    return 0;
}

void ProgramExecutable::updateActiveSamplers(const ProgramState &programState)
{
    const std::vector<SamplerBinding> &samplerBindings = programState.getSamplerBindings();

    for (uint32_t samplerIndex = 0; samplerIndex < samplerBindings.size(); ++samplerIndex)
    {
        const SamplerBinding &samplerBinding = samplerBindings[samplerIndex];
        if (samplerBinding.unreferenced)
            continue;

        uint32_t uniformIndex = programState.getUniformIndexFromSamplerIndex(samplerIndex);
        const gl::LinkedUniform &samplerUniform = programState.getUniforms()[uniformIndex];

        for (GLint textureUnit : samplerBinding.boundTextureUnits)
        {
            if (++mActiveSamplerRefCounts[textureUnit] == 1)
            {
                mActiveSamplerTypes[textureUnit]      = samplerBinding.textureType;
                mActiveSamplerFormats[textureUnit]    = samplerBinding.format;
                mActiveSamplerShaderBits[textureUnit] = samplerUniform.activeShaders();
            }
            else
            {
                if (mActiveSamplerTypes[textureUnit] != samplerBinding.textureType)
                {
                    mActiveSamplerTypes[textureUnit] = TextureType::InvalidEnum;
                }
                if (mActiveSamplerFormats[textureUnit] != samplerBinding.format)
                {
                    mActiveSamplerFormats[textureUnit] = SamplerFormat::InvalidEnum;
                }
            }
            mActiveSamplersMask.set(textureUnit);
        }
    }
}

void ProgramExecutable::updateActiveImages(std::vector<ImageBinding> &imageBindings)
{
    const bool compute = isCompute() ? true : false;
    for (uint32_t imageIndex = 0; imageIndex < imageBindings.size(); ++imageIndex)
    {
        const gl::ImageBinding &imageBinding = imageBindings[imageIndex];
        if (imageBinding.unreferenced)
            continue;

        uint32_t uniformIndex = mProgramState->getUniformIndexFromImageIndex(imageIndex);
        const gl::LinkedUniform &imageUniform = mProgramState->getUniforms()[uniformIndex];
        const ShaderBitSet shaderBits         = imageUniform.activeShaders();
        for (GLint imageUnit : imageBinding.boundImageUnits)
        {
            mActiveImagesMask.set(imageUnit);
            if (compute)
                mActiveImageShaderBits[imageUnit].set(gl::ShaderType::Compute);
            else
                mActiveImageShaderBits[imageUnit] = shaderBits;
        }
    }
}

void ProgramExecutable::setSamplerUniformTextureTypeAndFormat(
    size_t textureUnitIndex,
    std::vector<SamplerBinding> &samplerBindings)
{
    bool foundBinding         = false;
    TextureType foundType     = TextureType::InvalidEnum;
    SamplerFormat foundFormat = SamplerFormat::InvalidEnum;

    for (const SamplerBinding &binding : samplerBindings)
    {
        if (binding.unreferenced)
            continue;

        // A conflict exists if samplers of different types are sourced by the same texture unit.
        // We need to check all bound textures to detect this error case.
        for (GLuint textureUnit : binding.boundTextureUnits)
        {
            if (textureUnit == textureUnitIndex)
            {
                if (!foundBinding)
                {
                    foundBinding = true;
                    foundType    = binding.textureType;
                    foundFormat  = binding.format;
                }
                else
                {
                    if (foundType != binding.textureType)
                    {
                        foundType = TextureType::InvalidEnum;
                    }
                    if (foundFormat != binding.format)
                    {
                        foundFormat = SamplerFormat::InvalidEnum;
                    }
                }
            }
        }
    }

    mActiveSamplerTypes[textureUnitIndex]   = foundType;
    mActiveSamplerFormats[textureUnitIndex] = foundFormat;
}

bool ProgramExecutable::linkValidateGlobalNames(InfoLog &infoLog) const
{
    std::unordered_map<std::string, const sh::ShaderVariable *> uniformMap;
    using BlockAndFieldPair = std::pair<const sh::InterfaceBlock *, const sh::ShaderVariable *>;
    std::unordered_map<std::string, std::vector<BlockAndFieldPair>> uniformBlockFieldMap;

    for (ShaderType shaderType : kAllGraphicsShaderTypes)
    {
        const ProgramState *programState = getProgramState(shaderType);
        if (!programState)
        {
            continue;
        }
        Shader *shader = programState->getAttachedShader(shaderType);
        if (!shader)
        {
            continue;
        }

        // Build a map of Uniforms
        const std::vector<sh::ShaderVariable> uniforms = shader->getUniforms();
        for (const auto &uniform : uniforms)
        {
            uniformMap[uniform.name] = &uniform;
        }

        // Build a map of Uniform Blocks
        // This will also detect any field name conflicts between Uniform Blocks without instance
        // names
        const std::vector<sh::InterfaceBlock> &uniformBlocks = shader->getUniformBlocks();
        for (const auto &uniformBlock : uniformBlocks)
        {
            // Only uniform blocks without an instance name can create a conflict with their field
            // names
            if (!uniformBlock.instanceName.empty())
            {
                continue;
            }

            for (const auto &field : uniformBlock.fields)
            {
                if (!uniformBlockFieldMap.count(field.name))
                {
                    // First time we've seen this uniform block field name, so add the
                    // (Uniform Block, Field) pair immediately since there can't be a conflict yet
                    BlockAndFieldPair blockAndFieldPair(&uniformBlock, &field);
                    std::vector<BlockAndFieldPair> newUniformBlockList;
                    newUniformBlockList.push_back(blockAndFieldPair);
                    uniformBlockFieldMap[field.name] = newUniformBlockList;
                    continue;
                }

                // We've seen this name before.
                // We need to check each of the uniform blocks that contain a field with this name
                // to see if there's a conflict or not.
                std::vector<BlockAndFieldPair> prevBlockFieldPairs =
                    uniformBlockFieldMap[field.name];
                for (const auto &prevBlockFieldPair : prevBlockFieldPairs)
                {
                    const sh::InterfaceBlock *prevUniformBlock      = prevBlockFieldPair.first;
                    const sh::ShaderVariable *prevUniformBlockField = prevBlockFieldPair.second;

                    if (uniformBlock.isSameInterfaceBlockAtLinkTime(*prevUniformBlock))
                    {
                        // The same uniform block should, by definition, contain the same field name
                        continue;
                    }

                    // The uniform blocks don't match, so check if the necessary field properties
                    // also match
                    if ((field.name == prevUniformBlockField->name) &&
                        (field.type == prevUniformBlockField->type) &&
                        (field.precision == prevUniformBlockField->precision))
                    {
                        infoLog << "Name conflicts between uniform block field names: "
                                << field.name;
                        return false;
                    }
                }

                // No conflict, so record this pair
                BlockAndFieldPair blockAndFieldPair(&uniformBlock, &field);
                uniformBlockFieldMap[field.name].push_back(blockAndFieldPair);
            }
        }
    }

    // Validate no uniform names conflict with attribute names
    const ProgramState *programState = getProgramState(ShaderType::Vertex);
    if (programState)
    {
        Shader *vertexShader = programState->getAttachedShader(ShaderType::Vertex);
        if (vertexShader)
        {
            for (const auto &attrib : vertexShader->getActiveAttributes())
            {
                if (uniformMap.count(attrib.name))
                {
                    infoLog << "Name conflicts between a uniform and an attribute: " << attrib.name;
                    return false;
                }
            }
        }
    }

    // Validate no Uniform Block fields conflict with other Uniforms
    for (const auto &uniformBlockField : uniformBlockFieldMap)
    {
        const std::string &fieldName = uniformBlockField.first;
        if (uniformMap.count(fieldName))
        {
            infoLog << "Name conflicts between a uniform and a uniform block field: " << fieldName;
            return false;
        }
    }

    return true;
}

}  // namespace gl
