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

// ProgramD3D.cpp: Defines the rx::ProgramD3D class which implements rx::ProgramImpl.

#include "libANGLE/renderer/d3d/ProgramD3D.h"

#include "common/MemoryBuffer.h"
#include "common/bitset_utils.h"
#include "common/string_utils.h"
#include "common/utilities.h"
#include "libANGLE/Context.h"
#include "libANGLE/Program.h"
#include "libANGLE/ProgramLinkedResources.h"
#include "libANGLE/Uniform.h"
#include "libANGLE/VertexArray.h"
#include "libANGLE/features.h"
#include "libANGLE/queryconversions.h"
#include "libANGLE/renderer/ContextImpl.h"
#include "libANGLE/renderer/d3d/ContextD3D.h"
#include "libANGLE/renderer/d3d/ShaderD3D.h"
#include "libANGLE/renderer/d3d/ShaderExecutableD3D.h"
#include "libANGLE/renderer/d3d/VertexDataManager.h"
#include "libANGLE/renderer/renderer_utils.h"
#include "libANGLE/trace.h"

using namespace angle;

namespace rx
{
namespace
{
bool HasFlatInterpolationVarying(const std::vector<sh::ShaderVariable> &varyings)
{
    // Note: this assumes nested structs can only be packed with one interpolation.
    for (const auto &varying : varyings)
    {
        if (varying.interpolation == sh::INTERPOLATION_FLAT)
        {
            return true;
        }
    }

    return false;
}

bool FindFlatInterpolationVaryingPerShader(const gl::SharedCompiledShaderState &shader)
{
    ASSERT(shader);
    switch (shader->shaderType)
    {
        case gl::ShaderType::Vertex:
            return HasFlatInterpolationVarying(shader->outputVaryings);
        case gl::ShaderType::Fragment:
            return HasFlatInterpolationVarying(shader->inputVaryings);
        case gl::ShaderType::Geometry:
            return HasFlatInterpolationVarying(shader->inputVaryings) ||
                   HasFlatInterpolationVarying(shader->outputVaryings);
        default:
            UNREACHABLE();
            return false;
    }
}

bool FindFlatInterpolationVarying(const gl::ShaderMap<gl::SharedCompiledShaderState> &shaders)
{
    for (gl::ShaderType shaderType : gl::kAllGraphicsShaderTypes)
    {
        const gl::SharedCompiledShaderState &shader = shaders[shaderType];
        if (!shader)
        {
            continue;
        }

        if (FindFlatInterpolationVaryingPerShader(shader))
        {
            return true;
        }
    }

    return false;
}

class HLSLBlockLayoutEncoderFactory : public gl::CustomBlockLayoutEncoderFactory
{
  public:
    sh::BlockLayoutEncoder *makeEncoder() override
    {
        return new sh::HLSLBlockEncoder(sh::HLSLBlockEncoder::ENCODE_PACKED, false);
    }
};

// GetExecutableTask class
class GetExecutableTask : public LinkSubTask, public d3d::Context
{
  public:
    GetExecutableTask(ProgramD3D *program, const SharedCompiledShaderStateD3D &shader)
        : mProgram(program), mExecutable(program->getExecutable()), mShader(shader)
    {}
    ~GetExecutableTask() override = default;

    angle::Result getResult(const gl::Context *context, gl::InfoLog &infoLog) override
    {
        ASSERT((mResult == angle::Result::Continue) == (mStoredHR == S_OK));

        ANGLE_TRY(checkTask(context, infoLog));

        // Append debug info
        if (mShader && mShaderExecutable != nullptr)
        {
            mShader->appendDebugInfo(mShaderExecutable->getDebugInfo());
        }

        return angle::Result::Continue;
    }

    void handleResult(HRESULT hr,
                      const char *message,
                      const char *file,
                      const char *function,
                      unsigned int line) override
    {
        mStoredHR       = hr;
        mStoredMessage  = message;
        mStoredFile     = file;
        mStoredFunction = function;
        mStoredLine     = line;
    }

  protected:
    void popError(d3d::Context *context)
    {
        ASSERT(mStoredFile);
        ASSERT(mStoredFunction);
        context->handleResult(mStoredHR, mStoredMessage.c_str(), mStoredFile, mStoredFunction,
                              mStoredLine);
    }

