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

#include "src/gpu/graphite/mtl/MtlCaps.h"

#include "include/core/SkTextureCompressionType.h"
#include "include/gpu/graphite/TextureInfo.h"
#include "include/gpu/graphite/mtl/MtlGraphiteTypes.h"
#include "src/gpu/SwizzlePriv.h"
#include "src/gpu/graphite/CommandBuffer.h"
#include "src/gpu/graphite/ComputePipelineDesc.h"
#include "src/gpu/graphite/GraphicsPipelineDesc.h"
#include "src/gpu/graphite/GraphiteResourceKey.h"
#include "src/gpu/graphite/RenderPassDesc.h"
#include "src/gpu/graphite/RendererProvider.h"
#include "src/gpu/graphite/TextureInfoPriv.h"
#include "src/gpu/graphite/TextureProxy.h"
#include "src/gpu/graphite/mtl/MtlGraphicsPipeline.h"
#include "src/gpu/graphite/mtl/MtlGraphiteUtils.h"
#include "src/gpu/mtl/MtlUtilsPriv.h"
#include "src/sksl/SkSLUtil.h"

namespace skgpu::graphite {

MtlCaps::MtlCaps(const id<MTLDevice> device, const ContextOptions& options)
        : Caps() {
    this->initGPUFamily(device);
    this->initCaps(device);
    this->initShaderCaps();

    this->initFormatTable(device);

    // Metal-specific MtlCaps

    this->finishInitialization(options);
}

bool MtlCaps::GetGPUFamily(id<MTLDevice> device, GPUFamily* gpuFamily, int* group) {
#if SKGPU_GRAPHITE_METAL_SDK_VERSION >= 220
    if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
        // Apple Silicon
#if SKGPU_GRAPHITE_METAL_SDK_VERSION >= 230
        if ([device supportsFamily:MTLGPUFamilyApple7]) {
            *gpuFamily = GPUFamily::kApple;
            *group = 7;
            return true;
        }
#endif
#ifdef SK_BUILD_FOR_IOS
        if ([device supportsFamily:MTLGPUFamilyApple6]) {
            *gpuFamily = GPUFamily::kApple;
            *group = 6;
            return true;
        }
        if ([device supportsFamily:MTLGPUFamilyApple5]) {
            *gpuFamily = GPUFamily::kApple;
            *group = 5;
            return true;
        }
        if ([device supportsFamily:MTLGPUFamilyApple4]) {
            *gpuFamily = GPUFamily::kApple;
            *group = 4;
            return true;
        }
        if ([device supportsFamily:MTLGPUFamilyApple3]) {
            *gpuFamily = GPUFamily::kApple;
            *group = 3;
            return true;
        }
        if ([device supportsFamily:MTLGPUFamilyApple2]) {
            *gpuFamily = GPUFamily::kApple;
            *group = 2;
            return true;
        }
        if ([device supportsFamily:MTLGPUFamilyApple1]) {
            *gpuFamily = GPUFamily::kApple;
            *group = 1;
            return true;
        }
#endif

        // Older Macs
        // MTLGPUFamilyMac1, MTLGPUFamilyMacCatalyst1, and MTLGPUFamilyMacCatalyst2 are deprecated.
        // However, some MTLGPUFamilyMac1 only hardware is still supported.
        // MacCatalyst families have the same features as Mac, so treat them the same
        //
        // Check if an Intel GPU is present; allow targeting issues specific to that hardware.
        bool isIntel = [device.name containsString:@"Intel"];
        if ([device supportsFamily:MTLGPUFamilyMac2] ||
            [device supportsFamily:(MTLGPUFamily)4002/*MTLGPUFamilyMacCatalyst2*/]) {
            *gpuFamily = isIntel ? GPUFamily::kMacIntel : GPUFamily::kMac;
            *group = 2;
            return true;
        }
        if ([device supportsFamily:(MTLGPUFamily)2001/*MTLGPUFamilyMac1*/] ||
            [device supportsFamily:(MTLGPUFamily)4001/*MTLGPUFamilyMacCatalyst1*/]) {
            *gpuFamily = isIntel ? GPUFamily::kMacIntel : GPUFamily::kMac;
            *group = 1;
            return true;
        }
    }
#endif

    // No supported GPU families were found
    return false;
}

void MtlCaps::initGPUFamily(id<MTLDevice> device) {
    if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
        if (GetGPUFamily(device, &fGPUFamily, &fFamilyGroup)) {
            return;
        }
    }

    // We don't know what this is, fall back to minimum defaults
#ifdef SK_BUILD_FOR_MAC
    fGPUFamily = GPUFamily::kMac;
    fFamilyGroup = 1;
#else
    fGPUFamily = GPUFamily::kApple;
    fFamilyGroup = 1;
#endif
}

