/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "GrMtlCaps.h"

#include "GrBackendSurface.h"
#include "GrMtlUtil.h"
#include "GrRenderTargetProxy.h"
#include "GrShaderCaps.h"
#include "GrSurfaceProxy.h"
#include "SkRect.h"

GrMtlCaps::GrMtlCaps(const GrContextOptions& contextOptions, const id<MTLDevice> device,
                     MTLFeatureSet featureSet)
        : INHERITED(contextOptions) {
    fShaderCaps.reset(new GrShaderCaps(contextOptions));

    this->initFeatureSet(featureSet);
    this->initGrCaps(device);
    this->initShaderCaps();
    this->initConfigTable();
    this->initStencilFormat(device);

    this->applyOptionsOverrides(contextOptions);
    fShaderCaps->applyOptionsOverrides(contextOptions);

    // The following are disabled due to the unfinished Metal backend, not because Metal itself
    // doesn't support it.
    fBlacklistCoverageCounting = true;   // CCPR shaders have some incompatabilities with SkSLC
    fFenceSyncSupport = false;           // Fences are not implemented yet
    fMipMapSupport = false;              // GrMtlGpu::onRegenerateMipMapLevels() not implemented
    fMultisampleDisableSupport = true;   // MSAA and resolving not implemented yet
    fDiscardRenderTargetSupport = false; // GrMtlGpuCommandBuffer::discard() not implemented
    fCrossContextTextureSupport = false; // GrMtlGpu::prepareTextureForCrossContextUsage() not impl
}

void GrMtlCaps::initFeatureSet(MTLFeatureSet featureSet) {
    // Mac OSX
#ifdef SK_BUILD_FOR_MAC
    if (MTLFeatureSet_OSX_GPUFamily1_v2 == featureSet) {
        fPlatform = Platform::kMac;
        fFamilyGroup = 1;
        fVersion = 2;
        return;
    }
    if (MTLFeatureSet_OSX_GPUFamily1_v1 == featureSet) {
        fPlatform = Platform::kMac;
        fFamilyGroup = 1;
        fVersion = 1;
        return;
    }
#endif

    // iOS Family group 3
#ifdef SK_BUILD_FOR_IOS
    if (MTLFeatureSet_iOS_GPUFamily3_v2 == featureSet) {
        fPlatform = Platform::kIOS;
        fFamilyGroup = 3;
        fVersion = 2;
        return;
    }
    if (MTLFeatureSet_iOS_GPUFamily3_v1 == featureSet) {
        fPlatform = Platform::kIOS;
        fFamilyGroup = 3;
        fVersion = 1;
        return;
    }

    // iOS Family group 2
    if (MTLFeatureSet_iOS_GPUFamily2_v3 == featureSet) {
        fPlatform = Platform::kIOS;
        fFamilyGroup = 2;
        fVersion = 3;
        return;
    }
    if (MTLFeatureSet_iOS_GPUFamily2_v2 == featureSet) {
        fPlatform = Platform::kIOS;
        fFamilyGroup = 2;
        fVersion = 2;
        return;
    }
    if (MTLFeatureSet_iOS_GPUFamily2_v1 == featureSet) {
        fPlatform = Platform::kIOS;
        fFamilyGroup = 2;
        fVersion = 1;
        return;
    }

    // iOS Family group 1
    if (MTLFeatureSet_iOS_GPUFamily1_v3 == featureSet) {
        fPlatform = Platform::kIOS;
        fFamilyGroup = 1;
        fVersion = 3;
        return;
    }
    if (MTLFeatureSet_iOS_GPUFamily1_v2 == featureSet) {
        fPlatform = Platform::kIOS;
        fFamilyGroup = 1;
        fVersion = 2;
        return;
    }
    if (MTLFeatureSet_iOS_GPUFamily1_v1 == featureSet) {
        fPlatform = Platform::kIOS;
        fFamilyGroup = 1;
        fVersion = 1;
        return;
    }
#endif
    // No supported feature sets were found
    SK_ABORT("Requested an unsupported feature set");
}

