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

// RenderStateCache.cpp: Defines rx::RenderStateCache, a cache of Direct3D render
// state objects.

#include "libANGLE/renderer/d3d/d3d11/RenderStateCache.h"

#include <float.h>

#include "common/Color.h"
#include "common/debug.h"
#include "libANGLE/Context.h"
#include "libANGLE/Framebuffer.h"
#include "libANGLE/FramebufferAttachment.h"
#include "libANGLE/renderer/d3d/d3d11/Context11.h"
#include "libANGLE/renderer/d3d/d3d11/Framebuffer11.h"
#include "libANGLE/renderer/d3d/d3d11/Renderer11.h"
#include "libANGLE/renderer/d3d/d3d11/renderer11_utils.h"

namespace rx
{
using namespace gl_d3d11;

RenderStateCache::RenderStateCache()
    : mBlendStateCache(kMaxStates),
      mRasterizerStateCache(kMaxStates),
      mDepthStencilStateCache(kMaxStates),
      mSamplerStateCache(kMaxStates)
{}

RenderStateCache::~RenderStateCache() {}

void RenderStateCache::clear()
{
    mBlendStateCache.Clear();
    mRasterizerStateCache.Clear();
    mDepthStencilStateCache.Clear();
    mSamplerStateCache.Clear();
}

// static
d3d11::BlendStateKey RenderStateCache::GetBlendStateKey(const gl::Context *context,
                                                        Framebuffer11 *framebuffer11,
                                                        const gl::BlendStateExt &blendStateExt,
                                                        bool sampleAlphaToCoverage)
{
    d3d11::BlendStateKey key;
    // All fields of the BlendStateExt inside the key should be initialized for the caching to
    // work correctly. Due to mrt_perf_workaround, the actual indices of active draw buffers may be
    // different, so both arrays should be tracked.
    key.blendStateExt                      = gl::BlendStateExt(blendStateExt.mMaxDrawBuffers);
    const gl::AttachmentList &colorbuffers = framebuffer11->getColorAttachmentsForRender(context);
    const gl::DrawBufferMask colorAttachmentsForRenderMask =
        framebuffer11->getLastColorAttachmentsForRenderMask();

    ASSERT(blendStateExt.mMaxDrawBuffers <= colorAttachmentsForRenderMask.size());
    ASSERT(colorbuffers.size() == colorAttachmentsForRenderMask.count());

    size_t keyBlendIndex = 0;

    // With blending disabled, factors and equations are ignored when building
    // D3D11_RENDER_TARGET_BLEND_DESC, so we can reduce the amount of unique keys by
    // enforcing default values.
    for (size_t sourceIndex : colorAttachmentsForRenderMask)
    {
        ASSERT(keyBlendIndex < colorbuffers.size());
        const gl::FramebufferAttachment *attachment = colorbuffers[keyBlendIndex];

        // Do not set blend state for null attachments that may be present when
        // mrt_perf_workaround is disabled.
        if (attachment == nullptr)
        {
            keyBlendIndex++;
            continue;
        }

        const uint8_t colorMask = blendStateExt.getColorMaskIndexed(sourceIndex);

        const gl::InternalFormat &internalFormat = *attachment->getFormat().info;

        key.blendStateExt.setColorMaskIndexed(keyBlendIndex,
                                              gl_d3d11::GetColorMask(internalFormat) & colorMask);
        key.rtvMax = static_cast<uint16_t>(keyBlendIndex) + 1;

        // Some D3D11 drivers produce unexpected results when blending is enabled for integer
        // attachments. Per OpenGL ES spec, it must be ignored anyway. When blending is disabled,
        // the state remains default to reduce the number of unique keys.
        if (blendStateExt.mEnabledMask.test(sourceIndex) && !internalFormat.isInt())
        {
            key.blendStateExt.setEnabledIndexed(keyBlendIndex, true);
            key.blendStateExt.setEquationsIndexed(keyBlendIndex, sourceIndex, blendStateExt);
            key.blendStateExt.setFactorsIndexed(keyBlendIndex, sourceIndex, blendStateExt);
        }
        keyBlendIndex++;
    }

    key.sampleAlphaToCoverage = sampleAlphaToCoverage ? 1 : 0;
    return key;
}

angle::Result RenderStateCache::getBlendState(const gl::Context *context,
                                              Renderer11 *renderer,
                                              const d3d11::BlendStateKey &key,
                                              const d3d11::BlendState **outBlendState)
{
    auto keyIter = mBlendStateCache.Get(key);
    if (keyIter != mBlendStateCache.end())
    {
        *outBlendState = &keyIter->second;
        return angle::Result::Continue;
    }

    TrimCache(kMaxStates, kGCLimit, "blend state", &mBlendStateCache);

    // Create a new blend state and insert it into the cache
    D3D11_BLEND_DESC blendDesc             = {};  // avoid undefined fields
    const gl::BlendStateExt &blendStateExt = key.blendStateExt;

    blendDesc.AlphaToCoverageEnable  = key.sampleAlphaToCoverage != 0 ? TRUE : FALSE;
    blendDesc.IndependentBlendEnable = key.rtvMax > 1 ? TRUE : FALSE;

    // D3D11 API always accepts an array of blend states. Its validity depends on the hardware
    // feature level. Given that we do not expose GL entrypoints that set per-buffer blend states on
    // systems lower than FL10_1, this array will be always valid.

    for (size_t i = 0; i < blendStateExt.mMaxDrawBuffers; i++)
    {
        D3D11_RENDER_TARGET_BLEND_DESC &rtDesc = blendDesc.RenderTarget[i];

        if (blendStateExt.mEnabledMask.test(i))
        {
            rtDesc.BlendEnable = true;
            rtDesc.SrcBlend =
                gl_d3d11::ConvertBlendFunc(blendStateExt.getSrcColorIndexed(i), false);
            rtDesc.DestBlend =
                gl_d3d11::ConvertBlendFunc(blendStateExt.getDstColorIndexed(i), false);
            rtDesc.BlendOp = gl_d3d11::ConvertBlendOp(blendStateExt.getEquationColorIndexed(i));
            rtDesc.SrcBlendAlpha =
                gl_d3d11::ConvertBlendFunc(blendStateExt.getSrcAlphaIndexed(i), true);
            rtDesc.DestBlendAlpha =
                gl_d3d11::ConvertBlendFunc(blendStateExt.getDstAlphaIndexed(i), true);
            rtDesc.BlendOpAlpha =
                gl_d3d11::ConvertBlendOp(blendStateExt.getEquationAlphaIndexed(i));
        }

        // blendStateExt.colorMask follows the same packing scheme as
        // D3D11_RENDER_TARGET_BLEND_DESC.RenderTargetWriteMask
        rtDesc.RenderTargetWriteMask = blendStateExt.getColorMaskIndexed(i);
    }

    d3d11::BlendState d3dBlendState;
    ANGLE_TRY(renderer->allocateResource(GetImplAs<Context11>(context), blendDesc, &d3dBlendState));
    const auto &iter = mBlendStateCache.Put(key, std::move(d3dBlendState));

    *outBlendState = &iter->second;

    return angle::Result::Continue;
}

angle::Result RenderStateCache::getRasterizerState(const gl::Context *context,
                                                   Renderer11 *renderer,
                                                   const gl::RasterizerState &rasterState,
                                                   bool scissorEnabled,
                                                   ID3D11RasterizerState **outRasterizerState)
{
    d3d11::RasterizerStateKey key;
    key.rasterizerState = rasterState;
    key.scissorEnabled  = scissorEnabled ? 1 : 0;

    auto keyIter = mRasterizerStateCache.Get(key);
    if (keyIter != mRasterizerStateCache.end())
    {
        *outRasterizerState = keyIter->second.get();
        return angle::Result::Continue;
    }

    TrimCache(kMaxStates, kGCLimit, "rasterizer state", &mRasterizerStateCache);

    D3D11_CULL_MODE cullMode =
        gl_d3d11::ConvertCullMode(rasterState.cullFace, rasterState.cullMode);

    // Disable culling if drawing points
    if (rasterState.pointDrawMode)
    {
        cullMode = D3D11_CULL_NONE;
    }

    D3D11_RASTERIZER_DESC rasterDesc;
    rasterDesc.FillMode              = D3D11_FILL_SOLID;
    rasterDesc.CullMode              = cullMode;
    rasterDesc.FrontCounterClockwise = (rasterState.frontFace == GL_CCW) ? FALSE : TRUE;
    rasterDesc.DepthBiasClamp = 0.0f;  // MSDN documentation of DepthBiasClamp implies a value of
                                       // zero will preform no clamping, must be tested though.
    rasterDesc.DepthClipEnable       = TRUE;
    rasterDesc.ScissorEnable         = scissorEnabled ? TRUE : FALSE;
    rasterDesc.MultisampleEnable     = rasterState.multiSample;
    rasterDesc.AntialiasedLineEnable = FALSE;

    if (rasterState.polygonOffsetFill)
    {
        rasterDesc.SlopeScaledDepthBias = rasterState.polygonOffsetFactor;
        rasterDesc.DepthBias            = (INT)rasterState.polygonOffsetUnits;
    }
    else
    {
        rasterDesc.SlopeScaledDepthBias = 0.0f;
        rasterDesc.DepthBias            = 0;
    }

    d3d11::RasterizerState dx11RasterizerState;
    ANGLE_TRY(renderer->allocateResource(GetImplAs<Context11>(context), rasterDesc,
                                         &dx11RasterizerState));
    *outRasterizerState = dx11RasterizerState.get();
    mRasterizerStateCache.Put(key, std::move(dx11RasterizerState));

    return angle::Result::Continue;
}

angle::Result RenderStateCache::getDepthStencilState(const gl::Context *context,
                                                     Renderer11 *renderer,
                                                     const gl::DepthStencilState &glState,
                                                     const d3d11::DepthStencilState **outDSState)
{
    auto keyIter = mDepthStencilStateCache.Get(glState);
    if (keyIter != mDepthStencilStateCache.end())
    {
        *outDSState = &keyIter->second;
        return angle::Result::Continue;
    }

    TrimCache(kMaxStates, kGCLimit, "depth stencil state", &mDepthStencilStateCache);

    D3D11_DEPTH_STENCIL_DESC dsDesc     = {};
    dsDesc.DepthEnable                  = glState.depthTest ? TRUE : FALSE;
    dsDesc.DepthWriteMask               = ConvertDepthMask(glState.depthMask);
    dsDesc.DepthFunc                    = ConvertComparison(glState.depthFunc);
    dsDesc.StencilEnable                = glState.stencilTest ? TRUE : FALSE;
    dsDesc.StencilReadMask              = ConvertStencilMask(glState.stencilMask);
    dsDesc.StencilWriteMask             = ConvertStencilMask(glState.stencilWritemask);
    dsDesc.FrontFace.StencilFailOp      = ConvertStencilOp(glState.stencilFail);
    dsDesc.FrontFace.StencilDepthFailOp = ConvertStencilOp(glState.stencilPassDepthFail);
    dsDesc.FrontFace.StencilPassOp      = ConvertStencilOp(glState.stencilPassDepthPass);
    dsDesc.FrontFace.StencilFunc        = ConvertComparison(glState.stencilFunc);
    dsDesc.BackFace.StencilFailOp       = ConvertStencilOp(glState.stencilBackFail);
    dsDesc.BackFace.StencilDepthFailOp  = ConvertStencilOp(glState.stencilBackPassDepthFail);
    dsDesc.BackFace.StencilPassOp       = ConvertStencilOp(glState.stencilBackPassDepthPass);
    dsDesc.BackFace.StencilFunc         = ConvertComparison(glState.stencilBackFunc);

    d3d11::DepthStencilState dx11DepthStencilState;
    ANGLE_TRY(
        renderer->allocateResource(GetImplAs<Context11>(context), dsDesc, &dx11DepthStencilState));
    const auto &iter = mDepthStencilStateCache.Put(glState, std::move(dx11DepthStencilState));

    *outDSState = &iter->second;

    return angle::Result::Continue;
}

angle::Result RenderStateCache::getSamplerState(const gl::Context *context,
                                                Renderer11 *renderer,
                                                const gl::SamplerState &samplerState,
                                                ID3D11SamplerState **outSamplerState)
{
    auto keyIter = mSamplerStateCache.Get(samplerState);
    if (keyIter != mSamplerStateCache.end())
    {
        *outSamplerState = keyIter->second.get();
        return angle::Result::Continue;
    }

    TrimCache(kMaxStates, kGCLimit, "sampler state", &mSamplerStateCache);

    const auto &featureLevel = renderer->getRenderer11DeviceCaps().featureLevel;

    D3D11_SAMPLER_DESC samplerDesc;
    samplerDesc.Filter =
        gl_d3d11::ConvertFilter(samplerState.getMinFilter(), samplerState.getMagFilter(),
                                samplerState.getMaxAnisotropy(), samplerState.getCompareMode());
    samplerDesc.AddressU   = gl_d3d11::ConvertTextureWrap(samplerState.getWrapS());
    samplerDesc.AddressV   = gl_d3d11::ConvertTextureWrap(samplerState.getWrapT());
    samplerDesc.AddressW   = gl_d3d11::ConvertTextureWrap(samplerState.getWrapR());
    samplerDesc.MipLODBias = 0;
    samplerDesc.MaxAnisotropy =
        gl_d3d11::ConvertMaxAnisotropy(samplerState.getMaxAnisotropy(), featureLevel);
    samplerDesc.ComparisonFunc = gl_d3d11::ConvertComparison(samplerState.getCompareFunc());
    angle::ColorF borderColor;
    if (samplerState.getBorderColor().type == angle::ColorGeneric::Type::Float)
    {
        borderColor = samplerState.getBorderColor().colorF;
    }
    samplerDesc.BorderColor[0] = borderColor.red;
    samplerDesc.BorderColor[1] = borderColor.green;
    samplerDesc.BorderColor[2] = borderColor.blue;
    samplerDesc.BorderColor[3] = borderColor.alpha;
    samplerDesc.MinLOD         = samplerState.getMinLod();
    samplerDesc.MaxLOD         = samplerState.getMaxLod();

    if (featureLevel <= D3D_FEATURE_LEVEL_9_3)
    {
        // Check that maxLOD is nearly FLT_MAX (1000.0f is the default), since 9_3 doesn't support
        // anything other than FLT_MAX. Note that Feature Level 9_* only supports GL ES 2.0, so the
        // consumer of ANGLE can't modify the Max LOD themselves.
        ASSERT(samplerState.getMaxLod() >= 999.9f);

        // Now just set MaxLOD to FLT_MAX. Other parts of the renderer (e.g. the non-zero max LOD
        // workaround) should take account of this.
        samplerDesc.MaxLOD = FLT_MAX;
    }

    d3d11::SamplerState dx11SamplerState;
    ANGLE_TRY(
        renderer->allocateResource(GetImplAs<Context11>(context), samplerDesc, &dx11SamplerState));
    *outSamplerState = dx11SamplerState.get();
    mSamplerStateCache.Put(samplerState, std::move(dx11SamplerState));

    return angle::Result::Continue;
}

}  // namespace rx