    angle::Result checkTask(const gl::Context *context, gl::InfoLog &infoLog)
    {
        // Forward any logs
        if (!mInfoLog.empty())
        {
            infoLog << mInfoLog.str();
        }

        // Forward any errors
        if (mResult != angle::Result::Continue)
        {
            ContextD3D *contextD3D = GetImplAs<ContextD3D>(context);
            popError(contextD3D);
            return angle::Result::Stop;
        }

        return angle::Result::Continue;
    }

    ProgramD3D *mProgram              = nullptr;
    ProgramExecutableD3D *mExecutable = nullptr;
    angle::Result mResult             = angle::Result::Continue;
    gl::InfoLog mInfoLog;
    ShaderExecutableD3D *mShaderExecutable = nullptr;
    SharedCompiledShaderStateD3D mShader;

    // Error handling
    HRESULT mStoredHR = S_OK;
    std::string mStoredMessage;
    const char *mStoredFile     = nullptr;
    const char *mStoredFunction = nullptr;
    unsigned int mStoredLine    = 0;
};
}  // anonymous namespace

// ProgramD3DMetadata Implementation
ProgramD3DMetadata::ProgramD3DMetadata(
    RendererD3D *renderer,
    const gl::SharedCompiledShaderState &fragmentShader,
    const gl::ShaderMap<SharedCompiledShaderStateD3D> &attachedShaders,
    EGLenum clientType,
    int shaderVersion)
    : mRendererMajorShaderModel(renderer->getMajorShaderModel()),
      mShaderModelSuffix(renderer->getShaderModelSuffix()),
      mUsesInstancedPointSpriteEmulation(
          renderer->getFeatures().useInstancedPointSpriteEmulation.enabled),
      mUsesViewScale(renderer->presentPathFastEnabled()),
      mCanSelectViewInVertexShader(renderer->canSelectViewInVertexShader()),
      mFragmentShader(fragmentShader),
      mAttachedShaders(attachedShaders),
      mClientType(clientType),
      mShaderVersion(shaderVersion)
{}

ProgramD3DMetadata::~ProgramD3DMetadata() = default;

int ProgramD3DMetadata::getRendererMajorShaderModel() const
{
    return mRendererMajorShaderModel;
}

bool ProgramD3DMetadata::usesBroadcast(const gl::Version &clientVersion) const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Fragment];
    return shader && shader->usesFragColor && shader->usesMultipleRenderTargets &&
           clientVersion.major < 3;
}

bool ProgramD3DMetadata::usesSecondaryColor() const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Fragment];
    return shader && shader->usesSecondaryColor;
}

FragDepthUsage ProgramD3DMetadata::getFragDepthUsage() const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Fragment];
    return shader ? shader->fragDepthUsage : FragDepthUsage::Unused;
}

bool ProgramD3DMetadata::usesPointCoord() const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Fragment];
    return shader && shader->usesPointCoord;
}

bool ProgramD3DMetadata::usesFragCoord() const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Fragment];
    return shader && shader->usesFragCoord;
}

bool ProgramD3DMetadata::usesPointSize() const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Vertex];
    return shader && shader->usesPointSize;
}

bool ProgramD3DMetadata::usesInsertedPointCoordValue() const
{
    return (!usesPointSize() || !mUsesInstancedPointSpriteEmulation) && usesPointCoord() &&
           mRendererMajorShaderModel >= 4;
}

bool ProgramD3DMetadata::usesViewScale() const
{
    return mUsesViewScale;
}

bool ProgramD3DMetadata::hasMultiviewEnabled() const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Vertex];
    return shader && shader->hasMultiviewEnabled;
}

bool ProgramD3DMetadata::usesVertexID() const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Vertex];
    return shader && shader->usesVertexID;
}

bool ProgramD3DMetadata::usesViewID() const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Fragment];
    return shader && shader->usesViewID;
}

bool ProgramD3DMetadata::canSelectViewInVertexShader() const
{
    return mCanSelectViewInVertexShader;
}