bool GrMtlCaps::canCopyAsBlit(GrPixelConfig dstConfig, int dstSampleCount,
                              GrSurfaceOrigin dstOrigin,
                              GrPixelConfig srcConfig, int srcSampleCount,
                              GrSurfaceOrigin srcOrigin,
                              const SkIRect& srcRect, const SkIPoint& dstPoint,
                              bool areDstSrcSameObj) const {
    if (dstConfig != srcConfig) {
        return false;
    }
    if ((dstSampleCount > 1 || srcSampleCount > 1) && (dstSampleCount != srcSampleCount)) {
        return false;
    }
    if (dstOrigin != srcOrigin) {
        return false;
    }
    if (areDstSrcSameObj) {
        SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.x(), dstPoint.y(),
                                            srcRect.width(), srcRect.height());
        if (dstRect.intersect(srcRect)) {
            return false;
        }
    }
    return true;
}

bool GrMtlCaps::canCopyAsDraw(GrPixelConfig dstConfig, bool dstIsRenderable,
                              GrPixelConfig srcConfig, bool srcIsTextureable) const {
    // TODO: Make copySurfaceAsDraw handle the swizzle
    if (this->shaderCaps()->configOutputSwizzle(srcConfig) !=
        this->shaderCaps()->configOutputSwizzle(dstConfig)) {
        return false;
    }

    if (!dstIsRenderable || !srcIsTextureable) {
        return false;
    }
    return true;
}

bool GrMtlCaps::canCopyAsDrawThenBlit(GrPixelConfig dstConfig, GrPixelConfig srcConfig,
                                      bool srcIsTextureable) const {
    // TODO: Make copySurfaceAsDraw handle the swizzle
    if (this->shaderCaps()->configOutputSwizzle(srcConfig) !=
        this->shaderCaps()->configOutputSwizzle(dstConfig)) {
        return false;
    }
    if (!srcIsTextureable) {
        return false;
    }
    return true;
}

bool GrMtlCaps::onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
                                 const SkIRect& srcRect, const SkIPoint& dstPoint) const {
    GrSurfaceOrigin dstOrigin = dst->origin();
    GrSurfaceOrigin srcOrigin = src->origin();

    int dstSampleCnt = 0;
    int srcSampleCnt = 0;
    if (const GrRenderTargetProxy* rtProxy = dst->asRenderTargetProxy()) {
        dstSampleCnt = rtProxy->numColorSamples();
    }
    if (const GrRenderTargetProxy* rtProxy = src->asRenderTargetProxy()) {
        srcSampleCnt = rtProxy->numColorSamples();
    }
    SkASSERT((dstSampleCnt > 0) == SkToBool(dst->asRenderTargetProxy()));
    SkASSERT((srcSampleCnt > 0) == SkToBool(src->asRenderTargetProxy()));

    return this->canCopyAsBlit(dst->config(), dstSampleCnt, dstOrigin,
                               src->config(), srcSampleCnt, srcOrigin,
                               srcRect, dstPoint, dst == src) ||
           this->canCopyAsDraw(dst->config(), SkToBool(dst->asRenderTargetProxy()),
                               src->config(), SkToBool(src->asTextureProxy())) ||
           this->canCopyAsDrawThenBlit(dst->config(), src->config(),
                                       SkToBool(src->asTextureProxy()));
}