void MtlCaps::initCaps(const id<MTLDevice> device) {
#if defined(GPU_TEST_UTILS)
    this->setDeviceName([[device name] UTF8String]);
#endif

    if (this->isMac() || fFamilyGroup >= 3) {
        fMaxTextureSize = 16384;
    } else {
        fMaxTextureSize = 8192;
    }

    // We use constant address space for our uniform buffers which has various alignment
    // requirements for the offset when binding the buffer. On MacOS Intel the offset must align
    // to 256. On iOS or Apple Silicon we must align to the max of the data type consumed by the
    // vertex function or 4 bytes, or we can ignore the data type and just use 16 bytes.
    //
    // On Mac, all copies must be aligned to at least 4 bytes; on iOS there is no alignment.
    if (this->isMac()) {
        fRequiredUniformBufferAlignment = 256;
        fRequiredTransferBufferAlignment = 4;
    } else {
        fRequiredUniformBufferAlignment = 16;
        fRequiredTransferBufferAlignment = 1;
    }

    fResourceBindingReqs.fBackendApi = BackendApi::kMetal;
    fResourceBindingReqs.fUniformBufferLayout = Layout::kMetal;
    fResourceBindingReqs.fStorageBufferLayout = Layout::kMetal;

    // Graphite/Metal does not group resources into different sets or bind groups at this time,
    // though ResourceBindingRequirements still expects valid assignments of these indices.
    // Assigning both to 0 conveys the usage of one single "set" for all resources.
    fResourceBindingReqs.fUniformsSetIdx = 0;
    fResourceBindingReqs.fTextureSamplerSetIdx = 0;

    fResourceBindingReqs.fComputeUsesDistinctIdxRangesForTextures = true;

    fResourceBindingReqs.fIntrinsicBufferBinding =
            MtlGraphicsPipeline::kIntrinsicUniformBufferIndex;
    fResourceBindingReqs.fCombinedUniformBufferBinding = MtlGraphicsPipeline::kCombinedUniformIndex;
    fResourceBindingReqs.fGradientBufferBinding = MtlGraphicsPipeline::kGradientBufferIndex;

    // Metal does not distinguish between uniform and storage buffers.
    fRequiredStorageBufferAlignment = fRequiredUniformBufferAlignment;

    fStorageBufferSupport = true;

    fComputeSupport = true;

    // See https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf, and what Dawn does at
    // https://crsrc.org/c/third_party/dawn/src/dawn/native/metal/PhysicalDeviceMTL.mm?q=maxInterStageShaderVariables
    if (this->isMac() || fFamilyGroup >= 4) {
        fMaxVaryings = 31;
    } else {
        fMaxVaryings = 15;
    }

    if (@available(macOS 10.12, iOS 14.0, tvOS 14.0, *)) {
        fClampToBorderSupport = (this->isMac() || fFamilyGroup >= 7);
    } else {
        fClampToBorderSupport = false;
    }

    // Init sample counts. All devices support 1
    fSupportedSampleCounts = SampleCount::k1;
    if (!this->isIntel()) {
        if (@available(macOS 10.11, iOS 9.0, tvOS 9.0, *)) {
            for (auto sampleCnt : {SampleCount::k2, SampleCount::k4, SampleCount::k8}) {
                if ([device supportsTextureSampleCount: (uint8_t) sampleCnt]) {
                    fSupportedSampleCounts |= sampleCnt;
                }
            }
        }
    } else {
        // All supported Intel Macs are of an old enough Intel generation that we can just assume
        // MSAA should be avoided instead of checking its generation. (>gen 11 FIXME?)
        fAvoidMSAA = true;
    }
}

void MtlCaps::initShaderCaps() {
    SkSL::ShaderCaps* shaderCaps = fShaderCaps.get();

    // Dual source blending requires Metal 1.2.
    if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, *)) {
        shaderCaps->fDualSourceBlendingSupport = true;
    }

    shaderCaps->fFlatInterpolationSupport = true;
    shaderCaps->fShaderDerivativeSupport = true;
    shaderCaps->fInfinitySupport = true;

    if (@available(macOS 11.0, *)) {
        if (this->isApple()) {
            shaderCaps->fFBFetchSupport = true;
            shaderCaps->fFBFetchColorName = "sk_LastFragColor";
        }
    }

    if (this->isIntel()) {
        shaderCaps->fVectorClampMinMaxSupport = false;
    }

    shaderCaps->fIntegerSupport = true;
    shaderCaps->fNonsquareMatrixSupport = true;
    shaderCaps->fInverseHyperbolicSupport = true;

    // Metal uses IEEE floats so assuming those values here.
    shaderCaps->fFloatIs32Bits = true;
}

// Define this so we can use it to initialize arrays and work around
// the fact that these pixel formats are not always available.
#define kMTLPixelFormatB5G6R5Unorm MTLPixelFormat(40)
#define kMTLPixelFormatABGR4Unorm MTLPixelFormat(42)
#define kMTLPixelFormatETC2_RGB8 MTLPixelFormat(180)

// These are all the valid MTLPixelFormats that we currently support in Skia.  They are roughly
// ordered from most frequently used to least to improve look up times in arrays.
static constexpr MTLPixelFormat kMtlFormats[] = {
    MTLPixelFormatRGBA8Unorm,
    MTLPixelFormatR8Unorm,
    MTLPixelFormatA8Unorm,
    MTLPixelFormatBGRA8Unorm,
    kMTLPixelFormatB5G6R5Unorm,
    MTLPixelFormatRGBA16Float,
    MTLPixelFormatR16Float,
    MTLPixelFormatRG8Unorm,
    MTLPixelFormatRGB10A2Unorm,
    // MTLPixelFormatBGR10A2Unorm
    kMTLPixelFormatABGR4Unorm,
    MTLPixelFormatRGBA8Unorm_sRGB,
    MTLPixelFormatR16Unorm,
    MTLPixelFormatRG16Unorm,
    kMTLPixelFormatETC2_RGB8,
#ifdef SK_BUILD_FOR_MAC
    MTLPixelFormatBC1_RGBA,
#endif
    MTLPixelFormatRGBA16Unorm,
    MTLPixelFormatRG16Float,

    MTLPixelFormatStencil8,
    MTLPixelFormatDepth16Unorm,
    MTLPixelFormatDepth32Float,
#ifdef SK_BUILD_FOR_MAC
    MTLPixelFormatDepth24Unorm_Stencil8,
#endif
    MTLPixelFormatDepth32Float_Stencil8,

    MTLPixelFormatInvalid,
};

void MtlCaps::setColorType(SkColorType colorType, std::initializer_list<MTLPixelFormat> formats) {
    int idx = static_cast<int>(colorType);
    for (auto it = formats.begin(); it != formats.end(); ++it) {
        const auto& info = this->getFormatInfo(*it);
        for (int i = 0; i < info.fColorTypeInfoCount; ++i) {
            if (info.fColorTypeInfos[i].fColorType == colorType) {
                fColorTypeToFormatTable[idx] = *it;
                return;
            }
        }
    }
}

size_t MtlCaps::GetFormatIndex(MTLPixelFormat pixelFormat) {
    static_assert(std::size(kMtlFormats) == MtlCaps::kNumMtlFormats,
                  "Size of kMtlFormats array must match static value in header");
    for (size_t i = 0; i < MtlCaps::kNumMtlFormats; ++i) {
        if (kMtlFormats[i] == pixelFormat) {
            return i;
        }
    }
    return GetFormatIndex(MTLPixelFormatInvalid);
}