bool ProgramD3DMetadata::addsPointCoordToVertexShader() const
{
    // PointSprite emulation requiress that gl_PointCoord is present in the vertex shader
    // VS_OUTPUT structure to ensure compatibility with the generated PS_INPUT of the pixel shader.
    // Even with a geometry shader, the app can render triangles or lines and reference
    // gl_PointCoord in the fragment shader, requiring us to provide a placeholder value. For
    // simplicity, we always add this to the vertex shader when the fragment shader
    // references gl_PointCoord, even if we could skip it in the geometry shader.
    return (mUsesInstancedPointSpriteEmulation && usesPointCoord()) ||
           usesInsertedPointCoordValue();
}

bool ProgramD3DMetadata::usesTransformFeedbackGLPosition() const
{
    // gl_Position only needs to be outputted from the vertex shader if transform feedback is
    // active. This isn't supported on D3D11 Feature Level 9_3, so we don't output gl_Position from
    // the vertex shader in this case. This saves us 1 output vector.
    return !(mRendererMajorShaderModel >= 4 && mShaderModelSuffix != "");
}

bool ProgramD3DMetadata::usesSystemValuePointSize() const
{
    return !mUsesInstancedPointSpriteEmulation && usesPointSize();
}

bool ProgramD3DMetadata::usesMultipleFragmentOuts() const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Fragment];
    return shader && shader->usesMultipleRenderTargets;
}

bool ProgramD3DMetadata::usesCustomOutVars() const
{
    switch (mClientType)
    {
        case EGL_OPENGL_API:
            return mShaderVersion >= 130;
        default:
            return mShaderVersion >= 300;
    }
}

bool ProgramD3DMetadata::usesSampleMask() const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Fragment];
    return shader && shader->usesSampleMask;
}

const gl::SharedCompiledShaderState &ProgramD3DMetadata::getFragmentShader() const
{
    return mFragmentShader;
}

uint8_t ProgramD3DMetadata::getClipDistanceArraySize() const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Vertex];
    return shader ? shader->clipDistanceSize : 0;
}

uint8_t ProgramD3DMetadata::getCullDistanceArraySize() const
{
    const SharedCompiledShaderStateD3D &shader = mAttachedShaders[gl::ShaderType::Vertex];
    return shader ? shader->cullDistanceSize : 0;
}

// ProgramD3D Implementation

class ProgramD3D::GetVertexExecutableTask : public GetExecutableTask
{
  public:
    GetVertexExecutableTask(ProgramD3D *program, const SharedCompiledShaderStateD3D &shader)
        : GetExecutableTask(program, shader)
    {}
    ~GetVertexExecutableTask() override = default;

    void operator()() override
    {
        ANGLE_TRACE_EVENT0("gpu.angle", "GetVertexExecutableTask::run");

        mExecutable->updateCachedImage2DBindLayoutFromShader(gl::ShaderType::Vertex);
        mResult = mExecutable->getVertexExecutableForCachedInputLayout(
            this, mProgram->mRenderer, &mShaderExecutable, &mInfoLog);
    }
};

class ProgramD3D::GetPixelExecutableTask : public GetExecutableTask
{
  public:
    GetPixelExecutableTask(ProgramD3D *program, const SharedCompiledShaderStateD3D &shader)
        : GetExecutableTask(program, shader)
    {}
    ~GetPixelExecutableTask() override = default;

    void operator()() override
    {
        ANGLE_TRACE_EVENT0("gpu.angle", "GetPixelExecutableTask::run");
        if (!mShader)
        {
            return;
        }

        mExecutable->updateCachedOutputLayoutFromShader();
        mExecutable->updateCachedImage2DBindLayoutFromShader(gl::ShaderType::Fragment);
        mResult = mExecutable->getPixelExecutableForCachedOutputLayout(
            this, mProgram->mRenderer, &mShaderExecutable, &mInfoLog);
    }
};

class ProgramD3D::GetGeometryExecutableTask : public GetExecutableTask
{
  public:
    GetGeometryExecutableTask(ProgramD3D *program,
                              const SharedCompiledShaderStateD3D &shader,
                              const gl::Caps &caps,
                              gl::ProvokingVertexConvention provokingVertex)
        : GetExecutableTask(program, shader), mCaps(caps), mProvokingVertex(provokingVertex)
    {}
    ~GetGeometryExecutableTask() override = default;

