//
// Copyright 2019 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.
//
// mtl_state_cache.mm:
//    Implements StateCache, RenderPipelineCache and various
//    C struct versions of Metal sampler, depth stencil, render pass, render pipeline descriptors.
//

#include "libANGLE/renderer/metal/mtl_state_cache.h"

#include <sstream>

#include "common/debug.h"
#include "common/span.h"
#include "common/unsafe_buffers.h"
#include "libANGLE/renderer/metal/ContextMtl.h"
#include "libANGLE/renderer/metal/mtl_resources.h"
#include "libANGLE/renderer/metal/mtl_utils.h"
#include "platform/autogen/FeaturesMtl_autogen.h"

namespace rx
{
namespace mtl
{

namespace
{

inline angle::ObjCPtr<MTLStencilDescriptor> ToObjC(const StencilDesc &desc)
{
    auto objCDesc = angle::adoptObjCPtr([[MTLStencilDescriptor alloc] init]);
    objCDesc.get().stencilFailureOperation = desc.getStencilFailureOperation();
    objCDesc.get().depthFailureOperation   = desc.getDepthFailureOperation();
    objCDesc.get().depthStencilPassOperation = desc.getDepthStencilPassOperation();
    objCDesc.get().stencilCompareFunction    = desc.getStencilCompareFunction();
    objCDesc.get().readMask                  = desc.readMask;
    objCDesc.get().writeMask                 = desc.writeMask;
    return objCDesc;
}

inline angle::ObjCPtr<MTLDepthStencilDescriptor> ToObjC(const DepthStencilDesc &desc)
{
    auto objCDesc = angle::adoptObjCPtr([[MTLDepthStencilDescriptor alloc] init]);
    objCDesc.get().backFaceStencil  = ToObjC(desc.backFaceStencil);
    objCDesc.get().frontFaceStencil = ToObjC(desc.frontFaceStencil);
    objCDesc.get().depthCompareFunction = desc.getDepthCompareFunction();
    objCDesc.get().depthWriteEnabled    = desc.isDepthWriteEnabled();
    return objCDesc;
}

inline angle::ObjCPtr<MTLSamplerDescriptor> ToObjC(const SamplerDesc &desc)
{
    auto objCDesc = angle::adoptObjCPtr([[MTLSamplerDescriptor alloc] init]);
    objCDesc.get().rAddressMode    = desc.getRAddressMode();
    objCDesc.get().sAddressMode    = desc.getSAddressMode();
    objCDesc.get().tAddressMode    = desc.getTAddressMode();
    objCDesc.get().minFilter       = desc.getMinFilter();
    objCDesc.get().magFilter       = desc.getMagFilter();
    objCDesc.get().mipFilter       = desc.getMipFilter();
    objCDesc.get().maxAnisotropy   = desc.getMaxAnisotropy();
    objCDesc.get().compareFunction = desc.getCompareFunction();
    return objCDesc;
}

inline angle::ObjCPtr<MTLVertexAttributeDescriptor> ToObjC(const VertexAttributeDesc &desc)
{
    auto objCDesc = angle::adoptObjCPtr([[MTLVertexAttributeDescriptor alloc] init]);
    objCDesc.get().format      = desc.getFormat();
    objCDesc.get().offset      = desc.getOffset();
    objCDesc.get().bufferIndex = desc.getBufferIndex();
    ASSERT(desc.getBufferIndex() >= kVboBindingIndexStart);
    return objCDesc;
}

inline angle::ObjCPtr<MTLVertexBufferLayoutDescriptor> ToObjC(const VertexBufferLayoutDesc &desc)
{
    auto objCDesc = angle::adoptObjCPtr([[MTLVertexBufferLayoutDescriptor alloc] init]);
    objCDesc.get().stepFunction = desc.getStepFunction();
    objCDesc.get().stepRate     = desc.stepRate;
    objCDesc.get().stride       = desc.stride;
    return objCDesc;
}

inline angle::ObjCPtr<MTLVertexDescriptor> ToObjC(const VertexDesc &desc)
{
    auto objCDesc = angle::adoptObjCPtr([[MTLVertexDescriptor alloc] init]);
    [objCDesc reset];

    for (uint8_t i = 0; i < desc.numAttribs; ++i)
    {
        [objCDesc.get().attributes setObject:ToObjC(ANGLE_UNSAFE_TODO(desc.attributes[i]))
                          atIndexedSubscript:i];
    }

    for (uint8_t i = 0; i < desc.numBufferLayouts; ++i)
    {
        // Ignore if stepFunction is kVertexStepFunctionInvalid.
        // If we don't set this slot, it will apparently be disabled by metal runtime.
        if (ANGLE_UNSAFE_TODO(desc.layouts[i].getStepFunction()) != kVertexStepFunctionInvalid)
        {
            [objCDesc.get().layouts setObject:ToObjC(ANGLE_UNSAFE_TODO(desc.layouts[i]))
                           atIndexedSubscript:i];
        }
    }

    return objCDesc;
}

inline angle::ObjCPtr<MTLRenderPipelineColorAttachmentDescriptor> ToObjC(
    const RenderPipelineColorAttachmentDesc &desc)
{
    auto objCDesc = angle::adoptObjCPtr([[MTLRenderPipelineColorAttachmentDescriptor alloc] init]);
    objCDesc.get().pixelFormat                 = desc.getPixelFormat();
    objCDesc.get().writeMask                   = desc.getWriteMask();
    objCDesc.get().sourceRGBBlendFactor        = desc.getSourceRgbBlendFactor();
    objCDesc.get().sourceAlphaBlendFactor      = desc.getSourceAlphaBlendFactor();
    objCDesc.get().destinationRGBBlendFactor   = desc.getDestinationRgbBlendFactor();
    objCDesc.get().destinationAlphaBlendFactor = desc.getDestinationAlphaBlendFactor();
    objCDesc.get().rgbBlendOperation           = desc.getRgbBlendOperation();
    objCDesc.get().alphaBlendOperation         = desc.getAlphaBlendOperation();
    objCDesc.get().blendingEnabled             = desc.isBlendingEnabled();
    return objCDesc;
}

id<MTLTexture> ToObjC(const TextureRef &texture)
{
    auto textureRef = texture;
    return textureRef ? textureRef->get() : nil;
}

void BaseRenderPassAttachmentDescToObjC(const RenderPassAttachmentDesc &src,
                                        MTLRenderPassAttachmentDescriptor *dst)
{
    const TextureRef &implicitMsTexture = src.implicitMSTexture;

    if (implicitMsTexture)
    {
        dst.texture        = ToObjC(implicitMsTexture);
        dst.level          = 0;
        dst.slice          = 0;
        dst.depthPlane     = 0;
        dst.resolveTexture = ToObjC(src.texture);
        dst.resolveLevel   = src.level.get();
        if (dst.resolveTexture.textureType == MTLTextureType3D)
        {
            dst.resolveDepthPlane = src.sliceOrDepth;
            dst.resolveSlice      = 0;
        }
        else
        {
            dst.resolveSlice      = src.sliceOrDepth;
            dst.resolveDepthPlane = 0;
        }
    }
    else
    {
        dst.texture = ToObjC(src.texture);
        dst.level   = src.level.get();
        if (dst.texture.textureType == MTLTextureType3D)
        {
            dst.depthPlane = src.sliceOrDepth;
            dst.slice      = 0;
        }
        else
        {
            dst.slice      = src.sliceOrDepth;
            dst.depthPlane = 0;
        }
        dst.resolveTexture    = nil;
        dst.resolveLevel      = 0;
        dst.resolveSlice      = 0;
        dst.resolveDepthPlane = 0;
    }

    dst.loadAction         = src.loadAction;
    dst.storeAction        = src.storeAction;
    dst.storeActionOptions = src.storeActionOptions;
}

void ToObjC(const RenderPassColorAttachmentDesc &desc,
            MTLRenderPassColorAttachmentDescriptor *objCDesc)
{
    BaseRenderPassAttachmentDescToObjC(desc, objCDesc);
    objCDesc.clearColor = desc.clearColor;
}

void ToObjC(const RenderPassDepthAttachmentDesc &desc,
            MTLRenderPassDepthAttachmentDescriptor *objCDesc)
{
    BaseRenderPassAttachmentDescToObjC(desc, objCDesc);
    objCDesc.clearDepth = desc.clearDepth;
}

void ToObjC(const RenderPassStencilAttachmentDesc &desc,
            MTLRenderPassStencilAttachmentDescriptor *objCDesc)
{
    BaseRenderPassAttachmentDescToObjC(desc, objCDesc);
    objCDesc.clearStencil = desc.clearStencil;
}

}  // namespace

void DepthStencilDesc::updateDepthTestEnabled(const gl::DepthStencilState &dsState)
{
    if (!dsState.depthTest)
    {
        setDepthWriteDisabled();
    }
    else
    {
        updateDepthCompareFunc(dsState);
        updateDepthWriteEnabled(dsState);
    }
}

void DepthStencilDesc::updateDepthWriteEnabled(const gl::DepthStencilState &dsState)
{
    mDepthWriteEnabled = dsState.depthTest && dsState.depthMask;
}

void DepthStencilDesc::updateDepthCompareFunc(const gl::DepthStencilState &dsState)
{
    if (!dsState.depthTest)
    {
        return;
    }
    mDepthCompareFunction = static_cast<uint32_t>(GetCompareFunc(dsState.depthFunc));
}

void DepthStencilDesc::updateStencilTestEnabled(const gl::DepthStencilState &dsState)
{
    if (!dsState.stencilTest)
    {
        frontFaceStencil.setStencilCompareFunction(MTLCompareFunctionAlways);
        frontFaceStencil.setDepthFailureOperation(MTLStencilOperationKeep);
        frontFaceStencil.setDepthStencilPassOperation(MTLStencilOperationKeep);
        frontFaceStencil.writeMask = 0;

        backFaceStencil.setStencilCompareFunction(MTLCompareFunctionAlways);
        backFaceStencil.setDepthFailureOperation(MTLStencilOperationKeep);
        backFaceStencil.setDepthStencilPassOperation(MTLStencilOperationKeep);
        backFaceStencil.writeMask                 = 0;
    }
    else
    {
        updateStencilFrontFuncs(dsState);
        updateStencilFrontOps(dsState);
        updateStencilFrontWriteMask(dsState);
        updateStencilBackFuncs(dsState);
        updateStencilBackOps(dsState);
        updateStencilBackWriteMask(dsState);
    }
}

void DepthStencilDesc::updateStencilFrontOps(const gl::DepthStencilState &dsState)
{
    if (!dsState.stencilTest)
    {
        return;
    }
    frontFaceStencil.setStencilFailureOperation(GetStencilOp(dsState.stencilFail));
    frontFaceStencil.setDepthFailureOperation(GetStencilOp(dsState.stencilPassDepthFail));
    frontFaceStencil.setDepthStencilPassOperation(GetStencilOp(dsState.stencilPassDepthPass));
}

void DepthStencilDesc::updateStencilBackOps(const gl::DepthStencilState &dsState)
{
    if (!dsState.stencilTest)
    {
        return;
    }
    backFaceStencil.setStencilFailureOperation(GetStencilOp(dsState.stencilBackFail));
    backFaceStencil.setDepthFailureOperation(GetStencilOp(dsState.stencilBackPassDepthFail));
    backFaceStencil.setDepthStencilPassOperation(GetStencilOp(dsState.stencilBackPassDepthPass));
}

void DepthStencilDesc::updateStencilFrontFuncs(const gl::DepthStencilState &dsState)
{
    if (!dsState.stencilTest)
    {
        return;
    }
    frontFaceStencil.setStencilCompareFunction(GetCompareFunc(dsState.stencilFunc));
    frontFaceStencil.readMask = dsState.stencilMask & mtl::kStencilMaskAll;
}

void DepthStencilDesc::updateStencilBackFuncs(const gl::DepthStencilState &dsState)
{
    if (!dsState.stencilTest)
    {
        return;
    }
    backFaceStencil.setStencilCompareFunction(GetCompareFunc(dsState.stencilBackFunc));
    backFaceStencil.readMask = dsState.stencilBackMask & mtl::kStencilMaskAll;
}

void DepthStencilDesc::updateStencilFrontWriteMask(const gl::DepthStencilState &dsState)
{
    if (!dsState.stencilTest)
    {
        return;
    }
    frontFaceStencil.writeMask = dsState.stencilWritemask & mtl::kStencilMaskAll;
}

void DepthStencilDesc::updateStencilBackWriteMask(const gl::DepthStencilState &dsState)
{
    if (!dsState.stencilTest)
    {
        return;
    }
    backFaceStencil.writeMask = dsState.stencilBackWritemask & mtl::kStencilMaskAll;
}

SamplerDesc::SamplerDesc(const gl::SamplerState &glState)
    : mRAddressMode(static_cast<uint32_t>(GetSamplerAddressMode(glState.getWrapR()))),
      mSAddressMode(static_cast<uint32_t>(GetSamplerAddressMode(glState.getWrapS()))),
      mTAddressMode(static_cast<uint32_t>(GetSamplerAddressMode(glState.getWrapT()))),
      mMinFilter(static_cast<uint32_t>(GetFilter(glState.getMinFilter()))),
      mMagFilter(static_cast<uint32_t>(GetFilter(glState.getMagFilter()))),
      mMipFilter(static_cast<uint32_t>(GetMipmapFilter(glState.getMinFilter()))),
      mMaxAnisotropy(static_cast<uint32_t>(glState.getMaxAnisotropy())),
      mCompareFunction(static_cast<uint32_t>(GetCompareFunc(glState.getCompareFunc())))
{
}

void BlendDesc::updateWriteMask(uint8_t angleMask)
{
    ASSERT(angleMask == (angleMask & 0xF));

// ANGLE's packed color mask is abgr (matches Vulkan & D3D11), while Metal expects rgba.
#if defined(__aarch64__)
    // ARM64 can reverse bits in a single instruction
    mWriteMask = __builtin_bitreverse8(angleMask) >> 4;
#else
    /* On other architectures, Clang generates a polyfill that uses more
       instructions than the following expression optimized for a 4-bit value.

       (abgr * 0x41) & 0x14A:
        .......abgr +
        .abgr...... &
        00101001010 =
        ..b.r..a.g.

       (b.r..a.g.) * 0x111:
                b.r..a.g. +
            b.r..a.g..... +
        b.r..a.g......... =
        b.r.bargbarg.a.g.
              ^^^^
    */
    mWriteMask = ((((angleMask * 0x41) & 0x14A) * 0x111) >> 7) & 0xF;
#endif
}

void RenderPipelineColorAttachmentDesc::reset(MTLPixelFormat format, MTLColorWriteMask writeMask)
{
    // Reset the entire BlendDesc to defaults (blendingEnabled=false, default factors/operations),
    // then set the specific values.
    BlendDesc::operator=({});
    mWriteMask   = static_cast<uint32_t>(writeMask);
    mPixelFormat = static_cast<uint32_t>(format);
}

void RenderPipelineColorAttachmentDesc::reset(MTLPixelFormat format, const BlendDesc &blendDesc)
{
    BlendDesc::operator=(blendDesc);
    mPixelFormat = static_cast<uint32_t>(format);
}

bool RenderPipelineOutputDesc::operator==(const RenderPipelineOutputDesc &rhs) const
{
    if (mNumColorAttachments != rhs.mNumColorAttachments)
    {
        return false;
    }

    for (uint8_t i = 0; i < mNumColorAttachments; ++i)
    {
        if (colorAttachments[i] != rhs.colorAttachments[i])
        {
            return false;
        }
    }

    return mDepthAttachmentPixelFormat == rhs.mDepthAttachmentPixelFormat &&
           mStencilAttachmentPixelFormat == rhs.mStencilAttachmentPixelFormat &&
           mRasterSampleCount == rhs.mRasterSampleCount;
}

size_t RenderPipelineOutputDesc::hash() const
{
    size_t hash = 0;
    angle::HashCombine(hash, mNumColorAttachments);
    for (uint8_t i = 0; i < mNumColorAttachments; ++i)
    {
        angle::HashCombine(hash, colorAttachments[i]);
    }
    angle::HashCombine(hash, mDepthAttachmentPixelFormat);
    angle::HashCombine(hash, mStencilAttachmentPixelFormat);
    angle::HashCombine(hash, mRasterSampleCount);
    return hash;
}

void RenderPipelineOutputDesc::updateEnabledDrawBuffers(gl::DrawBufferMask enabledBuffers)
{
    for (uint32_t colorIndex = 0; colorIndex < mNumColorAttachments; ++colorIndex)
    {
        if (!enabledBuffers.test(colorIndex))
        {
            colorAttachments[colorIndex].setWriteMask(MTLColorWriteMaskNone);
        }
    }
}

bool RenderPipelineDesc::rasterizationEnabled() const
{
    return mRasterizationType != static_cast<uint32_t>(RenderPipelineRasterization::Disabled);
}

angle::ObjCPtr<MTLRenderPipelineDescriptor> RenderPipelineDesc::createMetalDesc(
    id<MTLFunction> vertexShader,
    id<MTLFunction> fragmentShader) const
{
    auto objCDesc = angle::adoptObjCPtr([[MTLRenderPipelineDescriptor alloc] init]);
    [objCDesc reset];

    objCDesc.get().vertexDescriptor = ToObjC(vertexDescriptor);

    for (uint8_t i = 0; i < outputDescriptor.getNumColorAttachments(); ++i)
    {
        [objCDesc.get().colorAttachments setObject:ToObjC(outputDescriptor.colorAttachments[i])
                                atIndexedSubscript:i];
    }
    objCDesc.get().depthAttachmentPixelFormat = outputDescriptor.getDepthAttachmentPixelFormat();
    objCDesc.get().stencilAttachmentPixelFormat =
        outputDescriptor.getStencilAttachmentPixelFormat();
    objCDesc.get().rasterSampleCount      = outputDescriptor.getRasterSampleCount();
    objCDesc.get().inputPrimitiveTopology = getInputPrimitiveTopology();
    objCDesc.get().alphaToCoverageEnabled = getAlphaToCoverageEnabled();

    // rasterizationEnabled will be true for both EmulatedDiscard & Enabled.
    objCDesc.get().rasterizationEnabled = rasterizationEnabled();

    objCDesc.get().vertexFunction   = vertexShader;
    objCDesc.get().fragmentFunction = objCDesc.get().rasterizationEnabled ? fragmentShader : nil;

    return objCDesc;
}

bool RenderPassAttachmentDesc::equalIgnoreLoadStoreOptions(
    const RenderPassAttachmentDesc &other) const
{
    return texture == other.texture && implicitMSTexture == other.implicitMSTexture &&
           level == other.level && sliceOrDepth == other.sliceOrDepth &&
           blendable == other.blendable;
}

bool RenderPassAttachmentDesc::operator==(const RenderPassAttachmentDesc &other) const
{
    if (!equalIgnoreLoadStoreOptions(other))
    {
        return false;
    }

    return loadAction == other.loadAction && storeAction == other.storeAction &&
           storeActionOptions == other.storeActionOptions;
}

void RenderPassDesc::populateRenderPipelineOutputDesc(RenderPipelineOutputDesc *outDesc) const
{
    WriteMaskArray writeMaskArray;
    writeMaskArray.fill(MTLColorWriteMaskAll);
    populateRenderPipelineOutputDesc(writeMaskArray, outDesc);
}

void RenderPassDesc::populateRenderPipelineOutputDesc(const WriteMaskArray &writeMaskArray,
                                                      RenderPipelineOutputDesc *outDesc) const
{
    // Default blend state with replaced color write masks.
    BlendDescArray blendDescArray;
    for (size_t i = 0; i < blendDescArray.size(); i++)
    {
        blendDescArray[i].reset(writeMaskArray[i]);
    }
    populateRenderPipelineOutputDesc(blendDescArray, outDesc);
}

void RenderPassDesc::populateRenderPipelineOutputDesc(const BlendDescArray &blendDescArray,
                                                      RenderPipelineOutputDesc *outDesc) const
{
    outDesc->setNumColorAttachments(numColorAttachments);
    outDesc->setRasterSampleCount(rasterSampleCount);
    for (uint32_t i = 0; i < this->numColorAttachments; ++i)
    {
        auto &renderPassColorAttachment = colorAttachments[i];
        auto texture                    = renderPassColorAttachment.texture;
        auto &outColorAttachment        = outDesc->colorAttachments[i];
        if (texture)
        {
            if (renderPassColorAttachment.blendable &&
                blendDescArray[i].getWriteMask() != MTLColorWriteMaskNone)
            {
                // Copy parameters from blend state
                outColorAttachment.reset(texture->pixelFormat(), blendDescArray[i]);
            }
            else
            {
                // Disable blending if the attachment's render target doesn't support blending
                // or if all its color channels are masked out. The latter is needed because:
                //
                // * When blending is enabled and *Source1* blend factors are used, Metal
                //   requires a fragment shader to bind both primary and secondary outputs
                //
                // * ANGLE frontend validation allows draw calls on draw buffers without
                //   bound fragment outputs if all their color channels are masked out
                //
                // * When all color channels are masked out, blending has no effect anyway
                //
                // Besides disabling blending, use default values for factors and
                // operations to reduce the number of unique pipeline states.
                outColorAttachment.reset(texture->pixelFormat(), blendDescArray[i].getWriteMask());
            }

            // Combine the masks. This is useful when the texture is not supposed to have alpha
            // channel such as GL_RGB8, however, Metal doesn't natively support 24 bit RGB, so
            // we need to use RGBA texture, and then disable alpha write to this texture
            outColorAttachment.setWriteMask(outColorAttachment.getWriteMask() &
                                            texture->getColorWritableMask());
        }
        else
        {

            outColorAttachment.setBlendingDisabled();
            outColorAttachment.setPixelFormat(MTLPixelFormatInvalid);
        }
    }

    // Reset the unused output slots to ensure consistent hash value
    for (uint32_t i = this->numColorAttachments; i < outDesc->colorAttachments.size(); ++i)
    {
        outDesc->colorAttachments[i] = {};
    }

    auto depthTexture = depthAttachment.texture;
    outDesc->setDepthAttachmentPixelFormat(depthTexture ? depthTexture->pixelFormat()
                                                        : MTLPixelFormatInvalid);

    auto stencilTexture = stencilAttachment.texture;
    outDesc->setStencilAttachmentPixelFormat(stencilTexture ? stencilTexture->pixelFormat()
                                                            : MTLPixelFormatInvalid);
}

bool RenderPassDesc::equalIgnoreLoadStoreOptions(const RenderPassDesc &other) const
{
    if (numColorAttachments != other.numColorAttachments)
    {
        return false;
    }

    for (uint32_t i = 0; i < numColorAttachments; ++i)
    {
        auto &renderPassColorAttachment = colorAttachments[i];
        auto &otherRPAttachment         = other.colorAttachments[i];
        if (!renderPassColorAttachment.equalIgnoreLoadStoreOptions(otherRPAttachment))
        {
            return false;
        }
    }

    if (defaultWidth != other.defaultWidth || defaultHeight != other.defaultHeight)
    {
        return false;
    }

    if (rasterSampleCount != other.rasterSampleCount)
    {
        return false;
    }

    return depthAttachment.equalIgnoreLoadStoreOptions(other.depthAttachment) &&
           stencilAttachment.equalIgnoreLoadStoreOptions(other.stencilAttachment);
}

bool RenderPassDesc::operator==(const RenderPassDesc &other) const
{
    if (numColorAttachments != other.numColorAttachments)
    {
        return false;
    }

    for (uint32_t i = 0; i < numColorAttachments; ++i)
    {
        if (colorAttachments[i] != other.colorAttachments[i])
        {
            return false;
        }
    }
    return depthAttachment == other.depthAttachment &&
           stencilAttachment == other.stencilAttachment && defaultWidth == other.defaultWidth &&
           defaultHeight == other.defaultHeight && rasterSampleCount == other.rasterSampleCount;
}

// Convert to Metal object
void RenderPassDesc::convertToMetalDesc(MTLRenderPassDescriptor *objCDesc,
                                        uint32_t deviceMaxRenderTargets) const
{
    ASSERT(deviceMaxRenderTargets <= kMaxRenderTargets);

    for (uint32_t i = 0; i < numColorAttachments; ++i)
    {
        ToObjC(colorAttachments[i], objCDesc.colorAttachments[i]);
    }
    for (uint32_t i = numColorAttachments; i < deviceMaxRenderTargets; ++i)
    {
        // Inactive render target
        objCDesc.colorAttachments[i].texture     = nil;
        objCDesc.colorAttachments[i].level       = 0;
        objCDesc.colorAttachments[i].slice       = 0;
        objCDesc.colorAttachments[i].depthPlane  = 0;
        objCDesc.colorAttachments[i].loadAction  = MTLLoadActionDontCare;
        objCDesc.colorAttachments[i].storeAction = MTLStoreActionDontCare;
    }

    ToObjC(depthAttachment, objCDesc.depthAttachment);
    ToObjC(stencilAttachment, objCDesc.stencilAttachment);

    if ((defaultWidth | defaultHeight) != 0)
    {
        objCDesc.renderTargetWidth        = defaultWidth;
        objCDesc.renderTargetHeight       = defaultHeight;
        objCDesc.defaultRasterSampleCount = 1;
    }
    else
    {
        objCDesc.renderTargetWidth  = 0;
        objCDesc.renderTargetHeight = 0;
        // No need to reset defaultRasterSampleCount as it is only applied when no attachments are
        // provided.
    }
}

// StateCache implementation
StateCache::StateCache(const angle::FeaturesMtl &features) : mFeatures(features) {}

StateCache::~StateCache() {}

angle::ObjCPtr<id<MTLDepthStencilState>> StateCache::getNullDepthStencilState(
    const mtl::ContextDevice &device)
{
    if (!mNullDepthStencilState)
    {
        DepthStencilDesc desc(MTLCompareFunctionAlways, false);
        ASSERT(desc.frontFaceStencil.getStencilCompareFunction() == MTLCompareFunctionAlways);
        mNullDepthStencilState = getDepthStencilState(device, desc);
    }
    return mNullDepthStencilState;
}

angle::ObjCPtr<id<MTLDepthStencilState>> StateCache::getDepthStencilState(
    const mtl::ContextDevice &device,
    const DepthStencilDesc &desc)
{
    auto ite = mDepthStencilStates.find(desc);
    if (ite == mDepthStencilStates.end())
    {
        auto re = mDepthStencilStates.insert(
            std::make_pair(desc, device.newDepthStencilStateWithDescriptor(ToObjC(desc))));
        if (!re.second)
        {
            return nil;
        }

        ite = re.first;
    }

    return ite->second;
}

angle::ObjCPtr<id<MTLSamplerState>> StateCache::getSamplerState(const mtl::ContextDevice &device,
                                                                const SamplerDesc &desc)
{
    auto ite = mSamplerStates.find(desc);
    if (ite == mSamplerStates.end())
    {
        auto objCDesc = ToObjC(desc);
        if (!mFeatures.allowRuntimeSamplerCompareMode.enabled)
        {
            // Runtime sampler compare mode is not supported, fallback to never.
            objCDesc.get().compareFunction = MTLCompareFunctionNever;
        }
        auto re = mSamplerStates.insert(
            std::make_pair(desc, device.newSamplerStateWithDescriptor(objCDesc)));
        if (!re.second)
            return nil;

        ite = re.first;
    }

    return ite->second;
}

angle::ObjCPtr<id<MTLSamplerState>> StateCache::getNullSamplerState(ContextMtl *context)
{
    return getNullSamplerState(context->getMetalDevice());
}

angle::ObjCPtr<id<MTLSamplerState>> StateCache::getNullSamplerState(
    const mtl::ContextDevice &device)
{
    SamplerDesc desc;
    return getSamplerState(device, desc);
}

void StateCache::clear()
{
    mNullDepthStencilState = nil;
    mDepthStencilStates.clear();
    mSamplerStates.clear();
}

}  // namespace mtl
}  // namespace rx