void MtlCaps::initFormatTable(const id<MTLDevice> device) {
    FormatInfo* info;

    if (@available(macOS 11.0, iOS 8.0, tvOS 9.0, *)) {
        if (this->isApple()) {
            SkASSERT(kMTLPixelFormatB5G6R5Unorm == MTLPixelFormatB5G6R5Unorm);
            SkASSERT(kMTLPixelFormatABGR4Unorm == MTLPixelFormatABGR4Unorm);
        }
    }

    // NOTE: MTLPixelFormat's naming convention orders channels from least significant to most,
    // matching the data address ordering of a little endian system.

    // Format: RGBA8Unorm
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatRGBA8Unorm)];
        info->fFlags = FormatInfo::kAllFlags;
        info->fColorTypeInfoCount = 2;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: RGBA8Unorm, Surface: kRGBA_8888
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kRGBA_8888_SkColorType;
            ctInfo.fTransferColorType = kRGBA_8888_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
        // Format: RGBA8Unorm, Surface: kRGB_888x
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kRGB_888x_SkColorType;
            ctInfo.fTransferColorType = kRGB_888x_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag;
            ctInfo.fReadSwizzle = skgpu::Swizzle::RGB1();
        }
    }

    // Format: R8Unorm
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatR8Unorm)];
        info->fFlags = FormatInfo::kAllFlags;
        info->fColorTypeInfoCount = 3;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: R8Unorm, Surface: kR8_unorm
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kR8_unorm_SkColorType;
            ctInfo.fTransferColorType = kR8_unorm_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
        // Format: R8Unorm, Surface: kAlpha_8
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kAlpha_8_SkColorType;
            ctInfo.fTransferColorType = kAlpha_8_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
            ctInfo.fReadSwizzle = skgpu::Swizzle("000r");
            ctInfo.fWriteSwizzle = skgpu::Swizzle("a000");
        }
        // Format: R8Unorm, Surface: kGray_8
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kGray_8_SkColorType;
            ctInfo.fTransferColorType = kGray_8_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag;
            ctInfo.fReadSwizzle = skgpu::Swizzle("rrr1");
        }
    }

    // Format: A8Unorm
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatA8Unorm)];
        info->fFlags = FormatInfo::kTexturable_Flag;
        info->fColorTypeInfoCount = 1;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: A8Unorm, Surface: kAlpha_8
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kAlpha_8_SkColorType;
            ctInfo.fTransferColorType = kAlpha_8_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
    }

    // Format: BGRA8Unorm
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatBGRA8Unorm)];
        info->fFlags = FormatInfo::kAllFlags;
        info->fColorTypeInfoCount = 1;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: BGRA8Unorm, Surface: kBGRA_8888
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kBGRA_8888_SkColorType;
            ctInfo.fTransferColorType = kBGRA_8888_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
    }

    if (@available(macOS 11.0, iOS 8.0, tvOS 9.0, *)) {
        if (this->isApple()) {
            // Format: B5G6R5Unorm
            {
                info = &fFormatTable[GetFormatIndex(MTLPixelFormatB5G6R5Unorm)];
                info->fFlags = FormatInfo::kAllFlags;
                info->fColorTypeInfoCount = 1;
                info->fColorTypeInfos =
                        std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
                int ctIdx = 0;
                // Format: B5G6R5Unorm, Surface: kRGB_565; misnamed SkColorType is really BGR data
                {
                    auto& ctInfo = info->fColorTypeInfos[ctIdx++];
                    ctInfo.fColorType = kRGB_565_SkColorType;
                    ctInfo.fTransferColorType = kRGB_565_SkColorType;
                    ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag |
                                    ColorTypeInfo::kRenderable_Flag;
                }
            }

            // Format: ABGR4Unorm
            {
                info = &fFormatTable[GetFormatIndex(MTLPixelFormatABGR4Unorm)];
                info->fFlags = FormatInfo::kAllFlags;
                info->fColorTypeInfoCount = 1;
                info->fColorTypeInfos =
                        std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
                int ctIdx = 0;
                // Format: ABGR4Unorm, Surface: kARGB_4444; misnamed SkColorType is really ABGR data
                {
                    auto& ctInfo = info->fColorTypeInfos[ctIdx++];
                    ctInfo.fColorType = kARGB_4444_SkColorType;
                    ctInfo.fTransferColorType = kARGB_4444_SkColorType;
                    ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag |
                                    ColorTypeInfo::kRenderable_Flag;
                }
            }
        }
    }

    // Format: RGBA8Unorm_sRGB
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatRGBA8Unorm_sRGB)];
        info->fFlags = FormatInfo::kAllFlags;
        info->fColorTypeInfoCount = 1;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: RGBA8Unorm_sRGB, Surface: kSRGBA_8888
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kSRGBA_8888_SkColorType;
            ctInfo.fTransferColorType = kSRGBA_8888_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
    }

    // Format: RGB10A2Unorm
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatRGB10A2Unorm)];
        if (this->isMac() || fFamilyGroup >= 3) {
            info->fFlags = FormatInfo::kAllFlags;
        } else {
            info->fFlags = FormatInfo::kTexturable_Flag;
        }
        info->fColorTypeInfoCount = 2;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: RGB10A2Unorm, Surface: kRGBA_1010102
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kRGBA_1010102_SkColorType;
            ctInfo.fTransferColorType = kRGBA_1010102_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
        // Format: RGB10A2Unorm, Surface: kRGB_101010x
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kRGB_101010x_SkColorType;
            ctInfo.fTransferColorType = kRGB_101010x_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag;
            ctInfo.fReadSwizzle = skgpu::Swizzle::RGB1();
        }
    }

    // Format: RGBA16Float
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatRGBA16Float)];
        info->fFlags = FormatInfo::kAllFlags;
        info->fColorTypeInfoCount = 3;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: RGBA16Float, Surface: RGBA_F16
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kRGBA_F16_SkColorType;
            ctInfo.fTransferColorType = kRGBA_F16_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
        // Format: RGBA16Float, Surface: RGBA_F16Norm
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kRGBA_F16Norm_SkColorType;
            ctInfo.fTransferColorType = kRGBA_F16Norm_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
        // Format: RGBA16Float, Surface: RGB_F16F16F16x
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kRGB_F16F16F16x_SkColorType;
            ctInfo.fTransferColorType = kRGB_F16F16F16x_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag;
            ctInfo.fReadSwizzle = skgpu::Swizzle::RGB1();
        }
    }

    // Format: R16Float
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatR16Float)];
        info->fFlags = FormatInfo::kAllFlags;
        info->fColorTypeInfoCount = 1;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: R16Float, Surface: kA16_float
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kA16_float_SkColorType;
            ctInfo.fTransferColorType = kA16_float_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
            ctInfo.fReadSwizzle = skgpu::Swizzle("000r");
            ctInfo.fWriteSwizzle = skgpu::Swizzle("a000");
        }
    }

    // Format: RG8Unorm
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatRG8Unorm)];
        info->fFlags = FormatInfo::kAllFlags;
        info->fColorTypeInfoCount = 1;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: RG8Unorm, Surface: kR8G8_unorm
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kR8G8_unorm_SkColorType;
            ctInfo.fTransferColorType = kR8G8_unorm_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
    }

    // Format: RGBA16Unorm
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatRGBA16Unorm)];
        if (this->isMac()) {
            info->fFlags = FormatInfo::kAllFlags;
        } else {
            info->fFlags = FormatInfo::kTexturable_Flag | FormatInfo::kRenderable_Flag;
        }
        info->fColorTypeInfoCount = 1;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: RGBA16Unorm, Surface: kR16G16B16A16_unorm
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kR16G16B16A16_unorm_SkColorType;
            ctInfo.fTransferColorType = kR16G16B16A16_unorm_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
    }

    // Format: RG16Float
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatRG16Float)];
        info->fFlags = FormatInfo::kAllFlags;
        info->fColorTypeInfoCount = 1;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: RG16Float, Surface: kR16G16_float
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kR16G16_float_SkColorType;
            ctInfo.fTransferColorType = kR16G16_float_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
    }

    // Format: R16Unorm
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatR16Unorm)];
        if (this->isMac()) {
            info->fFlags = FormatInfo::kAllFlags;
        } else {
            info->fFlags = FormatInfo::kTexturable_Flag | FormatInfo::kRenderable_Flag;
        }
        info->fColorTypeInfoCount = 2;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: R16Unorm, Surface: kA16_unorm
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kA16_unorm_SkColorType;
            ctInfo.fTransferColorType = kA16_unorm_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
            ctInfo.fReadSwizzle = skgpu::Swizzle("000r");
            ctInfo.fWriteSwizzle = skgpu::Swizzle("a000");
        }
        // Format: R16Unorm, Surface: kR16_unorm
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kR16_unorm_SkColorType;
            ctInfo.fTransferColorType = kR16_unorm_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
    }

    // Format: RG16Unorm
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatRG16Unorm)];
        if (this->isMac()) {
            info->fFlags = FormatInfo::kAllFlags;
        } else {
            info->fFlags = FormatInfo::kTexturable_Flag | FormatInfo::kRenderable_Flag;
        }
        info->fColorTypeInfoCount = 1;
        info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
        int ctIdx = 0;
        // Format: RG16Unorm, Surface: kR16G16_unorm
        {
            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
            ctInfo.fColorType = kR16G16_unorm_SkColorType;
            ctInfo.fTransferColorType = kR16G16_unorm_SkColorType;
            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
        }
    }

    // Format: ETC2_RGB8
    {
        if (@available(macOS 11.0, iOS 8.0, tvOS 9.0, *)) {
            if (this->isApple()) {
                info = &fFormatTable[GetFormatIndex(MTLPixelFormatETC2_RGB8)];
                info->fFlags = FormatInfo::kTexturable_Flag;
                info->fColorTypeInfoCount = 1;
                info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
                int ctIdx = 0;
                // Format: ETC2_RGB8, Surface: kRGB_888x
                {
                    auto& ctInfo = info->fColorTypeInfos[ctIdx++];
                    ctInfo.fColorType = kRGB_888x_SkColorType;
                    ctInfo.fTransferColorType = kRGB_888x_SkColorType;
                    ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag;
                }
            }
        }
    }

    // Format: BC1_RGBA
    {
#ifdef SK_BUILD_FOR_MAC
        if (this->isMac()) {
            info = &fFormatTable[GetFormatIndex(MTLPixelFormatBC1_RGBA)];
            info->fFlags = FormatInfo::kTexturable_Flag;
            info->fColorTypeInfoCount = 1;
            info->fColorTypeInfos = std::make_unique<ColorTypeInfo[]>(info->fColorTypeInfoCount);
            int ctIdx = 0;
            // Format: BC1_RGBA, Surface: kRGBA_8888
            {
                auto& ctInfo = info->fColorTypeInfos[ctIdx++];
                ctInfo.fColorType = kRGBA_8888_SkColorType;
                ctInfo.fTransferColorType = kRGBA_8888_SkColorType;
                ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag;
            }
        }
#endif
    }

    /*
     * Non-color formats (renderable, but with no color type)
     */

    // Format: Stencil8
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatStencil8)];
        info->fFlags = FormatInfo::kMSAA_Flag | FormatInfo::kRenderable_Flag;
        info->fColorTypeInfoCount = 0;
    }

    // Format: Depth16Unorm
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatDepth16Unorm)];
        info->fFlags = FormatInfo::kMSAA_Flag | FormatInfo::kRenderable_Flag;
        if (this->isMac() || fFamilyGroup >= 3) {
            info->fFlags |= FormatInfo::kResolve_Flag;
        }
        info->fColorTypeInfoCount = 0;
    }

    // Format: Depth32Float
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatDepth32Float)];
        info->fFlags = FormatInfo::kMSAA_Flag | FormatInfo::kRenderable_Flag;
        if (this->isMac() || fFamilyGroup >= 3) {
            info->fFlags |= FormatInfo::kResolve_Flag;
        }
        info->fColorTypeInfoCount = 0;
    }

    // Format: Depth24Unorm_Stencil8
    {
#ifdef SK_BUILD_FOR_MAC
        if (this->isMac() && [device isDepth24Stencil8PixelFormatSupported]) {
            info = &fFormatTable[GetFormatIndex(MTLPixelFormatDepth24Unorm_Stencil8)];
            info->fFlags = FormatInfo::kMSAA_Flag |
                           FormatInfo::kRenderable_Flag |
                           FormatInfo::kResolve_Flag;
            info->fColorTypeInfoCount = 0;
        }
#endif
    }

    // Format: Depth32Float_Stencil8
    {
        info = &fFormatTable[GetFormatIndex(MTLPixelFormatDepth32Float_Stencil8)];
        info->fFlags = FormatInfo::kMSAA_Flag | FormatInfo::kRenderable_Flag;
        if (this->isMac() || fFamilyGroup >= 3) {
            info->fFlags |= FormatInfo::kResolve_Flag;
        }
        info->fColorTypeInfoCount = 0;
    }

    ////////////////////////////////////////////////////////////////////////////
    // Map SkColorTypes (used for creating SkSurfaces) to MTLPixelFormats. The order in which the
    // formats are passed into the setColorType function indicates the priority in selecting which
    // format we use for a given SkColorType.

    std::fill_n(fColorTypeToFormatTable, kSkColorTypeCnt, MTLPixelFormatInvalid);

    this->setColorType(kAlpha_8_SkColorType,          { MTLPixelFormatR8Unorm,
                                                        MTLPixelFormatA8Unorm });
    if (@available(macOS 11.0, iOS 8.0, tvOS 9.0, *)) {
        if (this->isApple()) {
            this->setColorType(kRGB_565_SkColorType,   { MTLPixelFormatB5G6R5Unorm });
            this->setColorType(kARGB_4444_SkColorType, { MTLPixelFormatABGR4Unorm  });
        }
    }

    this->setColorType(kRGBA_8888_SkColorType,         { MTLPixelFormatRGBA8Unorm      });
    this->setColorType(kRGB_888x_SkColorType,          { MTLPixelFormatRGBA8Unorm      });
    this->setColorType(kBGRA_8888_SkColorType,         { MTLPixelFormatBGRA8Unorm      });
    this->setColorType(kRGBA_1010102_SkColorType,      { MTLPixelFormatRGB10A2Unorm    });
    this->setColorType(kRGB_101010x_SkColorType,       { MTLPixelFormatRGB10A2Unorm    });
    // kBGRA_1010102_SkColorType
    // kBGR_101010x_SkColorType
    // kBGR_101010x_XR_SkColorType
    this->setColorType(kGray_8_SkColorType,            { MTLPixelFormatR8Unorm         });
    this->setColorType(kRGBA_F16Norm_SkColorType,      { MTLPixelFormatRGBA16Float     });
    this->setColorType(kRGBA_F16_SkColorType,          { MTLPixelFormatRGBA16Float     });
    this->setColorType(kRGB_F16F16F16x_SkColorType,    { MTLPixelFormatRGBA16Float     });
    // kRGBA_F32_SkColorType
    this->setColorType(kR8G8_unorm_SkColorType,        { MTLPixelFormatRG8Unorm        });
    this->setColorType(kA16_float_SkColorType,         { MTLPixelFormatR16Float        });
    this->setColorType(kR16G16_float_SkColorType,      { MTLPixelFormatRG16Float       });
    this->setColorType(kA16_unorm_SkColorType,         { MTLPixelFormatR16Unorm        });
    this->setColorType(kR16_unorm_SkColorType,         { MTLPixelFormatR16Unorm        });
    this->setColorType(kR16G16_unorm_SkColorType,      { MTLPixelFormatRG16Unorm       });
    this->setColorType(kR16G16B16A16_unorm_SkColorType,{ MTLPixelFormatRGBA16Unorm     });
    this->setColorType(kSRGBA_8888_SkColorType,        { MTLPixelFormatRGBA8Unorm_sRGB });
    this->setColorType(kR8_unorm_SkColorType,          { MTLPixelFormatR8Unorm         });

}