    void operator()() override
    {
        ANGLE_TRACE_EVENT0("gpu.angle", "GetGeometryExecutableTask::run");
        // Auto-generate the geometry shader here, if we expect to be using point rendering in
        // D3D11.
        if (mExecutable->usesGeometryShader(mProgram->mRenderer, mProvokingVertex,
                                            gl::PrimitiveMode::Points))
        {
            mResult = mExecutable->getGeometryExecutableForPrimitiveType(
                this, mProgram->mRenderer, mCaps, mProvokingVertex, gl::PrimitiveMode::Points,
                &mShaderExecutable, &mInfoLog);
        }
    }

  private:
    const gl::Caps &mCaps;
    gl::ProvokingVertexConvention mProvokingVertex;
};

class ProgramD3D::GetComputeExecutableTask : public GetExecutableTask
{
  public:
    GetComputeExecutableTask(ProgramD3D *program, const SharedCompiledShaderStateD3D &shader)
        : GetExecutableTask(program, shader)
    {}
    ~GetComputeExecutableTask() override = default;

    void operator()() override
    {
        ANGLE_TRACE_EVENT0("gpu.angle", "GetComputeExecutableTask::run");

        mExecutable->updateCachedImage2DBindLayoutFromShader(gl::ShaderType::Compute);
        mResult = mExecutable->getComputeExecutableForImage2DBindLayout(
            this, mProgram->mRenderer, &mShaderExecutable, &mInfoLog);
    }
};

class ProgramD3D::LinkLoadTaskD3D : public d3d::Context, public LinkTask
{
  public:
    LinkLoadTaskD3D(ProgramD3D *program) : mProgram(program), mExecutable(program->getExecutable())
    {
        ASSERT(mProgram);
    }
    ~LinkLoadTaskD3D() override = default;

    angle::Result getResult(const gl::Context *context, gl::InfoLog &infoLog) override
    {
        if (mStoredHR != S_OK)
        {
            GetImplAs<ContextD3D>(context)->handleResult(mStoredHR, mStoredMessage.c_str(),
                                                         mStoredFile, mStoredFunction, mStoredLine);
            return angle::Result::Stop;
        }

        return angle::Result::Continue;
    }

    void handleResult(HRESULT hr,
                      const char *message,
                      const char *file,
                      const char *function,
                      unsigned int line) override
    {
        mStoredHR       = hr;
        mStoredMessage  = message;
        mStoredFile     = file;
        mStoredFunction = function;
        mStoredLine     = line;
    }

  protected:
    // The front-end ensures that the program is not accessed while linking, so it is safe to
    // direclty access the state from a potentially parallel job.
    ProgramD3D *mProgram;
    ProgramExecutableD3D *mExecutable = nullptr;

    // Error handling
    HRESULT mStoredHR = S_OK;
    std::string mStoredMessage;
    const char *mStoredFile     = nullptr;
    const char *mStoredFunction = nullptr;
    unsigned int mStoredLine    = 0;
};

class ProgramD3D::LinkTaskD3D final : public LinkLoadTaskD3D
{
  public:
    LinkTaskD3D(const gl::Version &clientVersion,
                const gl::Caps &caps,
                EGLenum clientType,
                ProgramD3D *program,
                gl::ProvokingVertexConvention provokingVertex)
        : LinkLoadTaskD3D(program),
          mClientVersion(clientVersion),
          mCaps(caps),
          mClientType(clientType),
          mProvokingVertex(provokingVertex)
    {}
    ~LinkTaskD3D() override = default;

    std::vector<std::shared_ptr<LinkSubTask>> link(const gl::ProgramLinkedResources &resources,
                                                   const gl::ProgramMergedVaryings &mergedVaryings,
                                                   bool *areSubTasksOptionalOut) override;

  private:
    const gl::Version mClientVersion;
    const gl::Caps &mCaps;
    const EGLenum mClientType;
    const gl::ProvokingVertexConvention mProvokingVertex;
};