void GrMtlCaps::initGrCaps(const id<MTLDevice> device) {
    // Max vertex attribs is the same on all devices
    fMaxVertexAttributes = 31;

    // Metal does not support scissor + clear
    fPerformPartialClearsAsDraws = true;

    // RenderTarget and Texture size
    if (this->isMac()) {
        fMaxRenderTargetSize = 16384;
    } else {
        if (3 == fFamilyGroup) {
            fMaxRenderTargetSize = 16384;
        } else {
            // Family group 1 and 2 support 8192 for version 2 and above, 4096 for v1
            if (1 == fVersion) {
                fMaxRenderTargetSize = 4096;
            } else {
                fMaxRenderTargetSize = 8192;
            }
        }
    }
    fMaxPreferredRenderTargetSize = fMaxRenderTargetSize;
    fMaxTextureSize = fMaxRenderTargetSize;

    // Init sample counts. All devices support 1 (i.e. 0 in skia).
    fSampleCounts.push_back(1);
    for (auto sampleCnt : {2, 4, 8}) {
        if ([device supportsTextureSampleCount:sampleCnt]) {
            fSampleCounts.push_back(sampleCnt);
        }
    }

    // Clamp to border is supported on Mac 10.12 and higher (gpu family.version >= 1.2). It is not
    // supported on iOS.
    if (this->isMac()) {
        if (fFamilyGroup == 1 && fVersion < 2) {
            fClampToBorderSupport = false;
        }
    } else {
        fClampToBorderSupport = false;
    }

    // Starting with the assumption that there isn't a reason to not map small buffers.
    fBufferMapThreshold = 0;

    // Buffers are always fully mapped.
    fMapBufferFlags = kCanMap_MapFlag;

    fOversizedStencilSupport = true;

    fSRGBSupport = true;   // always available in Metal
    fSRGBWriteControl = false;
    fMipMapSupport = true;   // always available in Metal
    fNPOTTextureTileSupport = true;  // always available in Metal
    fDiscardRenderTargetSupport = true;

    fReuseScratchTextures = true; // Assuming this okay

    fTextureBarrierSupport = false; // Need to figure out if we can do this

    fSampleLocationsSupport = false;
    fMultisampleDisableSupport = false;

    if (this->isMac() || 3 == fFamilyGroup) {
        fInstanceAttribSupport = true;
    }

    fUsesMixedSamples = false;
    fGpuTracingSupport = false;

    fFenceSyncSupport = true;   // always available in Metal
    fCrossContextTextureSupport = false;
    fHalfFloatVertexAttributeSupport = true;
}


int GrMtlCaps::maxRenderTargetSampleCount(GrPixelConfig config) const {
    if (fConfigTable[config].fFlags & ConfigInfo::kMSAA_Flag) {
        return fSampleCounts[fSampleCounts.count() - 1];
    } else if (fConfigTable[config].fFlags & ConfigInfo::kRenderable_Flag) {
        return 1;
    }
    return 0;
}

int GrMtlCaps::getRenderTargetSampleCount(int requestedCount, GrPixelConfig config) const {
    requestedCount = SkTMax(requestedCount, 1);
    if (fConfigTable[config].fFlags & ConfigInfo::kMSAA_Flag) {
        int count = fSampleCounts.count();
        for (int i = 0; i < count; ++i) {
            if (fSampleCounts[i] >= requestedCount) {
                return fSampleCounts[i];
            }
        }
    } else if (fConfigTable[config].fFlags & ConfigInfo::kRenderable_Flag) {
        return 1 == requestedCount ? 1 : 0;
    }
    return 0;
}

void GrMtlCaps::initShaderCaps() {
    GrShaderCaps* shaderCaps = fShaderCaps.get();

    // fConfigOutputSwizzle will default to RGBA so we only need to set it for alpha only config.
    for (int i = 0; i < kGrPixelConfigCnt; ++i) {
        GrPixelConfig config = static_cast<GrPixelConfig>(i);
        if (GrPixelConfigIsAlphaOnly(config)) {
            shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RRRR();
            shaderCaps->fConfigOutputSwizzle[i] = GrSwizzle::AAAA();
        } else {
            if (kGray_8_GrPixelConfig == config) {
                shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RRRA();
            } else {
                shaderCaps->fConfigTextureSwizzle[i] = GrSwizzle::RGBA();
            }
        }
    }

    // Setting this true with the assumption that this cap will eventually mean we support varying
    // precisions and not just via modifiers.
    shaderCaps->fUsesPrecisionModifiers = true;
    shaderCaps->fFlatInterpolationSupport = true;
    // We haven't yet tested that using flat attributes perform well.
    shaderCaps->fPreferFlatInterpolation = true;

    shaderCaps->fShaderDerivativeSupport = true;
    shaderCaps->fGeometryShaderSupport = false;

    if ((this->isMac() && fVersion >= 2) ||
        (this->isIOS() && ((1 == fFamilyGroup && 4 == fVersion) ||
                           (2 == fFamilyGroup && 4 == fVersion) ||
                           (3 == fFamilyGroup && 3 == fVersion)))) {
        shaderCaps->fDualSourceBlendingSupport = true;
    }

    // TODO: Re-enable this once skbug:8720 is fixed. Will also need to remove asserts in
    // GrMtlPipelineStateBuilder which assert we aren't using this feature.
#if 0
    if (this->isIOS()) {
        shaderCaps->fFBFetchSupport = true;
        shaderCaps->fFBFetchNeedsCustomOutput = true; // ??
        shaderCaps->fFBFetchColorName = ""; // Somehow add [[color(0)]] to arguments to frag shader
    }
#endif
    shaderCaps->fDstReadInShaderSupport = shaderCaps->fFBFetchSupport;

    shaderCaps->fIntegerSupport = true;
    shaderCaps->fVertexIDSupport = false;
    shaderCaps->fImageLoadStoreSupport = false;

    // Metal uses IEEE float and half floats so assuming those values here.
    shaderCaps->fFloatIs32Bits = true;
    shaderCaps->fHalfIs32Bits = false;

    // Metal supports unsigned integers.
    shaderCaps->fUnsignedSupport = true;

    shaderCaps->fMaxFragmentSamplers = 16;
}