bool MtlCaps::isSampleCountSupported(TextureFormat format, SampleCount requestedSampleCount) const {
    const FormatInfo& formatInfo = this->getFormatInfo(TextureFormatToMTLPixelFormat(format));
    if (!SkToBool(formatInfo.fFlags & FormatInfo::kRenderable_Flag)) {
        return false;
    }
    if (SkToBool(formatInfo.fFlags & FormatInfo::kMSAA_Flag)) {
        return SkToBool(fSupportedSampleCounts & (SampleCount::V) requestedSampleCount);
    } else {
        // Only single sampling is supported for the format, so 1 sample should be generally
        // available, too.
        SkASSERT(SkToBool(fSupportedSampleCounts & SampleCount::k1));
        return requestedSampleCount == SampleCount::k1;
    }
}

TextureFormat MtlCaps::getDepthStencilFormat(SkEnumBitMask<DepthStencilFlags> mask) const {
    if (mask == DepthStencilFlags::kDepth) {
        // Graphite only needs 16-bits for depth values, so save some memory. If needed for
        // workarounds, MTLPixelFormatDepth32Float is also available.
        return TextureFormat::kD16;
    } else if (mask == DepthStencilFlags::kStencil) {
        return TextureFormat::kS8;
    } else if (mask == DepthStencilFlags::kDepthStencil) {
#if defined(SK_BUILD_FOR_MAC)
        if (SkToBool(this->getFormatInfo(MTLPixelFormatDepth24Unorm_Stencil8).fFlags)) {
            return TextureFormat::kD24_S8;
        }
#endif
        return TextureFormat::kD32F_S8;
    }
    return TextureFormat::kUnsupported;
}