std::vector<std::shared_ptr<LinkSubTask>> ProgramD3D::LinkTaskD3D::link(
    const gl::ProgramLinkedResources &resources,
    const gl::ProgramMergedVaryings &mergedVaryings,
    bool *areSubTasksOptionalOut)
{
    ANGLE_TRACE_EVENT0("gpu.angle", "LinkTaskD3D::link");

    angle::Result result =
        mProgram->linkJobImpl(this, mCaps, mClientVersion, mClientType, resources, mergedVaryings);
    ASSERT((result == angle::Result::Continue) == (mStoredHR == S_OK));

    if (result != angle::Result::Continue)
    {
        return {};
    }

    // Create the subtasks
    std::vector<std::shared_ptr<LinkSubTask>> subTasks;

    if (mExecutable->hasShaderStage(gl::ShaderType::Compute))
    {
        subTasks.push_back(std::make_shared<GetComputeExecutableTask>(
            mProgram, mProgram->getAttachedShader(gl::ShaderType::Compute)));
    }
    else
    {
        // Geometry shaders are currently only used internally, so there is no corresponding shader
        // object at the interface level. For now the geometry shader debug info is prepended to the
        // vertex shader.
        subTasks.push_back(std::make_shared<GetVertexExecutableTask>(
            mProgram, mProgram->getAttachedShader(gl::ShaderType::Vertex)));
        subTasks.push_back(std::make_shared<GetPixelExecutableTask>(
            mProgram, mProgram->getAttachedShader(gl::ShaderType::Fragment)));
        subTasks.push_back(std::make_shared<GetGeometryExecutableTask>(
            mProgram, mProgram->getAttachedShader(gl::ShaderType::Vertex), mCaps,
            mProvokingVertex));
    }

    *areSubTasksOptionalOut = false;
    return subTasks;
}

class ProgramD3D::LoadTaskD3D final : public LinkLoadTaskD3D
{
  public:
    LoadTaskD3D(ProgramD3D *program, angle::MemoryBuffer &&streamData)
        : LinkLoadTaskD3D(program), mStreamData(std::move(streamData))
    {}
    ~LoadTaskD3D() override = default;

    std::vector<std::shared_ptr<LinkSubTask>> load(bool *areSubTasksOptionalOut) override
    {
        ANGLE_TRACE_EVENT0("gpu.angle", "LoadTaskD3D::load");

        gl::BinaryInputStream stream(mStreamData.data(), mStreamData.size());
        mResult = mExecutable->loadBinaryShaderExecutables(this, mProgram->mRenderer, &stream);

        return {};
    }

    angle::Result getResult(const gl::Context *context, gl::InfoLog &infoLog) override
    {
        ANGLE_TRY(LinkLoadTaskD3D::getResult(context, infoLog));
        return mResult;
    }

  private:
    angle::MemoryBuffer mStreamData;
    angle::Result mResult = angle::Result::Continue;
};

ProgramD3D::ProgramD3D(const gl::ProgramState &state, RendererD3D *renderer)
    : ProgramImpl(state), mRenderer(renderer)
{}

ProgramD3D::~ProgramD3D() = default;

void ProgramD3D::destroy(const gl::Context *context)
{
    getExecutable()->reset();
}

angle::Result ProgramD3D::load(const gl::Context *context,
                               gl::BinaryInputStream *stream,
                               std::shared_ptr<LinkTask> *loadTaskOut,
                               egl::CacheGetResult *resultOut)
{
    if (!getExecutable()->load(context, mRenderer, stream))
    {
        mState.getExecutable().getInfoLog()
            << "Invalid program binary, device configuration has changed.";
        return angle::Result::Continue;
    }

    // Copy the remaining data from the stream locally so that the client can't modify it when
    // loading off thread.
    angle::MemoryBuffer streamData;
    const size_t dataSize = stream->remainingSize();
    if (!streamData.resize(dataSize))
    {
        mState.getExecutable().getInfoLog()
            << "Failed to copy program binary data to local buffer.";
        return angle::Result::Stop;
    }
    memcpy(streamData.data(), stream->data() + stream->offset(), dataSize);

    // Note: pretty much all the above can also be moved to the task
    *loadTaskOut = std::shared_ptr<LinkTask>(new LoadTaskD3D(this, std::move(streamData)));
    *resultOut   = egl::CacheGetResult::GetSuccess;

    return angle::Result::Continue;
}

void ProgramD3D::save(const gl::Context *context, gl::BinaryOutputStream *stream)
{
    getExecutable()->save(context, mRenderer, stream);
}

void ProgramD3D::setBinaryRetrievableHint(bool /* retrievable */) {}

void ProgramD3D::setSeparable(bool /* separable */) {}