void GrMtlCaps::initConfigTable() {
    ConfigInfo* info;
    // Alpha_8 uses R8Unorm
    info = &fConfigTable[kAlpha_8_GrPixelConfig];
    info->fFlags = ConfigInfo::kAllFlags;

    // Gray_8 uses R8Unorm
    info = &fConfigTable[kGray_8_GrPixelConfig];
    info->fFlags = ConfigInfo::kAllFlags;

    // RGB_565 uses B5G6R5Unorm, even though written opposite this format packs how we want
    info = &fConfigTable[kRGB_565_GrPixelConfig];
    if (this->isMac()) {
        info->fFlags = 0;
    } else {
        info->fFlags = ConfigInfo::kAllFlags;
    }

    // RGBA_4444 uses ABGR4Unorm
    info = &fConfigTable[kRGBA_4444_GrPixelConfig];
    if (this->isMac()) {
        info->fFlags = 0;
    } else {
        info->fFlags = ConfigInfo::kAllFlags;
    }

    // RGBA_8888 uses RGBA8Unorm
    info = &fConfigTable[kRGBA_8888_GrPixelConfig];
    info->fFlags = ConfigInfo::kAllFlags;

    // BGRA_8888 uses BGRA8Unorm
    info = &fConfigTable[kBGRA_8888_GrPixelConfig];
    info->fFlags = ConfigInfo::kAllFlags;

    // SRGBA_8888 uses RGBA8Unorm_sRGB
    info = &fConfigTable[kSRGBA_8888_GrPixelConfig];
    info->fFlags = ConfigInfo::kAllFlags;

    // SBGRA_8888 uses BGRA8Unorm_sRGB
    info = &fConfigTable[kSBGRA_8888_GrPixelConfig];
    info->fFlags = ConfigInfo::kAllFlags;

    // RGBA_float uses RGBA32Float
    info = &fConfigTable[kRGBA_float_GrPixelConfig];
    if (this->isMac()) {
        info->fFlags = ConfigInfo::kAllFlags;
    } else {
        info->fFlags = 0;
    }

    // RG_float uses RG32Float
    info = &fConfigTable[kRG_float_GrPixelConfig];
    if (this->isMac()) {
        info->fFlags = ConfigInfo::kAllFlags;
    } else {
        info->fFlags = ConfigInfo::kRenderable_Flag;
    }

    // Alpha_half uses R16Float
    info = &fConfigTable[kAlpha_half_GrPixelConfig];
    info->fFlags = ConfigInfo::kAllFlags;

    // RGBA_half uses RGBA16Float
    info = &fConfigTable[kRGBA_half_GrPixelConfig];
    info->fFlags = ConfigInfo::kAllFlags;
}

void GrMtlCaps::initStencilFormat(id<MTLDevice> physDev) {
    fPreferredStencilFormat = StencilFormat{ MTLPixelFormatStencil8, 8, 8, true };
}

GrBackendFormat GrMtlCaps::getBackendFormatFromGrColorType(GrColorType ct,
                                                           GrSRGBEncoded srgbEncoded) const {
    GrPixelConfig config = GrColorTypeToPixelConfig(ct, srgbEncoded);
    if (config == kUnknown_GrPixelConfig) {
        return GrBackendFormat();
    }
    MTLPixelFormat format;
    if (!GrPixelConfigToMTLFormat(config, &format)) {
        return GrBackendFormat();
    }
    return GrBackendFormat::MakeMtl(format);
}