TextureInfo MtlCaps::getDefaultAttachmentTextureInfo(AttachmentDesc desc,
                                                     Protected,
                                                     Discardable discardable) const {
    if (!this->isSampleCountSupported(desc.fFormat, desc.fSampleCount)) {
        return {};
    }

    // Default to private in the event it's not discardable or memoryless is not available
    MTLStorageMode storageMode = MTLStorageModePrivate;

    // Try to use memoryless if it's available (only on new Apple silicon)
    if (discardable == Discardable::kYes && this->isApple()) {
        if (@available(macOS 11.0, iOS 10.0, tvOS 10.0, *)) {
            storageMode = MTLStorageModeMemoryless;
        }
    }

    MtlTextureInfo info;
    info.fSampleCount = desc.fSampleCount;
    info.fMipmapped = Mipmapped::kNo;
    info.fFormat = TextureFormatToMTLPixelFormat(desc.fFormat);
    info.fUsage = MTLTextureUsageRenderTarget;
    info.fStorageMode = storageMode;
    info.fFramebufferOnly = false;

    return TextureInfos::MakeMetal(info);
}

TextureInfo MtlCaps::getDefaultSampledTextureInfo(SkColorType colorType,
                                                  Mipmapped mipmapped,
                                                  Protected,
                                                  Renderable renderable) const {
    MTLTextureUsage usage = MTLTextureUsageShaderRead;
    if (renderable == Renderable::kYes) {
        usage |= MTLTextureUsageRenderTarget;
    }

    MTLPixelFormat format = this->getFormatFromColorType(colorType);
    if (format == MTLPixelFormatInvalid) {
        return {};
    }

    MtlTextureInfo info;
    info.fSampleCount = SampleCount::k1;
    info.fMipmapped = mipmapped;
    info.fFormat = format;
    info.fUsage = usage;
    info.fStorageMode = MTLStorageModePrivate;
    info.fFramebufferOnly = false;

    return TextureInfos::MakeMetal(info);
}