void ProgramD3D::prepareForLink(const gl::ShaderMap<ShaderImpl *> &shaders)
{
    ProgramExecutableD3D *executableD3D = getExecutable();

    for (gl::ShaderType shaderType : gl::AllShaderTypes())
    {
        executableD3D->mAttachedShaders[shaderType].reset();

        if (shaders[shaderType] != nullptr)
        {
            const ShaderD3D *shaderD3D                  = GetAs<ShaderD3D>(shaders[shaderType]);
            executableD3D->mAttachedShaders[shaderType] = shaderD3D->getCompiledState();
        }
    }
}

angle::Result ProgramD3D::link(const gl::Context *context, std::shared_ptr<LinkTask> *linkTaskOut)
{
    ANGLE_TRACE_EVENT0("gpu.angle", "ProgramD3D::link");
    const gl::Version &clientVersion = context->getClientVersion();
    const gl::Caps &caps             = context->getCaps();
    EGLenum clientType               = context->getClientType();

    // Ensure the compiler is initialized to avoid race conditions.
    ANGLE_TRY(mRenderer->ensureHLSLCompilerInitialized(GetImplAs<ContextD3D>(context)));

    *linkTaskOut = std::shared_ptr<LinkTask>(new LinkTaskD3D(
        clientVersion, caps, clientType, this, context->getState().getProvokingVertex()));

    return angle::Result::Continue;
}