TextureInfo MtlCaps::getTextureInfoForSampledCopy(const TextureInfo& textureInfo,
                                                  Mipmapped mipmapped) const {
    MtlTextureInfo info;
    info.fSampleCount = SampleCount::k1;
    info.fMipmapped = mipmapped;
    info.fFormat = TextureInfoPriv::Get<MtlTextureInfo>(textureInfo).fFormat;
    info.fUsage = MTLTextureUsageShaderRead;
    info.fStorageMode = MTLStorageModePrivate;
    info.fFramebufferOnly = false;

    return TextureInfos::MakeMetal(info);
}

namespace {

skgpu::UniqueKey::Domain get_domain() {
    static const skgpu::UniqueKey::Domain kMtlGraphicsPipelineDomain =
            skgpu::UniqueKey::GenerateDomain();

    return kMtlGraphicsPipelineDomain;
}

MTLPixelFormat format_from_compression(SkTextureCompressionType compression) {
    switch (compression) {
        case SkTextureCompressionType::kETC2_RGB8_UNORM:
            return kMTLPixelFormatETC2_RGB8;
        case SkTextureCompressionType::kBC1_RGBA8_UNORM:
#ifdef SK_BUILD_FOR_MAC
            return MTLPixelFormatBC1_RGBA;
#endif
        default:
            return MTLPixelFormatInvalid;
    }
}
}

TextureInfo MtlCaps::getDefaultCompressedTextureInfo(SkTextureCompressionType compression,
                                                     Mipmapped mipmapped,
                                                     Protected) const {
    MTLTextureUsage usage = MTLTextureUsageShaderRead;

    MTLPixelFormat format = format_from_compression(compression);
    if (format == MTLPixelFormatInvalid) {
        return {};
    }

    MtlTextureInfo info;
    info.fSampleCount = SampleCount::k1;
    info.fMipmapped = mipmapped;
    info.fFormat = format;
    info.fUsage = usage;
    info.fStorageMode = MTLStorageModePrivate;
    info.fFramebufferOnly = false;

    return TextureInfos::MakeMetal(info);
}

TextureInfo MtlCaps::getDefaultStorageTextureInfo(SkColorType colorType) const {
    // Storage textures are currently always sampleable from a shader.
    MTLPixelFormat format = static_cast<MTLPixelFormat>(this->getFormatFromColorType(colorType));
    if (format == MTLPixelFormatInvalid) {
        return {};
    }

    const FormatInfo& formatInfo = this->getFormatInfo(format);
    if (!SkToBool(FormatInfo::kStorage_Flag & formatInfo.fFlags)) {
        return {};
    }

    MtlTextureInfo info;
    info.fSampleCount = SampleCount::k1;
    info.fMipmapped = Mipmapped::kNo;
    info.fFormat = format;
    info.fUsage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;
    info.fStorageMode = MTLStorageModePrivate;
    info.fFramebufferOnly = false;

    return TextureInfos::MakeMetal(info);
}

const Caps::ColorTypeInfo* MtlCaps::getColorTypeInfo(
        SkColorType ct, const TextureInfo& textureInfo) const {
    MTLPixelFormat mtlFormat = TextureInfoPriv::Get<MtlTextureInfo>(textureInfo).fFormat;
    if (mtlFormat == MTLPixelFormatInvalid) {
        return nullptr;
    }

    const FormatInfo& info = this->getFormatInfo(mtlFormat);
    for (int i = 0; i < info.fColorTypeInfoCount; ++i) {
        const ColorTypeInfo& ctInfo = info.fColorTypeInfos[i];
        if (ctInfo.fColorType == ct) {
            return &ctInfo;
        }
    }

    return nullptr;
}

static constexpr int kMtlGraphicsPipelineKeyData32Count = 4;

UniqueKey MtlCaps::makeGraphicsPipelineKey(const GraphicsPipelineDesc& pipelineDesc,
                                           const RenderPassDesc& renderPassDesc) const {
    UniqueKey pipelineKey;
    {
        // 4 uint32_t's (render step id, paint id, renderpass desc, uint16 write swizzle key)
        UniqueKey::Builder builder(&pipelineKey, get_domain(),
                                   kMtlGraphicsPipelineKeyData32Count, "MtlGraphicsPipeline");
        // add GraphicsPipelineDesc key
        builder[0] = static_cast<uint32_t>(pipelineDesc.renderStepID());
        builder[1] = pipelineDesc.paintParamsID().asUInt();

        // add RenderPassDesc key
        builder[2] = this->getRenderPassDescKey(renderPassDesc);
        builder[3] = renderPassDesc.fWriteSwizzle.asKey();

        builder.finish();
    }

    return pipelineKey;
}

bool MtlCaps::extractGraphicsDescs(const UniqueKey& key,
                                   GraphicsPipelineDesc* pipelineDesc,
                                   RenderPassDesc* renderPassDesc,
                                   const RendererProvider* rendererProvider) const {
    struct UnpackedKeyData {
        // From the GraphicsPipelineDesc
        RenderStep::RenderStepID fRenderStepID = RenderStep::RenderStepID::kInvalid;
        UniquePaintParamsID fPaintParamsID = UniquePaintParamsID::Invalid();

        // From the RenderPassDesc
        TextureFormat fColorFormat = TextureFormat::kUnsupported;
        SampleCount fColorSampleCount = SampleCount::k1;

        TextureFormat fDSFormat = TextureFormat::kUnsupported;
        SampleCount fDSSampleCount = SampleCount::k1;

        Swizzle fWriteSwizzle;
    } keyData;

    SkASSERT(key.domain() == get_domain());
    SkASSERT(key.dataSize() == 4 * kMtlGraphicsPipelineKeyData32Count);

    const uint32_t* rawKeyData = key.data();

    SkASSERT(RenderStep::IsValidRenderStepID(rawKeyData[0]));
    keyData.fRenderStepID = static_cast<RenderStep::RenderStepID>(rawKeyData[0]);
    keyData.fPaintParamsID = rawKeyData[1] ? UniquePaintParamsID(rawKeyData[1])
                                           : UniquePaintParamsID::Invalid();

    keyData.fDSFormat = static_cast<TextureFormat>((rawKeyData[2] >> 8) & 0xFF);
    keyData.fDSSampleCount = static_cast<SampleCount>(rawKeyData[2] & 0xFF);

    keyData.fColorFormat = static_cast<TextureFormat>((rawKeyData[2] >> 24) & 0xFF);
    keyData.fColorSampleCount = static_cast<SampleCount>((rawKeyData[2] >> 16) & 0xFF);

    keyData.fWriteSwizzle = SwizzleCtorAccessor::Make(rawKeyData[3]);

    // Recreate the RenderPassDesc, picking arbitrary load/store ops. Since Metal doesn't need
    // to include resolve attachment details, assume that if color attachment's sample count is > 1
    // that there is a matching resolve attachment (no MSAA-render-to-single-sample support in MTL).
    SkASSERT(keyData.fColorSampleCount == keyData.fDSSampleCount ||
             keyData.fDSFormat == TextureFormat::kUnsupported);
    *renderPassDesc = {};
    renderPassDesc->fColorAttachment = {keyData.fColorFormat,
                                        LoadOp::kClear,
                                        StoreOp::kStore,
                                        keyData.fColorSampleCount};
    renderPassDesc->fDepthStencilAttachment = {keyData.fDSFormat,
                                               LoadOp::kClear,
                                               StoreOp::kDiscard,
                                               keyData.fDSSampleCount};
    if (keyData.fColorSampleCount > SampleCount::k1) {
        renderPassDesc->fColorResolveAttachment = {keyData.fColorFormat,
                                                   LoadOp::kClear,
                                                   StoreOp::kStore,
                                                   SampleCount::k1};
        renderPassDesc->fColorAttachment.fStoreOp = StoreOp::kDiscard;
    }

    renderPassDesc->fSampleCount = keyData.fColorSampleCount;
    renderPassDesc->fWriteSwizzle = keyData.fWriteSwizzle;
    renderPassDesc->fDstReadStrategy = this->getDstReadStrategy();

    // Recreate the GraphicsPipelineDesc
    const RenderStep* renderStep = rendererProvider->lookup(keyData.fRenderStepID);

    UniquePaintParamsID paintID = renderStep->performsShading() ? keyData.fPaintParamsID
                                                                : UniquePaintParamsID::Invalid();

    *pipelineDesc = GraphicsPipelineDesc(renderStep->renderStepID(), paintID);

    return true;
}

uint32_t MtlCaps::getRenderPassDescKey(const RenderPassDesc& renderPassDesc) const {
    static_assert(kTextureFormatCount <= 256);

    // Each attachment format + sample count fits in 16-bits. Load/store ops are ignored.
    auto attachmentKey = [](AttachmentDesc desc) {
        SkASSERT(desc.fFormat != TextureFormat::kUnsupported ||
                 desc.fSampleCount == SampleCount::k1);
        return (static_cast<uint32_t>(desc.fFormat) << 8) |
                static_cast<uint32_t>(desc.fSampleCount);
    };

    // The MtlRenderPassDescriptor requires no information about the resolve attachment
    return (attachmentKey(renderPassDesc.fColorAttachment) << 16) |
            attachmentKey(renderPassDesc.fDepthStencilAttachment);
}

UniqueKey MtlCaps::makeComputePipelineKey(const ComputePipelineDesc& pipelineDesc) const {
    UniqueKey pipelineKey;
    {
        static const skgpu::UniqueKey::Domain kComputePipelineDomain = UniqueKey::GenerateDomain();
        // The key is made up of a single uint32_t corresponding to the compute step ID.
        UniqueKey::Builder builder(&pipelineKey, kComputePipelineDomain, 1, "ComputePipeline");
        builder[0] = pipelineDesc.computeStep()->uniqueID();

        // TODO(b/240615224): The local work group size should factor into the key on platforms
        // that don't support specialization constants and require the workgroup/threadgroup size to
        // be specified in the shader text (D3D12, Vulkan 1.0, and OpenGL).

        builder.finish();
    }
    return pipelineKey;
}

bool MtlCaps::onIsTexturable(const TextureInfo& info) const {
    if (!info.isValid()) {
        return false;
    }
    const auto& mtlInfo = TextureInfoPriv::Get<MtlTextureInfo>(info);
    if (!(mtlInfo.fUsage & MTLTextureUsageShaderRead)) {
        return false;
    }
    if (mtlInfo.fFramebufferOnly) {
        return false;
    }
    return this->isTexturable(mtlInfo.fFormat);
}

bool MtlCaps::isTexturable(MTLPixelFormat format) const {
    const FormatInfo& formatInfo = this->getFormatInfo(format);
    return SkToBool(FormatInfo::kTexturable_Flag & formatInfo.fFlags);
}

bool MtlCaps::isRenderable(const TextureInfo& info) const {
    if (!info.isValid()) {
        return false;
    }
    TextureFormat format = TextureInfoPriv::ViewFormat(info);
    const auto& mtlInfo = TextureInfoPriv::Get<MtlTextureInfo>(info);
    return (mtlInfo.fUsage & MTLTextureUsageRenderTarget) &&
           this->isSampleCountSupported(format, info.sampleCount());
}