angle::Result ProgramD3D::linkJobImpl(d3d::Context *context,
                                      const gl::Caps &caps,
                                      const gl::Version &clientVersion,
                                      EGLenum clientType,
                                      const gl::ProgramLinkedResources &resources,
                                      const gl::ProgramMergedVaryings &mergedVaryings)
{
    ProgramExecutableD3D *executableD3D = getExecutable();

    const gl::SharedCompiledShaderState &computeShader =
        mState.getAttachedShader(gl::ShaderType::Compute);
    if (computeShader)
    {
        const gl::SharedCompiledShaderState &shader =
            mState.getAttachedShader(gl::ShaderType::Compute);
        executableD3D->mShaderHLSL[gl::ShaderType::Compute] = shader->translatedSource;

        executableD3D->mShaderSamplers[gl::ShaderType::Compute].resize(
            caps.maxShaderTextureImageUnits[gl::ShaderType::Compute]);
        executableD3D->mImages[gl::ShaderType::Compute].resize(caps.maxImageUnits);
        executableD3D->mReadonlyImages[gl::ShaderType::Compute].resize(caps.maxImageUnits);

        executableD3D->mShaderUniformsDirty.set(gl::ShaderType::Compute);

        linkResources(resources);

        for (const sh::ShaderVariable &uniform : computeShader->uniforms)
        {
            if (gl::IsImageType(uniform.type) && gl::IsImage2DType(uniform.type))
            {
                executableD3D->mImage2DUniforms[gl::ShaderType::Compute].push_back(uniform);
            }
        }

        executableD3D->defineUniformsAndAssignRegisters(mRenderer, mState.getAttachedShaders());

        return angle::Result::Continue;
    }

    for (gl::ShaderType shaderType : gl::kAllGraphicsShaderTypes)
    {
        const gl::SharedCompiledShaderState &shader = mState.getAttachedShader(shaderType);
        if (shader)
        {
            executableD3D->mAttachedShaders[shaderType]->generateWorkarounds(
                &executableD3D->mShaderWorkarounds[shaderType]);
            executableD3D->mShaderSamplers[shaderType].resize(
                caps.maxShaderTextureImageUnits[shaderType]);
            executableD3D->mImages[shaderType].resize(caps.maxImageUnits);
            executableD3D->mReadonlyImages[shaderType].resize(caps.maxImageUnits);

            executableD3D->mShaderUniformsDirty.set(shaderType);

            for (const sh::ShaderVariable &uniform : shader->uniforms)
            {
                if (gl::IsImageType(uniform.type) && gl::IsImage2DType(uniform.type))
                {
                    executableD3D->mImage2DUniforms[shaderType].push_back(uniform);
                }
            }

            for (const std::string &slowBlock :
                 executableD3D->mAttachedShaders[shaderType]->slowCompilingUniformBlockSet)
            {
                WARN() << "Uniform block '" << slowBlock << "' will be slow to compile. "
                       << "See UniformBlockToStructuredBufferTranslation.md "
                       << "(https://shorturl.at/drFY7) for details.";
            }
        }
    }

    if (mRenderer->getNativeLimitations().noFrontFacingSupport)
    {
        const SharedCompiledShaderStateD3D &fragmentShader =
            executableD3D->mAttachedShaders[gl::ShaderType::Fragment];
        if (fragmentShader && fragmentShader->usesFrontFacing)
        {
            mState.getExecutable().getInfoLog()
                << "The current renderer doesn't support gl_FrontFacing";
            // Fail compilation
            ANGLE_CHECK_HR(context, false, "gl_FrontFacing not supported", E_NOTIMPL);
        }
    }

    const gl::VaryingPacking &varyingPacking =
        resources.varyingPacking.getOutputPacking(gl::ShaderType::Vertex);

    ProgramD3DMetadata metadata(mRenderer, mState.getAttachedShader(gl::ShaderType::Fragment),
                                executableD3D->mAttachedShaders, clientType,
                                mState.getAttachedShader(gl::ShaderType::Vertex)->shaderVersion);
    BuiltinVaryingsD3D builtins(metadata, varyingPacking);

    DynamicHLSL::GenerateShaderLinkHLSL(mRenderer, caps, mState.getAttachedShaders(),
                                        executableD3D->mAttachedShaders, metadata, varyingPacking,
                                        builtins, &executableD3D->mShaderHLSL);

    executableD3D->mUsesPointSize =
        executableD3D->mAttachedShaders[gl::ShaderType::Vertex] &&
        executableD3D->mAttachedShaders[gl::ShaderType::Vertex]->usesPointSize;
    DynamicHLSL::GetPixelShaderOutputKey(mRenderer, caps, clientVersion, mState.getExecutable(),
                                         metadata, &executableD3D->mPixelShaderKey);
    executableD3D->mFragDepthUsage      = metadata.getFragDepthUsage();
    executableD3D->mUsesSampleMask      = metadata.usesSampleMask();
    executableD3D->mUsesVertexID        = metadata.usesVertexID();
    executableD3D->mUsesViewID          = metadata.usesViewID();
    executableD3D->mHasMultiviewEnabled = metadata.hasMultiviewEnabled();

    // Cache if we use flat shading
    executableD3D->mUsesFlatInterpolation =
        FindFlatInterpolationVarying(mState.getAttachedShaders());

    if (mRenderer->getMajorShaderModel() >= 4)
    {
        executableD3D->mGeometryShaderPreamble = DynamicHLSL::GenerateGeometryShaderPreamble(
            mRenderer, varyingPacking, builtins, executableD3D->mHasMultiviewEnabled,
            metadata.canSelectViewInVertexShader());
    }

    executableD3D->initAttribLocationsToD3DSemantic(
        mState.getAttachedShader(gl::ShaderType::Vertex));

    executableD3D->defineUniformsAndAssignRegisters(mRenderer, mState.getAttachedShaders());

    executableD3D->gatherTransformFeedbackVaryings(mRenderer, varyingPacking,
                                                   mState.getTransformFeedbackVaryingNames(),
                                                   builtins[gl::ShaderType::Vertex]);

    linkResources(resources);

    if (mState.getAttachedShader(gl::ShaderType::Vertex))
    {
        executableD3D->updateCachedInputLayoutFromShader(
            mRenderer, mState.getAttachedShader(gl::ShaderType::Vertex));
    }

    return angle::Result::Continue;
}

GLboolean ProgramD3D::validate(const gl::Caps & /*caps*/)
{
    // TODO(jmadill): Do something useful here?
    return GL_TRUE;
}

void ProgramD3D::linkResources(const gl::ProgramLinkedResources &resources)
{
    HLSLBlockLayoutEncoderFactory hlslEncoderFactory;
    gl::ProgramLinkedResourcesLinker linker(&hlslEncoderFactory);

    linker.linkResources(mState, resources);

    ProgramExecutableD3D *executableD3D = getExecutable();

    executableD3D->initializeUniformBlocks();
    executableD3D->initializeShaderStorageBlocks(mState.getAttachedShaders());
}

}  // namespace rx