bool MtlCaps::isStorage(const TextureInfo& info) const {
    if (!info.isValid()) {
        return false;
    }
    const auto& mtlInfo = TextureInfoPriv::Get<MtlTextureInfo>(info);
    if (!(mtlInfo.fUsage & MTLTextureUsageShaderWrite)) {
        return false;
    }
    if (mtlInfo.fFramebufferOnly) {
        return false;
    }
    const FormatInfo& formatInfo = this->getFormatInfo(mtlInfo.fFormat);
    return info.sampleCount() == SampleCount::k1 &&
           SkToBool(FormatInfo::kStorage_Flag & formatInfo.fFlags);
}

bool MtlCaps::supportsWritePixels(const TextureInfo& texInfo) const {
    if (!texInfo.isValid()) {
        return false;
    }
    const auto& mtlInfo = TextureInfoPriv::Get<MtlTextureInfo>(texInfo);
    if (mtlInfo.fFramebufferOnly) {
        return false;
    }

    if (texInfo.sampleCount() > SampleCount::k1) {
        return false;
    }

    return true;
}

bool MtlCaps::supportsReadPixels(const TextureInfo& texInfo) const {
    if (!texInfo.isValid()) {
        return false;
    }
    const auto& mtlInfo = TextureInfoPriv::Get<MtlTextureInfo>(texInfo);
    if (mtlInfo.fFramebufferOnly) {
        return false;
    }

    // We disallow reading back directly from compressed textures.
    if (MtlFormatIsCompressed(mtlInfo.fFormat)) {
        return false;
    }

    if (texInfo.sampleCount() > SampleCount::k1) {
        return false;
    }

    return true;
}

std::pair<SkColorType, bool /*isRGBFormat*/> MtlCaps::supportedWritePixelsColorType(
        SkColorType dstColorType,
        const TextureInfo& dstTextureInfo,
        SkColorType srcColorType) const {
    if (!dstTextureInfo.isValid()) {
        return {kUnknown_SkColorType, false};
    }
    const auto& mtlInfo = TextureInfoPriv::Get<MtlTextureInfo>(dstTextureInfo);

    const FormatInfo& info = this->getFormatInfo(mtlInfo.fFormat);
    for (int i = 0; i < info.fColorTypeInfoCount; ++i) {
        const auto& ctInfo = info.fColorTypeInfos[i];
        if (ctInfo.fColorType == dstColorType) {
            return {ctInfo.fTransferColorType, false};
        }
    }
    return {kUnknown_SkColorType, false};
}

std::pair<SkColorType, bool /*isRGBFormat*/> MtlCaps::supportedReadPixelsColorType(
        SkColorType srcColorType,
        const TextureInfo& srcTextureInfo,
        SkColorType dstColorType) const {
    if (!srcTextureInfo.isValid()) {
        return {kUnknown_SkColorType, false};
    }
    const auto& mtlInfo = TextureInfoPriv::Get<MtlTextureInfo>(srcTextureInfo);

    // TODO: handle compressed formats
    if (MtlFormatIsCompressed(mtlInfo.fFormat)) {
        SkASSERT(this->isTexturable(mtlInfo.fFormat));
        return {kUnknown_SkColorType, false};
    }

    const FormatInfo& info = this->getFormatInfo(mtlInfo.fFormat);
    for (int i = 0; i < info.fColorTypeInfoCount; ++i) {
        const auto& ctInfo = info.fColorTypeInfos[i];
        if (ctInfo.fColorType == srcColorType) {
            return {ctInfo.fTransferColorType, false};
        }
    }
    return {kUnknown_SkColorType, false};
}

void MtlCaps::buildKeyForTexture(SkISize dimensions,
                                 const TextureInfo& info,
                                 ResourceType type,
                                 GraphiteResourceKey* key) const {
    const auto& mtlInfo = TextureInfoPriv::Get<MtlTextureInfo>(info);

    SkASSERT(!dimensions.isEmpty());

    // A MTLPixelFormat is an NSUInteger type which is documented to be 32 bits in 32 bit
    // applications and 64 bits in 64 bit applications. So it should fit in an uint64_t, but adding
    // the assert heere to make sure.
    static_assert(sizeof(MTLPixelFormat) <= sizeof(uint64_t));
    SkASSERT(mtlInfo.fFormat != MTLPixelFormatInvalid);
    uint64_t formatKey = static_cast<uint64_t>(mtlInfo.fFormat);

    uint32_t samplesKey = SamplesToKey(info.sampleCount());
    // We don't have to key the number of mip levels because it is inherit in the combination of
    // isMipped and dimensions.
    bool isMipped = mtlInfo.fMipmapped == Mipmapped::kYes;
    Protected isProtected = info.isProtected();
    bool isFBOnly = mtlInfo.fFramebufferOnly;

    // Confirm all the below parts of the key can fit in a single uint32_t. The sum of the shift
    // amounts in the asserts must be less than or equal to 32.
    SkASSERT(samplesKey                         < (1u << kNumSampleKeyBits));
    SkASSERT(static_cast<uint32_t>(isMipped)    < (1u << 1));
    SkASSERT(static_cast<uint32_t>(isProtected) < (1u << 1));
    SkASSERT(mtlInfo.fUsage                     < (1u << 5));
    SkASSERT(mtlInfo.fStorageMode               < (1u << 2));
    SkASSERT(static_cast<uint32_t>(isFBOnly)    < (1u << 1));

    // We need two uint32_ts for dimensions, 2 for format, and 1 for the rest of the key;
    static int kNum32DataCnt = 2 + 2 + 1;

    GraphiteResourceKey::Builder builder(key, type, kNum32DataCnt);

    builder[0] = dimensions.width();
    builder[1] = dimensions.height();
    builder[2] = formatKey & 0xFFFFFFFF;
    builder[3] = (formatKey >> 32) & 0xFFFFFFFF;
    builder[4] = (samplesKey                                  << 0) |
                 (static_cast<uint32_t>(isMipped)             << kNumSampleKeyBits) |
                 (static_cast<uint32_t>(isProtected)          << 4) |
                 (static_cast<uint32_t>(mtlInfo.fUsage)       << 5) |
                 (static_cast<uint32_t>(mtlInfo.fStorageMode) << 10)|
                 (static_cast<uint32_t>(isFBOnly)             << 12);

}

} // namespace skgpu::graphite
