/*
 * Copyright 2023 Rive
 */

#include "rive/renderer/metal/render_context_metal_impl.h"

#include "background_shader_compiler.h"
#include "rive/renderer/buffer_ring.hpp"
#include "rive/renderer/texture.hpp"
#include "rive/renderer/rive_render_buffer.hpp"
#include "shaders/constants.glsl"
#include <sstream>

#include "generated/shaders/color_ramp.glsl.exports.h"
#include "generated/shaders/tessellate.glsl.exports.h"

#if defined(RIVE_IOS_SIMULATOR)
#import <mach-o/arch.h>
#endif

namespace rive::gpu
{
#if defined(RIVE_IOS)
#include "generated/shaders/rive_pls_ios.metallib.c"
#elif defined(RIVE_IOS_SIMULATOR)
#include "generated/shaders/rive_pls_ios_simulator.metallib.c"
#elif defined(RIVE_XROS)
#include "generated/shaders/rive_renderer_xros.metallib.c"
#elif defined(RIVE_XROS_SIMULATOR)
#include "generated/shaders/rive_renderer_xros_simulator.metallib.c"
#elif defined(RIVE_APPLETVOS)
#include "generated/shaders/rive_renderer_appletvos.metallib.c"
#elif defined(RIVE_APPLETVOS_SIMULATOR)
#include "generated/shaders/rive_renderer_appletvsimulator.metallib.c"
#else
#include "generated/shaders/rive_pls_macosx.metallib.c"
#endif

static id<MTLRenderPipelineState> make_pipeline_state(
    id<MTLDevice> gpu, MTLRenderPipelineDescriptor* desc)
{
    NSError* err = nil;
    id<MTLRenderPipelineState> state =
        [gpu newRenderPipelineStateWithDescriptor:desc error:&err];
    if (err != nil || state == nil)
    {
        NSLog(@"RIVE: make_pipeline_state error %@",
              err != nil ? err.localizedDescription : @"<nil>");
    }
    return state;
}

static MTLSamplerAddressMode address_mode_for_image_wrap(ImageWrap wrap)
{
    switch (wrap)
    {
        case ImageWrap::clamp:
            return MTLSamplerAddressModeClampToEdge;
        case ImageWrap::repeat:
            return MTLSamplerAddressModeRepeat;
        case ImageWrap::mirror:
            return MTLSamplerAddressModeMirrorRepeat;
    }

    RIVE_UNREACHABLE();
}

static MTLSamplerMinMagFilter min_mag_filter_for_image_filter(
    ImageFilter option)
{
    switch (option)
    {
        case ImageFilter::bilinear:
            return MTLSamplerMinMagFilterLinear;
        case ImageFilter::nearest:
            return MTLSamplerMinMagFilterNearest;
    }

    RIVE_UNREACHABLE();
}

static MTLSamplerMipFilter mip_filter_for_image_filter(ImageFilter option)
{
    switch (option)
    {
        case ImageFilter::nearest:
        case ImageFilter::bilinear:
            return MTLSamplerMipFilterNearest;
    }

    RIVE_UNREACHABLE();
}

// Renders color ramps to the gradient texture.
class RenderContextMetalImpl::ColorRampPipeline
{
public:
    ColorRampPipeline(id<MTLDevice> gpu, id<MTLLibrary> plsLibrary)
    {
        MTLRenderPipelineDescriptor* desc =
            [[MTLRenderPipelineDescriptor alloc] init];
        desc.vertexFunction =
            [plsLibrary newFunctionWithName:@GLSL_colorRampVertexMain];
        desc.fragmentFunction =
            [plsLibrary newFunctionWithName:@GLSL_colorRampFragmentMain];
        desc.colorAttachments[0].pixelFormat = MTLPixelFormatRGBA8Unorm;
        m_pipelineState = make_pipeline_state(gpu, desc);
    }

    id<MTLRenderPipelineState> pipelineState() const { return m_pipelineState; }

private:
    id<MTLRenderPipelineState> m_pipelineState;
};

// Renders tessellated vertices to the tessellation texture.
class RenderContextMetalImpl::TessellatePipeline
{
public:
    TessellatePipeline(id<MTLDevice> gpu, id<MTLLibrary> plsLibrary)
    {
        MTLRenderPipelineDescriptor* desc =
            [[MTLRenderPipelineDescriptor alloc] init];
        desc.vertexFunction =
            [plsLibrary newFunctionWithName:@GLSL_tessellateVertexMain];
        desc.fragmentFunction =
            [plsLibrary newFunctionWithName:@GLSL_tessellateFragmentMain];
        desc.colorAttachments[0].pixelFormat = MTLPixelFormatRGBA32Uint;
        m_pipelineState = make_pipeline_state(gpu, desc);
    }

    id<MTLRenderPipelineState> pipelineState() const { return m_pipelineState; }

private:
    id<MTLRenderPipelineState> m_pipelineState;
};

// Renders feathered fills and strokes to the atlas.
class RenderContextMetalImpl::AtlasPipeline
{
public:
    AtlasPipeline(id<MTLDevice> gpu,
                  id<MTLLibrary> plsLibrary,
                  NSString* fragmentMain,
                  MTLBlendOperation blendOperation)
    {
        MTLRenderPipelineDescriptor* desc =
            [[MTLRenderPipelineDescriptor alloc] init];
        desc.vertexFunction =
            [plsLibrary newFunctionWithName:@GLSL_atlasVertexMain];
        desc.fragmentFunction = [plsLibrary newFunctionWithName:fragmentMain];
        desc.colorAttachments[0].pixelFormat = MTLPixelFormatR32Float;
        desc.colorAttachments[0].blendingEnabled = TRUE;
        desc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne;
        desc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOne;
        desc.colorAttachments[0].rgbBlendOperation = blendOperation;
        desc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
        desc.colorAttachments[0].destinationAlphaBlendFactor =
            MTLBlendFactorOne;
        desc.colorAttachments[0].alphaBlendOperation = blendOperation;
        desc.colorAttachments[0].writeMask = MTLColorWriteMaskAll;
        m_pipelineState = make_pipeline_state(gpu, desc);
    }

    id<MTLRenderPipelineState> pipelineState() const { return m_pipelineState; }

private:
    id<MTLRenderPipelineState> m_pipelineState;
};

// Renders paths to the main render target.
class RenderContextMetalImpl::DrawPipeline
{
public:
    // Precompiled functions are embedded in namespaces. Return the fully
    // qualified name of the desired function, including its namespace.
    static NSString* GetPrecompiledFunctionName(
        DrawType drawType,
        gpu::ShaderFeatures shaderFeatures,
        gpu::ShaderMiscFlags shaderMiscFlags,
        id<MTLLibrary> precompiledLibrary,
        const char* functionBaseName)
    {
        // Each feature corresponds to a specific index in the namespaceID.
        // These must stay in sync with generate_draw_combinations.py.
        char namespaceID[] = "000000000";
        static_assert(sizeof(namespaceID) ==
                      gpu::kShaderFeatureCount + 1 /*DRAW_INTERIOR_TRIANGLES*/ +
                          1 /*ATLAS_BLIT*/ + 1 /*null terminator*/);
        for (size_t i = 0; i < gpu::kShaderFeatureCount; ++i)
        {
            ShaderFeatures feature = static_cast<ShaderFeatures>(1 << i);
            if (shaderFeatures & feature)
            {
                namespaceID[i] = '1';
            }
            static_assert((int)ShaderFeatures::ENABLE_CLIPPING == 1 << 0);
            static_assert((int)ShaderFeatures::ENABLE_CLIP_RECT == 1 << 1);
            static_assert((int)ShaderFeatures::ENABLE_ADVANCED_BLEND == 1 << 2);
            static_assert((int)ShaderFeatures::ENABLE_FEATHER == 1 << 3);
            static_assert((int)ShaderFeatures::ENABLE_EVEN_ODD == 1 << 4);
            static_assert((int)ShaderFeatures::ENABLE_NESTED_CLIPPING ==
                          1 << 5);
            static_assert((int)ShaderFeatures::ENABLE_HSL_BLEND_MODES ==
                          1 << 6);
        }
        if (drawType == DrawType::interiorTriangulation)
        {
            namespaceID[gpu::kShaderFeatureCount] = '1';
        }
        else if (drawType == DrawType::atlasBlit)
        {
            namespaceID[gpu::kShaderFeatureCount] = '1';
            namespaceID[gpu::kShaderFeatureCount + 1] = '1';
        }

        char namespacePrefix;
        switch (drawType)
        {
            case DrawType::midpointFanPatches:
            case DrawType::midpointFanCenterAAPatches:
            case DrawType::outerCurvePatches:
            case DrawType::interiorTriangulation:
            case DrawType::atlasBlit:
                namespacePrefix =
                    (shaderMiscFlags & gpu::ShaderMiscFlags::clockwiseFill)
                        ? 'c'
                        : 'p';
                break;
            case DrawType::imageRect:
                RIVE_UNREACHABLE();
            case DrawType::imageMesh:
                namespacePrefix = 'm';
                break;
            case DrawType::msaaStrokes:
            case DrawType::msaaMidpointFanBorrowedCoverage:
            case DrawType::msaaMidpointFans:
            case DrawType::msaaMidpointFanStencilReset:
            case DrawType::msaaMidpointFanPathsStencil:
            case DrawType::msaaMidpointFanPathsCover:
            case DrawType::msaaOuterCubics:
            case DrawType::msaaStencilClipReset:
            case DrawType::renderPassInitialize:
            case DrawType::renderPassResolve:
                RIVE_UNREACHABLE();
        }

        return [NSString stringWithFormat:@"%c%s::%s",
                                          namespacePrefix,
                                          namespaceID,
                                          functionBaseName];
    }

    DrawPipeline(id<MTLDevice> gpu,
                 id<MTLLibrary> library,
                 NSString* vertexFunctionName,
                 NSString* fragmentFunctionName,
                 gpu::DrawType drawType,
                 gpu::InterlockMode interlockMode,
                 gpu::ShaderFeatures shaderFeatures,
                 gpu::ShaderMiscFlags shaderMiscFlags
#ifdef WITH_RIVE_TOOLS
                 ,
                 gpu::SynthesizedFailureType synthesizedFailureType
#endif
    )
    {
        if (library == nil)
        {
            // This pipeline is being built from a shader that failed to
            // compile. Leave everything nil and let draws fail.
            return;
        }

#ifdef WITH_RIVE_TOOLS
        if (synthesizedFailureType == SynthesizedFailureType::pipelineCreation)
        {
            NSLog(@"RIVE: Synthesizing pipeline creation failure...");
            return;
        }
#endif

        auto makePipelineState = [=](id<MTLFunction> vertexMain,
                                     id<MTLFunction> fragmentMain,
                                     MTLPixelFormat pixelFormat) {
            MTLRenderPipelineDescriptor* desc =
                [[MTLRenderPipelineDescriptor alloc] init];
            desc.vertexFunction = vertexMain;
            desc.fragmentFunction = fragmentMain;

            auto* framebuffer = desc.colorAttachments[COLOR_PLANE_IDX];
            framebuffer.pixelFormat = pixelFormat;

            switch (interlockMode)
            {
                case gpu::InterlockMode::rasterOrdering:
                    // In rasterOrdering mode, the PLS planes are accessed as
                    // color attachments.
                    desc.colorAttachments[CLIP_PLANE_IDX].pixelFormat =
                        MTLPixelFormatR32Uint;
                    desc.colorAttachments[SCRATCH_COLOR_PLANE_IDX].pixelFormat =
                        pixelFormat;
                    desc.colorAttachments[COVERAGE_PLANE_IDX].pixelFormat =
                        MTLPixelFormatR32Uint;
                    break;

                case gpu::InterlockMode::atomics:
                    // In atomic mode, the PLS planes are accessed as device
                    // buffers. We only use the "framebuffer" attachment
                    // configured above.
                    if (shaderMiscFlags &
                        gpu::ShaderMiscFlags::fixedFunctionColorOutput)
                    {
                        // The shader expectes a "src-over" blend function in
                        // order to to implement antialiasing and opacity.
                        framebuffer.blendingEnabled = TRUE;
                        framebuffer.sourceRGBBlendFactor = MTLBlendFactorOne;
                        framebuffer.destinationRGBBlendFactor =
                            MTLBlendFactorOneMinusSourceAlpha;
                        framebuffer.rgbBlendOperation = MTLBlendOperationAdd;
                        framebuffer.sourceAlphaBlendFactor = MTLBlendFactorOne;
                        framebuffer.destinationAlphaBlendFactor =
                            MTLBlendFactorOneMinusSourceAlpha;
                        framebuffer.alphaBlendOperation = MTLBlendOperationAdd;
                        framebuffer.writeMask = MTLColorWriteMaskAll;
                    }
                    else if (drawType == gpu::DrawType::renderPassResolve)
                    {
                        // We're resolving from the offscreen color buffer to
                        // the framebuffer attachment. Write out the final color
                        // directly without any blend modes.
                        framebuffer.blendingEnabled = FALSE;
                        framebuffer.writeMask = MTLColorWriteMaskAll;
                    }
                    else
                    {
                        // This pipeline renders by storing to the offscreen
                        // color buffer; disable writes to the framebuffer
                        // attachment.
                        framebuffer.blendingEnabled = FALSE;
                        framebuffer.writeMask = MTLColorWriteMaskNone;
                    }
                    break;

                case gpu::InterlockMode::clockwise:
                case gpu::InterlockMode::clockwiseAtomic:
                case gpu::InterlockMode::msaa:
                    RIVE_UNREACHABLE();
            }
            return make_pipeline_state(gpu, desc);
        };
        id<MTLFunction> vertexMain =
            [library newFunctionWithName:vertexFunctionName];
        id<MTLFunction> fragmentMain =
            [library newFunctionWithName:fragmentFunctionName];
        m_pipelineStateRGBA8 = makePipelineState(
            vertexMain, fragmentMain, MTLPixelFormatRGBA8Unorm);
        m_pipelineStateBGRA8 = makePipelineState(
            vertexMain, fragmentMain, MTLPixelFormatBGRA8Unorm);
    }

    bool valid() const
    {
        assert((m_pipelineStateRGBA8 != nil) == (m_pipelineStateBGRA8 != nil));
        return m_pipelineStateRGBA8 != nil;
    }

    id<MTLRenderPipelineState> pipelineState(MTLPixelFormat pixelFormat) const
    {
        assert(valid());
        assert(pixelFormat == MTLPixelFormatRGBA8Unorm ||
               pixelFormat == MTLPixelFormatRGBA16Float ||
               pixelFormat == MTLPixelFormatRGBA8Unorm_sRGB ||
               pixelFormat == MTLPixelFormatBGRA8Unorm ||
               pixelFormat == MTLPixelFormatBGRA8Unorm_sRGB);

        switch (pixelFormat)
        {
            case MTLPixelFormatRGBA8Unorm_sRGB:
            case MTLPixelFormatRGBA8Unorm:
            case MTLPixelFormatRGBA16Float:
                return m_pipelineStateRGBA8;
            default:
                return m_pipelineStateBGRA8;
        }
    }

private:
    id<MTLRenderPipelineState> m_pipelineStateRGBA8 = nil;
    id<MTLRenderPipelineState> m_pipelineStateBGRA8 = nil;
};

#if defined(RIVE_IOS) || defined(RIVE_XROS) || defined(RIVE_APPLETVOS)
static bool is_apple_silicon(id<MTLDevice> gpu)
{
    if (@available(iOS 13, tvOS 13, visionOS 1, *))
    {
        return [gpu supportsFamily:MTLGPUFamilyApple4];
    }
    return false;
}
#endif

class BufferRingMetalImpl : public BufferRing
{
public:
    static std::unique_ptr<BufferRingMetalImpl> Make(id<MTLDevice> gpu,
                                                     size_t capacityInBytes)
    {
        return capacityInBytes != 0
                   ? std::make_unique<BufferRingMetalImpl>(gpu, capacityInBytes)
                   : nullptr;
    }

    BufferRingMetalImpl(id<MTLDevice> gpu, size_t capacityInBytes) :
        BufferRing(capacityInBytes)
    {
        for (int i = 0; i < kBufferRingSize; ++i)
        {
            m_buffers[i] =
                [gpu newBufferWithLength:capacityInBytes
                                 options:MTLResourceStorageModeShared];
        }
    }

    id<MTLBuffer> submittedBuffer() const
    {
        return m_buffers[submittedBufferIdx()];
    }

protected:
    void* onMapBuffer(int bufferIdx, size_t mapSizeInBytes) override
    {
        return m_buffers[bufferIdx].contents;
    }

    void onUnmapAndSubmitBuffer(int bufferIdx, size_t mapSizeInBytes) override
    {}

private:
    id<MTLBuffer> m_buffers[kBufferRingSize];
};

std::unique_ptr<RenderContext> RenderContextMetalImpl::MakeContext(
    id<MTLDevice> gpu, const ContextOptions& contextOptions)
{
    auto renderContextImpl = std::unique_ptr<RenderContextMetalImpl>(
        new RenderContextMetalImpl(gpu, contextOptions));
    return std::make_unique<RenderContext>(std::move(renderContextImpl));
}

RenderContextMetalImpl::RenderContextMetalImpl(
    id<MTLDevice> gpu, const ContextOptions& contextOptions) :
    m_contextOptions(contextOptions), m_gpu(gpu)
{
    // It appears, so far, that we don't need to use flat interpolation for path
    // IDs on any Apple device, and it's faster not to.
    m_platformFeatures.avoidFlatVaryings = true;
    m_platformFeatures.clipSpaceBottomUp = true;
    m_platformFeatures.framebufferBottomUp = false;
    if ([m_gpu supportsFamily:MTLGPUFamilyApple2] ||
        [m_gpu supportsFamily:MTLGPUFamilyMac2])
    {
        m_platformFeatures.maxTextureSize = 16384;
    }
    else
    {
        m_platformFeatures.maxTextureSize = 8192;
    }
#if defined(RIVE_IOS) || defined(RIVE_XROS) || defined(RIVE_APPLETVOS)
    m_platformFeatures.supportsRasterOrderingMode = true;
    m_platformFeatures.supportsAtomicMode = false;
    if (!is_apple_silicon(m_gpu))
    {
        // The PowerVR GPU, at least on A10, has fp16 precision issues. We can't
        // use the the bottom 3 bits of the path and clip IDs in order for our
        // equality testing to work.
        m_platformFeatures.pathIDGranularity = 8;
    }
#elif defined(RIVE_IOS_SIMULATOR) || defined(RIVE_XROS_SIMULATOR) ||           \
    defined(RIVE_APPLETVOS_SIMULATOR)
    // The simulator does not support framebuffer reads. Fall back on atomic
    // mode.
    m_platformFeatures.supportsRasterOrderingMode = false;
    m_platformFeatures.supportsAtomicMode = true;
#else
    m_platformFeatures.supportsRasterOrderingMode =
        [m_gpu supportsFamily:MTLGPUFamilyApple1] &&
        !contextOptions.disableFramebufferReads;
    m_platformFeatures.supportsAtomicMode = true;
#endif
    m_platformFeatures.atomicPLSInitNeedsDraw = true;

#if defined(RIVE_IOS) || defined(RIVE_XROS) || defined(RIVE_XROS_SIMULATOR) || \
    defined(RIVE_APPLETVOS) || defined(RIVE_APPLETVOS_SIMULATOR)
    // Atomic barriers are never used on iOS, but if we ever did need them, we
    // would use rasterOrderGroups.
    m_metalFeatures.atomicBarrierType = AtomicBarrierType::rasterOrderGroup;
#elif defined(RIVE_IOS_SIMULATOR)
    const NXArchInfo* hostArchitecture = NXGetLocalArchInfo();
    if (strncmp(hostArchitecture->name, "arm64", 5) == 0)
    {
        // The simulator doesn't advertise support for raster order groups, but
        // they appear to work anyway on an Apple-Silicon-hosted simulator. Use
        // rasterOrderGroup in this case because it's much faster than
        // renderPassBreak. (On Intel/AMD this doesn't matter anyway because
        // renderPassBreaks are cheap and actually faster than
        // rasterOrderGroups.)
        m_metalFeatures.atomicBarrierType = AtomicBarrierType::rasterOrderGroup;
    }
    else
    {
        m_metalFeatures.atomicBarrierType = AtomicBarrierType::renderPassBreak;
    }
#else
    // Use real memory barriers for atomic mode if they're availabile.
    // "GPU devices in Apple3 through Apple9 families don’t support memory
    // barriers that include the MTLRenderStages.fragment or .tile stages in the
    // after argument..."
    if (([m_gpu supportsFamily:MTLGPUFamilyCommon2] ||
         [m_gpu supportsFamily:MTLGPUFamilyMac2]) &&
        ![m_gpu supportsFamily:MTLGPUFamilyApple3])
    {
        m_metalFeatures.atomicBarrierType = AtomicBarrierType::memoryBarrier;
    }
    else if (m_gpu.rasterOrderGroupsSupported)
    {
        m_metalFeatures.atomicBarrierType = AtomicBarrierType::rasterOrderGroup;
    }
    else
    {
        m_metalFeatures.atomicBarrierType = AtomicBarrierType::renderPassBreak;
    }
#endif

    for (int i = 0; i < rive::ImageSampler::MAX_SAMPLER_PERMUTATIONS; ++i)
    {
        auto wrapX = ImageSampler::GetWrapXOptionFromKey(i);
        auto wrapY = ImageSampler::GetWrapYOptionFromKey(i);
        auto filter = ImageSampler::GetFilterOptionFromKey(i);

        MTLSamplerDescriptor* samplerDescriptor = [MTLSamplerDescriptor new];
        samplerDescriptor.minFilter = min_mag_filter_for_image_filter(filter);
        samplerDescriptor.magFilter = min_mag_filter_for_image_filter(filter);
        samplerDescriptor.mipFilter = mip_filter_for_image_filter(filter);
        samplerDescriptor.sAddressMode = address_mode_for_image_wrap(wrapX);
        samplerDescriptor.tAddressMode = address_mode_for_image_wrap(wrapY);

        m_imageSamplers[i] =
            [gpu newSamplerStateWithDescriptor:samplerDescriptor];
    }

    m_backgroundShaderCompiler =
        std::make_unique<BackgroundShaderCompiler>(m_gpu, m_metalFeatures);

    // Load the precompiled shaders.
    dispatch_data_t metallibData = dispatch_data_create(
#if defined(RIVE_IOS)
        rive_pls_ios_metallib,
        rive_pls_ios_metallib_len,
#elif defined(RIVE_IOS_SIMULATOR)
        rive_pls_ios_simulator_metallib,
        rive_pls_ios_simulator_metallib_len,
#elif defined(RIVE_XROS)
        rive_renderer_xros_metallib,
        rive_renderer_xros_metallib_len,
#elif defined(RIVE_XROS_SIMULATOR)
        rive_renderer_xros_simulator_metallib,
        rive_renderer_xros_simulator_metallib_len,
#elif defined(RIVE_APPLETVOS)
        rive_renderer_appletvos_metallib,
        rive_renderer_appletvos_metallib_len,
#elif defined(RIVE_APPLETVOS_SIMULATOR)
        rive_renderer_appletvsimulator_metallib,
        rive_renderer_appletvsimulator_metallib_len,
#else
        rive_pls_macosx_metallib,
        rive_pls_macosx_metallib_len,
#endif
        nil,
        nil);
    NSError* err = nil;
    m_plsPrecompiledLibrary = [m_gpu newLibraryWithData:metallibData
                                                  error:&err];
    if (err != nil || m_plsPrecompiledLibrary == nil)
    {
        NSLog(@"RIVE: Failed to load pls metallib error: %@",
              err != nil ? err.localizedDescription : @"<nil>");
        return;
    }

    m_colorRampPipeline =
        std::make_unique<ColorRampPipeline>(m_gpu, m_plsPrecompiledLibrary);

    MTLTextureDescriptor* desc = [[MTLTextureDescriptor alloc] init];
    desc.pixelFormat = MTLPixelFormatR16Float;
    desc.textureType = MTLTextureType1DArray;
    desc.width = gpu::GAUSSIAN_TABLE_SIZE;
    desc.mipmapLevelCount = 1;
    desc.arrayLength = FEATHER_TEXTURE_1D_ARRAY_LENGTH;
    desc.usage = MTLTextureUsageShaderRead;
    m_featherTexture = [m_gpu newTextureWithDescriptor:desc];
    [m_featherTexture
        replaceRegion:MTLRegionMake2D(0, 0, gpu::GAUSSIAN_TABLE_SIZE, 1)
          mipmapLevel:0
                slice:FEATHER_FUNCTION_ARRAY_INDEX
            withBytes:gpu::g_gaussianIntegralTableF16
          bytesPerRow:sizeof(gpu::g_gaussianIntegralTableF16)
        bytesPerImage:sizeof(gpu::g_gaussianIntegralTableF16)];
    [m_featherTexture
        replaceRegion:MTLRegionMake2D(0, 0, gpu::GAUSSIAN_TABLE_SIZE, 1)
          mipmapLevel:0
                slice:FEATHER_INVERSE_FUNCTION_ARRAY_INDEX
            withBytes:gpu::g_inverseGaussianIntegralTableF16
          bytesPerRow:sizeof(gpu::g_gaussianIntegralTableF16)
        bytesPerImage:sizeof(gpu::g_gaussianIntegralTableF16)];

    m_tessPipeline =
        std::make_unique<TessellatePipeline>(m_gpu, m_plsPrecompiledLibrary);
    m_tessSpanIndexBuffer =
        [m_gpu newBufferWithBytes:gpu::kTessSpanIndices
                           length:sizeof(gpu::kTessSpanIndices)
                          options:MTLResourceStorageModeShared];

    // The precompiled static library has a fully-featured shader for each
    // drawType in "rasterOrdering" mode. We load these at initialization and
    // use them while waiting for the background compiler to generate more
    // specialized, higher performance shaders.
    if (m_platformFeatures.supportsRasterOrderingMode)
    {
        for (auto drawType : {DrawType::midpointFanPatches,
                              DrawType::interiorTriangulation,
                              DrawType::atlasBlit,
                              DrawType::imageMesh})
        {
            for (auto shaderMiscFlags : {gpu::ShaderMiscFlags::none,
                                         gpu::ShaderMiscFlags::clockwiseFill})
            {
                if (drawType == gpu::DrawType::atlasBlit &&
                    shaderMiscFlags != gpu::ShaderMiscFlags::none)
                {
                    continue;
                }
                gpu::ShaderFeatures allShaderFeatures =
                    gpu::ShaderFeaturesMaskFor(
                        drawType, gpu::InterlockMode::rasterOrdering);
                uint32_t pipelineKey =
                    ShaderUniqueKey(drawType,
                                    allShaderFeatures,
                                    gpu::InterlockMode::rasterOrdering,
                                    shaderMiscFlags);
                m_drawPipelines[pipelineKey] = std::make_unique<DrawPipeline>(
                    m_gpu,
                    m_plsPrecompiledLibrary,
                    DrawPipeline::GetPrecompiledFunctionName(
                        drawType,
                        allShaderFeatures & gpu::kVertexShaderFeaturesMask,
                        gpu::ShaderMiscFlags::none,
                        m_plsPrecompiledLibrary,
                        GLSL_drawVertexMain),
                    DrawPipeline::GetPrecompiledFunctionName(
                        drawType,
                        allShaderFeatures,
                        shaderMiscFlags,
                        m_plsPrecompiledLibrary,
                        GLSL_drawFragmentMain),
                    drawType,
                    gpu::InterlockMode::rasterOrdering,
                    allShaderFeatures,
                    shaderMiscFlags
#ifdef WITH_RIVE_TOOLS
                    ,
                    SynthesizedFailureType::none
#endif
                );
            }
        }
    }

    // Create vertex and index buffers for the different PLS patches.
    m_pathPatchVertexBuffer =
        [m_gpu newBufferWithLength:kPatchVertexBufferCount * sizeof(PatchVertex)
                           options:MTLResourceStorageModeShared];
    m_pathPatchIndexBuffer =
        [m_gpu newBufferWithLength:kPatchIndexBufferCount * sizeof(uint16_t)
                           options:MTLResourceStorageModeShared];
    GeneratePatchBufferData(
        reinterpret_cast<PatchVertex*>(m_pathPatchVertexBuffer.contents),
        reinterpret_cast<uint16_t*>(m_pathPatchIndexBuffer.contents));

    // Set up the imageRect rendering buffers. (gpu::InterlockMode::atomics
    // only.)
    m_imageRectVertexBuffer =
        [m_gpu newBufferWithBytes:gpu::kImageRectVertices
                           length:sizeof(gpu::kImageRectVertices)
                          options:MTLResourceStorageModeShared];
    m_imageRectIndexBuffer =
        [m_gpu newBufferWithBytes:gpu::kImageRectIndices
                           length:sizeof(gpu::kImageRectIndices)
                          options:MTLResourceStorageModeShared];
}

RenderContextMetalImpl::~RenderContextMetalImpl() {}

// If the GPU supports framebuffer reads (called "programmable blending" in the
// feature tables), PLS planes besides the main framebuffer can exist in
// ephemeral "memoryless" storage. This means their contents are never actually
// written to main memory, and they only exist in fast tiled memory.
static id<MTLTexture> make_pls_memoryless_texture(id<MTLDevice> gpu,
                                                  MTLPixelFormat pixelFormat,
                                                  uint32_t width,
                                                  uint32_t height)
{
    MTLTextureDescriptor* desc = [[MTLTextureDescriptor alloc] init];
    desc.pixelFormat = pixelFormat;
    desc.width = width;
    desc.height = height;
    desc.usage = MTLTextureUsageRenderTarget;
    desc.textureType = MTLTextureType2D;
    desc.mipmapLevelCount = 1;
    desc.storageMode = MTLStorageModeMemoryless;
    return [gpu newTextureWithDescriptor:desc];
}

RenderTargetMetal::RenderTargetMetal(id<MTLDevice> gpu,
                                     MTLPixelFormat pixelFormat,
                                     uint32_t width,
                                     uint32_t height,
                                     const PlatformFeatures& platformFeatures) :
    RenderTarget(width, height), m_gpu(gpu), m_pixelFormat(pixelFormat)
{
    m_targetTexture = nil; // Will be configured later by setTargetTexture().
    if (platformFeatures.supportsRasterOrderingMode)
    {
        m_coverageMemorylessTexture = make_pls_memoryless_texture(
            gpu, MTLPixelFormatR32Uint, width, height);
        m_clipMemorylessTexture = make_pls_memoryless_texture(
            gpu, MTLPixelFormatR32Uint, width, height);
        m_scratchColorMemorylessTexture =
            make_pls_memoryless_texture(gpu, m_pixelFormat, width, height);
    }
}

void RenderTargetMetal::setTargetTexture(id<MTLTexture> texture)
{
    assert(!texture || compatibleWith(texture));
    m_targetTexture = texture;
}

rcp<RenderTargetMetal> RenderContextMetalImpl::makeRenderTarget(
    MTLPixelFormat pixelFormat, uint32_t width, uint32_t height)
{
    return rcp(new RenderTargetMetal(
        m_gpu, pixelFormat, width, height, m_platformFeatures));
}

class RenderBufferMetalImpl
    : public LITE_RTTI_OVERRIDE(RiveRenderBuffer, RenderBufferMetalImpl)
{
public:
    RenderBufferMetalImpl(RenderBufferType renderBufferType,
                          RenderBufferFlags renderBufferFlags,
                          size_t sizeInBytes,
                          id<MTLDevice> gpu) :
        lite_rtti_override(renderBufferType, renderBufferFlags, sizeInBytes),
        m_gpu(gpu)
    {
        int bufferCount =
            flags() & RenderBufferFlags::mappedOnceAtInitialization
                ? 1
                : gpu::kBufferRingSize;
        for (int i = 0; i < bufferCount; ++i)
        {
            m_buffers[i] =
                [gpu newBufferWithLength:sizeInBytes
                                 options:MTLResourceStorageModeShared];
        }
    }

    id<MTLBuffer> submittedBuffer() { return m_buffers[frontBufferIdx()]; }

protected:
    void* onMap() override
    {
        assert(m_buffers[backBufferIdx()] != nil);
        return m_buffers[backBufferIdx()].contents;
    }

    void onUnmap() override {}

private:
    id<MTLDevice> m_gpu;
    id<MTLBuffer> m_buffers[gpu::kBufferRingSize];
    int m_submittedBufferIdx = -1;
};

rcp<RenderBuffer> RenderContextMetalImpl::makeRenderBuffer(
    RenderBufferType type, RenderBufferFlags flags, size_t sizeInBytes)
{
    return make_rcp<RenderBufferMetalImpl>(type, flags, sizeInBytes, m_gpu);
}

class TextureMetalImpl : public Texture
{
public:
    TextureMetalImpl(id<MTLDevice> gpu,
                     uint32_t width,
                     uint32_t height,
                     uint32_t mipLevelCount,
                     const uint8_t imageDataRGBAPremul[]) :
        Texture(width, height)
    {
        // Create the texture.
        MTLTextureDescriptor* desc = [[MTLTextureDescriptor alloc] init];
        desc.pixelFormat = MTLPixelFormatRGBA8Unorm;
        desc.width = width;
        desc.height = height;
        desc.mipmapLevelCount = mipLevelCount;
        desc.usage = MTLTextureUsageShaderRead;
        desc.textureType = MTLTextureType2D;
        m_texture = [gpu newTextureWithDescriptor:desc];

        // Specify the top-level image in the mipmap chain.
        MTLRegion region = MTLRegionMake2D(0, 0, width, height);
        [m_texture replaceRegion:region
                     mipmapLevel:0
                       withBytes:imageDataRGBAPremul
                     bytesPerRow:width * 4];
    }

    void ensureMipmaps(id<MTLCommandBuffer> commandBuffer) const
    {
        if (m_mipsDirty)
        {
            // Generate mipmaps.
            id<MTLBlitCommandEncoder> mipEncoder =
                [commandBuffer blitCommandEncoder];
            [mipEncoder generateMipmapsForTexture:m_texture];
            [mipEncoder endEncoding];
            m_mipsDirty = false;
        }
    }

    id<MTLTexture> texture() const { return m_texture; }

private:
    id<MTLTexture> m_texture;
    mutable bool m_mipsDirty = true;
};

rcp<Texture> RenderContextMetalImpl::makeImageTexture(
    uint32_t width,
    uint32_t height,
    uint32_t mipLevelCount,
    const uint8_t imageDataRGBAPremul[])
{
    return make_rcp<TextureMetalImpl>(
        m_gpu, width, height, mipLevelCount, imageDataRGBAPremul);
}

std::unique_ptr<BufferRing> RenderContextMetalImpl::makeUniformBufferRing(
    size_t capacityInBytes)
{
    return BufferRingMetalImpl::Make(m_gpu, capacityInBytes);
}

std::unique_ptr<BufferRing> RenderContextMetalImpl::makeStorageBufferRing(
    size_t capacityInBytes, gpu::StorageBufferStructure)
{
    return BufferRingMetalImpl::Make(m_gpu, capacityInBytes);
}

std::unique_ptr<BufferRing> RenderContextMetalImpl::makeVertexBufferRing(
    size_t capacityInBytes)
{
    return BufferRingMetalImpl::Make(m_gpu, capacityInBytes);
}

void RenderContextMetalImpl::resizeGradientTexture(uint32_t width,
                                                   uint32_t height)
{
    if (width == 0 || height == 0)
    {
        m_gradientTexture = nil;
        return;
    }
    MTLTextureDescriptor* desc = [[MTLTextureDescriptor alloc] init];
    desc.pixelFormat = MTLPixelFormatRGBA8Unorm;
    desc.width = width;
    desc.height = height;
    desc.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
    desc.textureType = MTLTextureType2D;
    desc.mipmapLevelCount = 1;
    desc.storageMode = MTLStorageModePrivate;
    m_gradientTexture = [m_gpu newTextureWithDescriptor:desc];
}

void RenderContextMetalImpl::resizeTessellationTexture(uint32_t width,
                                                       uint32_t height)
{
    if (width == 0 || height == 0)
    {
        m_tessVertexTexture = nil;
        return;
    }
    MTLTextureDescriptor* desc = [[MTLTextureDescriptor alloc] init];
    desc.pixelFormat = MTLPixelFormatRGBA32Uint;
    desc.width = width;
    desc.height = height;
    desc.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
    desc.textureType = MTLTextureType2D;
    desc.mipmapLevelCount = 1;
    desc.storageMode = MTLStorageModePrivate;
    m_tessVertexTexture = [m_gpu newTextureWithDescriptor:desc];
}

void RenderContextMetalImpl::resizeAtlasTexture(uint32_t width, uint32_t height)
{
    if (width == 0 || height == 0)
    {
        m_atlasTexture = nil;
        return;
    }

    MTLTextureDescriptor* desc = [[MTLTextureDescriptor alloc] init];
    desc.pixelFormat = MTLPixelFormatR32Float;
    desc.width = width;
    desc.height = height;
    desc.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
    desc.textureType = MTLTextureType2D;
    desc.mipmapLevelCount = 1;
    desc.storageMode = MTLStorageModePrivate;
    m_atlasTexture = [m_gpu newTextureWithDescriptor:desc];

    // Don't build atlas pipelines until we get an indication that they will be
    // used.
    assert((m_atlasFillPipeline == nil) == (m_atlasStrokePipeline == nil));
    if (m_atlasFillPipeline == nil)
    {
        m_atlasFillPipeline =
            std::make_unique<AtlasPipeline>(m_gpu,
                                            m_plsPrecompiledLibrary,
                                            @GLSL_atlasFillFragmentMain,
                                            MTLBlendOperationAdd);
        m_atlasStrokePipeline =
            std::make_unique<AtlasPipeline>(m_gpu,
                                            m_plsPrecompiledLibrary,
                                            @GLSL_atlasStrokeFragmentMain,
                                            MTLBlendOperationMax);
    }
}

const RenderContextMetalImpl::DrawPipeline* RenderContextMetalImpl::
    findCompatibleDrawPipeline(gpu::DrawType drawType,
                               gpu::ShaderFeatures shaderFeatures,
                               const gpu::FlushDescriptor& desc,
                               gpu::ShaderMiscFlags shaderMiscFlags)
{
    // Find a fully-featured superset of features whose pipeline we can fall
    // back on while waiting for it to compile.
    ShaderFeatures fullyFeaturedPipelineFeatures =
        gpu::UbershaderFeaturesMaskFor(
            shaderFeatures, drawType, desc.interlockMode, m_platformFeatures);

    if (m_contextOptions.shaderCompilationMode ==
        ShaderCompilationMode::onlyUbershaders)
    {
        // Force the shader features to be the full set if that's what was
        // requested.
        shaderFeatures = fullyFeaturedPipelineFeatures;
    }

#ifdef WITH_RIVE_TOOLS
    if (desc.synthesizedFailureType == SynthesizedFailureType::ubershaderLoad)
    {
        // Pretend that the requested shader is not ready yet and the ubershader
        // compilation failed
        return nil;
    }
#endif

    uint32_t pipelineKey = gpu::ShaderUniqueKey(
        drawType, shaderFeatures, desc.interlockMode, shaderMiscFlags);
    auto pipelineIter = m_drawPipelines.find(pipelineKey);
    if (pipelineIter == m_drawPipelines.end())
    {
        // The shader for this pipeline hasn't been scheduled for compiling yet.
        // Schedule it to compile in the background.
        m_backgroundShaderCompiler->pushJob({
            .drawType = drawType,
            .shaderFeatures = shaderFeatures,
            .interlockMode = desc.interlockMode,
            .shaderMiscFlags = shaderMiscFlags,
#ifdef WITH_RIVE_TOOLS
            .synthesizedFailureType = desc.synthesizedFailureType,
#endif
        });
        pipelineIter = m_drawPipelines.insert({pipelineKey, nullptr}).first;
    }

    if (pipelineIter->second == nullptr)
    {
        // The shader for this pipeline hasn't finished compiling yet.
        // Fully-featured "rasterOrdering" pipelines should have already been
        // pre-loaded from the static library.
        assert(shaderFeatures != fullyFeaturedPipelineFeatures ||
               desc.interlockMode != gpu::InterlockMode::rasterOrdering);

        // Poll to see if the shader is actually done compiling, but only wait
        // if it's a fully-feature pipeline. Otherwise, we can fall back on the
        // fully-featured pipeline while we wait for compilation.
        BackgroundCompileJob job;
        bool shouldWaitForBackgroundCompilation =
            shaderFeatures == fullyFeaturedPipelineFeatures ||
            m_contextOptions.shaderCompilationMode !=
                ShaderCompilationMode::allowAsynchronous;
        while (m_backgroundShaderCompiler->popFinishedJob(
            &job, shouldWaitForBackgroundCompilation))
        {
            uint32_t jobKey = gpu::ShaderUniqueKey(job.drawType,
                                                   job.shaderFeatures,
                                                   job.interlockMode,
                                                   job.shaderMiscFlags);
            m_drawPipelines[jobKey] =
                std::make_unique<DrawPipeline>(m_gpu,
                                               job.compiledLibrary,
                                               @GLSL_drawVertexMain,
                                               @GLSL_drawFragmentMain,
                                               job.drawType,
                                               job.interlockMode,
                                               job.shaderFeatures,
                                               job.shaderMiscFlags
#ifdef WITH_RIVE_TOOLS
                                               ,
                                               desc.synthesizedFailureType
#endif
                );
            if (jobKey == pipelineKey)
            {
                // The shader we wanted was actually done compiling and pending
                // being built into a pipeline.
                break;
            }
        }
    }

    if ((pipelineIter->second == nullptr || !pipelineIter->second->valid()) &&
        shaderFeatures != fullyFeaturedPipelineFeatures)
    {
        // The shader for this feature set hasn't finished compiling (or it
        // failed to compile). Use the uber-shader pipeline that has all
        // features enabled while we wait for it to finish.
        return findCompatibleDrawPipeline(
            drawType, fullyFeaturedPipelineFeatures, desc, shaderMiscFlags);
    }

    return pipelineIter->second.get();
}

void RenderContextMetalImpl::prepareToFlush(uint64_t nextFrameNumber,
                                            uint64_t safeFrameNumber)
{
    // Wait until the GPU finishes rendering flush "N + 1 - kBufferRingSize".
    // This ensures it is safe for the CPU to begin modifying the next buffers
    // in our rings.
    m_bufferRingIdx = (m_bufferRingIdx + 1) % kBufferRingSize;
    m_bufferRingLocks[m_bufferRingIdx].lock();
}

static id<MTLBuffer> mtl_buffer(const BufferRing* bufferRing)
{
    assert(bufferRing != nullptr);
    return static_cast<const BufferRingMetalImpl*>(bufferRing)
        ->submittedBuffer();
}

static MTLViewport make_viewport(uint32_t x,
                                 uint32_t y,
                                 uint32_t width,
                                 uint32_t height)
{
    return {
        static_cast<double>(x),
        static_cast<double>(y),
        static_cast<double>(width),
        static_cast<double>(height),
        0,
        1,
    };
}

static MTLScissorRect make_scissor(const TAABB<uint16_t>& scissor)
{
    return {
        static_cast<NSUInteger>(scissor.left),
        static_cast<NSUInteger>(scissor.top),
        static_cast<NSUInteger>(scissor.width()),
        static_cast<NSUInteger>(scissor.height()),
    };
}

id<MTLRenderCommandEncoder> RenderContextMetalImpl::makeRenderPassForDraws(
    const gpu::FlushDescriptor& flushDesc,
    MTLRenderPassDescriptor* passDesc,
    id<MTLCommandBuffer> commandBuffer,
    gpu::ShaderMiscFlags baselineShaderMiscFlags)
{
    auto* renderTarget =
        static_cast<RenderTargetMetal*>(flushDesc.renderTarget);

    id<MTLRenderCommandEncoder> encoder =
        [commandBuffer renderCommandEncoderWithDescriptor:passDesc];

    [encoder
        setViewport:make_viewport(
                        0, 0, renderTarget->width(), renderTarget->height())];
    [encoder setVertexBuffer:mtl_buffer(flushUniformBufferRing())
                      offset:flushDesc.flushUniformDataOffsetInBytes
                     atIndex:METAL_BUFFER_IDX(FLUSH_UNIFORM_BUFFER_IDX)];
    [encoder setFragmentBuffer:mtl_buffer(flushUniformBufferRing())
                        offset:flushDesc.flushUniformDataOffsetInBytes
                       atIndex:METAL_BUFFER_IDX(FLUSH_UNIFORM_BUFFER_IDX)];
    [encoder setVertexTexture:m_tessVertexTexture
                      atIndex:TESS_VERTEX_TEXTURE_IDX];
    [encoder setVertexTexture:m_featherTexture atIndex:FEATHER_TEXTURE_IDX];
    [encoder setFragmentTexture:m_gradientTexture atIndex:GRAD_TEXTURE_IDX];
    [encoder setFragmentTexture:m_featherTexture atIndex:FEATHER_TEXTURE_IDX];
    [encoder setFragmentTexture:m_atlasTexture atIndex:ATLAS_TEXTURE_IDX];
    if (flushDesc.pathCount > 0)
    {
        [encoder setVertexBuffer:mtl_buffer(pathBufferRing())
                          offset:flushDesc.firstPath * sizeof(gpu::PathData)
                         atIndex:METAL_BUFFER_IDX(PATH_BUFFER_IDX)];
        if (flushDesc.interlockMode == gpu::InterlockMode::atomics)
        {
            [encoder
                setFragmentBuffer:mtl_buffer(paintBufferRing())
                           offset:flushDesc.firstPaint * sizeof(gpu::PaintData)
                          atIndex:METAL_BUFFER_IDX(PAINT_BUFFER_IDX)];
            [encoder setFragmentBuffer:mtl_buffer(paintAuxBufferRing())
                                offset:flushDesc.firstPaintAux *
                                       sizeof(gpu::PaintAuxData)
                               atIndex:METAL_BUFFER_IDX(PAINT_AUX_BUFFER_IDX)];
        }
        else
        {
            [encoder
                setVertexBuffer:mtl_buffer(paintBufferRing())
                         offset:flushDesc.firstPaint * sizeof(gpu::PaintData)
                        atIndex:METAL_BUFFER_IDX(PAINT_BUFFER_IDX)];
            [encoder setVertexBuffer:mtl_buffer(paintAuxBufferRing())
                              offset:flushDesc.firstPaintAux *
                                     sizeof(gpu::PaintAuxData)
                             atIndex:METAL_BUFFER_IDX(PAINT_AUX_BUFFER_IDX)];
        }
    }
    if (flushDesc.contourCount > 0)
    {
        [encoder
            setVertexBuffer:mtl_buffer(contourBufferRing())
                     offset:flushDesc.firstContour * sizeof(gpu::ContourData)
                    atIndex:METAL_BUFFER_IDX(CONTOUR_BUFFER_IDX)];
    }
    if (flushDesc.interlockMode == gpu::InterlockMode::atomics)
    {
        // In atomic mode, the PLS planes are buffers that we need to bind
        // separately. Since the PLS plane indices collide with other buffer
        // bindings, offset the binding indices of these buffers by
        // DEFAULT_BINDINGS_SET_SIZE.
        if (!(baselineShaderMiscFlags &
              gpu::ShaderMiscFlags::fixedFunctionColorOutput))
        {
            [encoder
                setFragmentBuffer:renderTarget->colorAtomicBuffer()
                           offset:0
                          atIndex:METAL_BUFFER_IDX(COLOR_PLANE_IDX +
                                                   DEFAULT_BINDINGS_SET_SIZE)];
        }
        [encoder setFragmentBuffer:renderTarget->clipAtomicBuffer()
                            offset:0
                           atIndex:METAL_BUFFER_IDX(CLIP_PLANE_IDX +
                                                    DEFAULT_BINDINGS_SET_SIZE)];
        [encoder setFragmentBuffer:renderTarget->coverageAtomicBuffer()
                            offset:0
                           atIndex:METAL_BUFFER_IDX(COVERAGE_PLANE_IDX +
                                                    DEFAULT_BINDINGS_SET_SIZE)];
    }
    if (flushDesc.wireframe)
    {
        [encoder setTriangleFillMode:MTLTriangleFillModeLines];
    }
    return encoder;
}

void RenderContextMetalImpl::flush(const FlushDescriptor& desc)
{
    assert(desc.interlockMode != gpu::InterlockMode::clockwise);
    assert(desc.interlockMode != gpu::InterlockMode::clockwiseAtomic);
    assert(desc.interlockMode != gpu::InterlockMode::msaa); // TODO: msaa.

    auto* renderTarget = static_cast<RenderTargetMetal*>(desc.renderTarget);
    id<MTLCommandBuffer> commandBuffer =
        (__bridge id<MTLCommandBuffer>)desc.externalCommandBuffer;

    // Render the color ramps to the gradient texture.
    if (desc.gradSpanCount > 0)
    {
        // We failed to load the precompiled library and therefore do not have
        // the abililty to draw anything.
        if (!m_colorRampPipeline)
        {
            return;
        }
        // We are removing the abort in the case this doesn't build. So give up
        // drawing if we still don't have a pipeline here.
        auto pipelineState = m_colorRampPipeline->pipelineState();
        if (!pipelineState)
        {
            return;
        }
        MTLRenderPassDescriptor* gradPass =
            [MTLRenderPassDescriptor renderPassDescriptor];
        gradPass.renderTargetWidth = kGradTextureWidth;
        gradPass.renderTargetHeight = desc.gradDataHeight;
        gradPass.colorAttachments[0].loadAction = MTLLoadActionDontCare;
        gradPass.colorAttachments[0].storeAction = MTLStoreActionStore;
        gradPass.colorAttachments[0].texture = m_gradientTexture;

        id<MTLRenderCommandEncoder> gradEncoder =
            [commandBuffer renderCommandEncoderWithDescriptor:gradPass];
        [gradEncoder
            setViewport:make_viewport(0,
                                      0,
                                      kGradTextureWidth,
                                      static_cast<float>(desc.gradDataHeight))];
        [gradEncoder setRenderPipelineState:pipelineState];
        [gradEncoder
            setVertexBuffer:mtl_buffer(flushUniformBufferRing())
                     offset:desc.flushUniformDataOffsetInBytes
                    atIndex:METAL_BUFFER_IDX(FLUSH_UNIFORM_BUFFER_IDX)];
        [gradEncoder
            setVertexBuffer:mtl_buffer(gradSpanBufferRing())
                     offset:desc.firstGradSpan * sizeof(gpu::GradientSpan)
                    atIndex:0];
        [gradEncoder setCullMode:MTLCullModeBack];
        [gradEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip
                        vertexStart:0
                        vertexCount:gpu::GRAD_SPAN_TRI_STRIP_VERTEX_COUNT
                      instanceCount:desc.gradSpanCount];
        [gradEncoder endEncoding];
    }

    // Tessellate all curves into vertices in the tessellation texture.
    if (desc.tessVertexSpanCount > 0)
    {
        // We failed to load the precompiled library and therefore do not have
        // the abililty to draw anything.
        if (!m_tessPipeline)
        {
            return;
        }
        // We are removing the abort in the case this doesn't build. So give up
        // drawing if we still don't have a pipeline here.
        auto pipelineState = m_tessPipeline->pipelineState();
        if (!pipelineState)
        {
            return;
        }

        MTLRenderPassDescriptor* tessPass =
            [MTLRenderPassDescriptor renderPassDescriptor];
        tessPass.renderTargetWidth = kTessTextureWidth;
        tessPass.renderTargetHeight = desc.tessDataHeight;
        tessPass.colorAttachments[0].loadAction = MTLLoadActionDontCare;
        tessPass.colorAttachments[0].storeAction = MTLStoreActionStore;
        tessPass.colorAttachments[0].texture = m_tessVertexTexture;

        id<MTLRenderCommandEncoder> tessEncoder =
            [commandBuffer renderCommandEncoderWithDescriptor:tessPass];
        [tessEncoder
            setViewport:make_viewport(
                            0, 0, kTessTextureWidth, desc.tessDataHeight)];
        [tessEncoder setRenderPipelineState:pipelineState];
        [tessEncoder setVertexTexture:m_featherTexture
                              atIndex:FEATHER_TEXTURE_IDX];
        [tessEncoder
            setVertexBuffer:mtl_buffer(flushUniformBufferRing())
                     offset:desc.flushUniformDataOffsetInBytes
                    atIndex:METAL_BUFFER_IDX(FLUSH_UNIFORM_BUFFER_IDX)];
        [tessEncoder setVertexBuffer:mtl_buffer(tessSpanBufferRing())
                              offset:desc.firstTessVertexSpan *
                                     sizeof(gpu::TessVertexSpan)
                             atIndex:0];
        assert(desc.pathCount > 0);
        [tessEncoder setVertexBuffer:mtl_buffer(pathBufferRing())
                              offset:desc.firstPath * sizeof(gpu::PathData)
                             atIndex:METAL_BUFFER_IDX(PATH_BUFFER_IDX)];
        assert(desc.contourCount > 0);
        [tessEncoder
            setVertexBuffer:mtl_buffer(contourBufferRing())
                     offset:desc.firstContour * sizeof(gpu::ContourData)
                    atIndex:METAL_BUFFER_IDX(CONTOUR_BUFFER_IDX)];
        [tessEncoder setCullMode:MTLCullModeBack];
        [tessEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
                                indexCount:std::size(gpu::kTessSpanIndices)
                                 indexType:MTLIndexTypeUInt16
                               indexBuffer:m_tessSpanIndexBuffer
                         indexBufferOffset:0
                             instanceCount:desc.tessVertexSpanCount];
        [tessEncoder endEncoding];
    }

    // Render the atlas if we have any offscreen feathers.
    if ((desc.atlasFillBatchCount | desc.atlasStrokeBatchCount) != 0)
    {
        // We failed to load the precompiled library and therefore do not have
        // the abililty to draw anything.
        if (!m_atlasStrokePipeline || !m_atlasFillPipeline)
        {
            return;
        }
        // We are removing the abort in the case this doesn't build. So give up
        // drawing if we still don't have a pipeline here.
        auto atlasFillpipelineState = m_atlasFillPipeline->pipelineState();
        if (!atlasFillpipelineState)
        {
            return;
        }

        auto atlasStrokepipelineState = m_atlasStrokePipeline->pipelineState();
        if (!atlasStrokepipelineState)
        {
            return;
        }

        MTLRenderPassDescriptor* atlasPass =
            [MTLRenderPassDescriptor renderPassDescriptor];
        atlasPass.renderTargetWidth = desc.atlasContentWidth;
        atlasPass.renderTargetHeight = desc.atlasContentHeight;
        atlasPass.colorAttachments[0].loadAction = MTLLoadActionClear;
        atlasPass.colorAttachments[0].storeAction = MTLStoreActionStore;
        atlasPass.colorAttachments[0].texture = m_atlasTexture;
        atlasPass.colorAttachments[0].clearColor =
            MTLClearColorMake(0, 0, 0, 0);

        id<MTLRenderCommandEncoder> atlasEncoder =
            [commandBuffer renderCommandEncoderWithDescriptor:atlasPass];
        [atlasEncoder setViewport:make_viewport(0,
                                                0,
                                                desc.atlasContentWidth,
                                                desc.atlasContentHeight)];
        [atlasEncoder
            setVertexBuffer:mtl_buffer(flushUniformBufferRing())
                     offset:desc.flushUniformDataOffsetInBytes
                    atIndex:METAL_BUFFER_IDX(FLUSH_UNIFORM_BUFFER_IDX)];
        [atlasEncoder
            setFragmentBuffer:mtl_buffer(flushUniformBufferRing())
                       offset:desc.flushUniformDataOffsetInBytes
                      atIndex:METAL_BUFFER_IDX(FLUSH_UNIFORM_BUFFER_IDX)];
        [atlasEncoder setVertexTexture:m_tessVertexTexture
                               atIndex:TESS_VERTEX_TEXTURE_IDX];
        [atlasEncoder setVertexTexture:m_featherTexture
                               atIndex:FEATHER_TEXTURE_IDX];
        [atlasEncoder setFragmentTexture:m_gradientTexture
                                 atIndex:GRAD_TEXTURE_IDX];
        [atlasEncoder setFragmentTexture:m_featherTexture
                                 atIndex:FEATHER_TEXTURE_IDX];
        if (desc.pathCount > 0)
        {
            [atlasEncoder setVertexBuffer:mtl_buffer(pathBufferRing())
                                   offset:desc.firstPath * sizeof(gpu::PathData)
                                  atIndex:METAL_BUFFER_IDX(PATH_BUFFER_IDX)];
            [atlasEncoder
                setVertexBuffer:mtl_buffer(paintBufferRing())
                         offset:desc.firstPaint * sizeof(gpu::PaintData)
                        atIndex:METAL_BUFFER_IDX(PAINT_BUFFER_IDX)];
            [atlasEncoder
                setVertexBuffer:mtl_buffer(paintAuxBufferRing())
                         offset:desc.firstPaintAux * sizeof(gpu::PaintAuxData)
                        atIndex:METAL_BUFFER_IDX(PAINT_AUX_BUFFER_IDX)];
        }
        if (desc.contourCount > 0)
        {
            [atlasEncoder
                setVertexBuffer:mtl_buffer(contourBufferRing())
                         offset:desc.firstContour * sizeof(gpu::ContourData)
                        atIndex:METAL_BUFFER_IDX(CONTOUR_BUFFER_IDX)];
        }
        [atlasEncoder setVertexBuffer:m_pathPatchVertexBuffer
                               offset:0
                              atIndex:0];
        [atlasEncoder setCullMode:MTLCullModeBack];

        if (desc.atlasFillBatchCount != 0)
        {
            [atlasEncoder setRenderPipelineState:atlasFillpipelineState];
            for (size_t i = 0; i < desc.atlasFillBatchCount; ++i)
            {
                const gpu::AtlasDrawBatch& fillBatch = desc.atlasFillBatches[i];
                [atlasEncoder setScissorRect:make_scissor(fillBatch.scissor)];
                [atlasEncoder
                    setVertexBytes:&fillBatch.basePatch
                            length:sizeof(uint32_t)
                           atIndex:METAL_BUFFER_IDX(
                                       PATH_BASE_INSTANCE_UNIFORM_BUFFER_IDX)];
                [atlasEncoder
                    drawIndexedPrimitives:MTLPrimitiveTypeTriangle
                               indexCount:
                                   gpu::kMidpointFanCenterAAPatchIndexCount
                                indexType:MTLIndexTypeUInt16
                              indexBuffer:m_pathPatchIndexBuffer
                        indexBufferOffset:
                            gpu::kMidpointFanCenterAAPatchBaseIndex *
                            sizeof(uint16_t)
                            instanceCount:fillBatch.patchCount];
            }
        }

        if (desc.atlasStrokeBatchCount != 0)
        {
            [atlasEncoder setRenderPipelineState:atlasStrokepipelineState];
            for (size_t i = 0; i < desc.atlasStrokeBatchCount; ++i)
            {
                const gpu::AtlasDrawBatch& strokeBatch =
                    desc.atlasStrokeBatches[i];
                [atlasEncoder setScissorRect:make_scissor(strokeBatch.scissor)];
                [atlasEncoder
                    setVertexBytes:&strokeBatch.basePatch
                            length:sizeof(uint32_t)
                           atIndex:METAL_BUFFER_IDX(
                                       PATH_BASE_INSTANCE_UNIFORM_BUFFER_IDX)];
                [atlasEncoder
                    drawIndexedPrimitives:MTLPrimitiveTypeTriangle
                               indexCount:gpu::kMidpointFanPatchBorderIndexCount
                                indexType:MTLIndexTypeUInt16
                              indexBuffer:m_pathPatchIndexBuffer
                        indexBufferOffset:gpu::kMidpointFanPatchBaseIndex *
                                          sizeof(uint16_t)
                            instanceCount:strokeBatch.patchCount];
            }
        }

        [atlasEncoder endEncoding];
    }

    // Generate mipmaps if needed.
    for (const DrawBatch& batch : *desc.drawList)
    {
        if (auto imageTextureMetal =
                static_cast<const TextureMetalImpl*>(batch.imageTexture))
        {
            imageTextureMetal->ensureMipmaps(commandBuffer);
        }
    }

    // Set up a render pass to do the final rendering using (some form of) pixel
    // local storage.
    MTLRenderPassDescriptor* pass =
        [MTLRenderPassDescriptor renderPassDescriptor];
    pass.renderTargetWidth = desc.renderTargetUpdateBounds.right;
    pass.renderTargetHeight = desc.renderTargetUpdateBounds.bottom;
    pass.colorAttachments[COLOR_PLANE_IDX].texture =
        renderTarget->targetTexture();
    switch (desc.colorLoadAction)
    {
        case gpu::LoadAction::clear:
        {
            float cc[4];
            UnpackColorToRGBA32FPremul(desc.colorClearValue, cc);
            pass.colorAttachments[COLOR_PLANE_IDX].loadAction =
                MTLLoadActionClear;
            pass.colorAttachments[COLOR_PLANE_IDX].clearColor =
                MTLClearColorMake(cc[0], cc[1], cc[2], cc[3]);
            break;
        }
        case gpu::LoadAction::preserveRenderTarget:
            pass.colorAttachments[COLOR_PLANE_IDX].loadAction =
                MTLLoadActionLoad;
            break;
        case gpu::LoadAction::dontCare:
            pass.colorAttachments[COLOR_PLANE_IDX].loadAction =
                MTLLoadActionDontCare;
            break;
    }
    pass.colorAttachments[COLOR_PLANE_IDX].storeAction = MTLStoreActionStore;

    auto baselineShaderMiscFlags = gpu::ShaderMiscFlags::none;
    if (desc.interlockMode == gpu::InterlockMode::rasterOrdering)
    {
        // In rasterOrdering mode, the PLS planes are accessed as color
        // attachments.
        pass.colorAttachments[CLIP_PLANE_IDX].texture =
            renderTarget->m_clipMemorylessTexture;
        pass.colorAttachments[CLIP_PLANE_IDX].loadAction = MTLLoadActionClear;
        pass.colorAttachments[CLIP_PLANE_IDX].clearColor =
            MTLClearColorMake(0, 0, 0, 0);
        pass.colorAttachments[CLIP_PLANE_IDX].storeAction =
            MTLStoreActionDontCare;

        pass.colorAttachments[SCRATCH_COLOR_PLANE_IDX].texture =
            renderTarget->m_scratchColorMemorylessTexture;
        pass.colorAttachments[SCRATCH_COLOR_PLANE_IDX].loadAction =
            MTLLoadActionDontCare;
        pass.colorAttachments[SCRATCH_COLOR_PLANE_IDX].storeAction =
            MTLStoreActionDontCare;

        pass.colorAttachments[COVERAGE_PLANE_IDX].texture =
            renderTarget->m_coverageMemorylessTexture;
        pass.colorAttachments[COVERAGE_PLANE_IDX].loadAction =
            MTLLoadActionClear;
        pass.colorAttachments[COVERAGE_PLANE_IDX].clearColor =
            MTLClearColorMake(desc.coverageClearValue, 0, 0, 0);
        pass.colorAttachments[COVERAGE_PLANE_IDX].storeAction =
            MTLStoreActionDontCare;
    }
    else if (desc.colorLoadAction == gpu::LoadAction::preserveRenderTarget &&
             !desc.fixedFunctionColorOutput)
    {
        // Since we need to preserve the renderTarget during load, and since
        // we're rendering to an offscreen color buffer, we have to literally
        // copy the renderTarget into the color buffer.
        assert(desc.interlockMode == gpu::InterlockMode::atomics);
        id<MTLBlitCommandEncoder> copyEncoder =
            [commandBuffer blitCommandEncoder];
        auto updateOrigin = MTLOriginMake(desc.renderTargetUpdateBounds.left,
                                          desc.renderTargetUpdateBounds.top,
                                          0);
        auto updateSize = MTLSizeMake(desc.renderTargetUpdateBounds.width(),
                                      desc.renderTargetUpdateBounds.height(),
                                      1);
        [copyEncoder copyFromTexture:renderTarget->targetTexture()
                         sourceSlice:0
                         sourceLevel:0
                        sourceOrigin:updateOrigin
                          sourceSize:updateSize
                            toBuffer:renderTarget->colorAtomicBuffer()
                   destinationOffset:(updateOrigin.y * renderTarget->width() +
                                      updateOrigin.x) *
                                     sizeof(uint32_t)
              destinationBytesPerRow:renderTarget->width() * sizeof(uint32_t)
            destinationBytesPerImage:renderTarget->height() *
                                     renderTarget->width() * sizeof(uint32_t)];
        [copyEncoder endEncoding];
    }

    // Execute the DrawList.
    id<MTLRenderCommandEncoder> encoder = makeRenderPassForDraws(
        desc, pass, commandBuffer, baselineShaderMiscFlags);
    for (const DrawBatch& batch : *desc.drawList)
    {
        // Setup the pipeline for this specific drawType and shaderFeatures.
        gpu::ShaderFeatures shaderFeatures;
        if (desc.interlockMode == gpu::InterlockMode::atomics)
        {
            // The combined shader features might have more flags set than are
            // actually relevant for this draw type, so filter them out.
            shaderFeatures =
                desc.combinedShaderFeatures &
                gpu::ShaderFeaturesMaskFor(batch.drawType, desc.interlockMode);
        }
        else
        {
            shaderFeatures = batch.shaderFeatures;
        }

        gpu::ShaderMiscFlags batchMiscFlags =
            baselineShaderMiscFlags | batch.shaderMiscFlags;
        if (!(batchMiscFlags & gpu::ShaderMiscFlags::fixedFunctionColorOutput))
        {
            if (batch.drawType == gpu::DrawType::renderPassResolve)
            {
                // Atomic mode can always do a coalesced resolve when rendering
                // to an offscreen color buffer.
                assert(desc.interlockMode == gpu::InterlockMode::atomics);
                batchMiscFlags |=
                    gpu::ShaderMiscFlags::coalescedResolveAndTransfer;
            }
            else if (batch.drawType == gpu::DrawType::renderPassInitialize)
            {
                assert(desc.interlockMode == gpu::InterlockMode::atomics);
                if (desc.colorLoadAction == gpu::LoadAction::clear)
                {
                    batchMiscFlags |= gpu::ShaderMiscFlags::storeColorClear;
                }
                else if (desc.colorLoadAction ==
                             gpu::LoadAction::preserveRenderTarget &&
                         renderTarget->pixelFormat() ==
                             MTLPixelFormatBGRA8Unorm)
                {
                    // We already copied the renderTarget to our color buffer,
                    // but since the target is BGRA, we also need to swizzle it
                    // to RGBA before it's ready for PLS.
                    batchMiscFlags |=
                        gpu::ShaderMiscFlags::swizzleColorBGRAToRGBA;
                }
            }
        }
        const DrawPipeline* drawPipeline = findCompatibleDrawPipeline(
            batch.drawType, shaderFeatures, desc, batchMiscFlags);
        if (drawPipeline == nullptr || !drawPipeline->valid())
        {
            // The shader for this draw AND the uber-shader both failed to
            // compile. This should virtually never happen, and can only happen
            // on non-Apple Silicon, where we don't use precompiled shaders.
            // Skip the draw.
            continue;
        }
        id<MTLRenderPipelineState> drawPipelineState =
            drawPipeline->pipelineState(renderTarget->pixelFormat());

        // Bind the appropriate image texture, if any.
        if (auto imageTextureMetal =
                static_cast<const TextureMetalImpl*>(batch.imageTexture))
        {
            [encoder setFragmentTexture:imageTextureMetal->texture()
                                atIndex:IMAGE_TEXTURE_IDX];

            [encoder setFragmentSamplerState:m_imageSamplers[batch.imageSampler
                                                                 .asKey()]
                                     atIndex:IMAGE_SAMPLER_IDX];
        }
        else
        {
            [encoder setFragmentSamplerState:
                         m_imageSamplers[ImageSampler::LINEAR_CLAMP_SAMPLER_KEY]
                                     atIndex:IMAGE_SAMPLER_IDX];
        }

        // Issue any barriers if needed.
        if (batch.barriers &
            (BarrierFlags::plsAtomic | BarrierFlags::plsAtomicPreResolve))
        {
            assert(desc.interlockMode == gpu::InterlockMode::atomics);
            switch (m_metalFeatures.atomicBarrierType)
            {
                case AtomicBarrierType::memoryBarrier:
#if defined(RIVE_MACOSX)
                    if (@available(macOS 10.14, *))
                    {
                        [encoder
                            memoryBarrierWithScope:MTLBarrierScopeBuffers |
                                                   MTLBarrierScopeRenderTargets
                                       afterStages:MTLRenderStageFragment
                                      beforeStages:MTLRenderStageFragment];
                        break;
                    }
#endif
                    // atomicBarrierType shouldn't be "memoryBarrier" in this
                    // case.
                    RIVE_UNREACHABLE();

                case AtomicBarrierType::rasterOrderGroup:
                    break;

                case AtomicBarrierType::renderPassBreak:
                    // On very old hardware that can't support barriers, we just
                    // take a sledge hammer and break the entire render pass
                    // between overlapping draws.
                    // TODO: Is there a lighter way to achieve this?
                    [encoder endEncoding];
                    pass.colorAttachments[COLOR_PLANE_IDX].loadAction =
                        MTLLoadActionLoad;
                    encoder = makeRenderPassForDraws(
                        desc, pass, commandBuffer, baselineShaderMiscFlags);
                    break;
            }
        }

        DrawType drawType = batch.drawType;
        switch (drawType)
        {
            case DrawType::midpointFanPatches:
            case DrawType::midpointFanCenterAAPatches:
            case DrawType::outerCurvePatches:
            {
                // Draw PLS patches that connect the tessellation vertices.
                [encoder setRenderPipelineState:drawPipelineState];
                [encoder setVertexBuffer:m_pathPatchVertexBuffer
                                  offset:0
                                 atIndex:0];
                [encoder setCullMode:MTLCullModeBack];
                // Don't use baseInstance in order to run on Apple GPU Family 2.
                // TODO: Use baseInstance instead once we deprecate Apple2.
                [encoder
                    setVertexBytes:&batch.baseElement
                            length:sizeof(uint32_t)
                           atIndex:METAL_BUFFER_IDX(
                                       PATH_BASE_INSTANCE_UNIFORM_BUFFER_IDX)];
                [encoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
                                    indexCount:gpu::PatchIndexCount(drawType)
                                     indexType:MTLIndexTypeUInt16
                                   indexBuffer:m_pathPatchIndexBuffer
                             indexBufferOffset:gpu::PatchBaseIndex(drawType) *
                                               sizeof(uint16_t)
                                 instanceCount:batch.elementCount];
                break;
            }
            case DrawType::interiorTriangulation:
            case DrawType::atlasBlit:
            {
                [encoder setRenderPipelineState:drawPipelineState];
                [encoder setVertexBuffer:mtl_buffer(triangleBufferRing())
                                  offset:0
                                 atIndex:0];
                [encoder setCullMode:MTLCullModeBack];
                [encoder drawPrimitives:MTLPrimitiveTypeTriangle
                            vertexStart:batch.baseElement
                            vertexCount:batch.elementCount];
                break;
            }
            case DrawType::imageRect:
            case DrawType::imageMesh:
            {
                [encoder setRenderPipelineState:drawPipelineState];
                [encoder
                    setVertexBuffer:mtl_buffer(imageDrawUniformBufferRing())
                             offset:batch.imageDrawDataOffset
                            atIndex:METAL_BUFFER_IDX(
                                        IMAGE_DRAW_UNIFORM_BUFFER_IDX)];
                [encoder
                    setFragmentBuffer:mtl_buffer(imageDrawUniformBufferRing())
                               offset:batch.imageDrawDataOffset
                              atIndex:METAL_BUFFER_IDX(
                                          IMAGE_DRAW_UNIFORM_BUFFER_IDX)];
                [encoder setCullMode:MTLCullModeNone];
                if (drawType == DrawType::imageRect)
                {
                    assert(desc.interlockMode == gpu::InterlockMode::atomics);
                    [encoder setVertexBuffer:m_imageRectVertexBuffer
                                      offset:0
                                     atIndex:0];
                    [encoder
                        drawIndexedPrimitives:MTLPrimitiveTypeTriangle
                                   indexCount:std::size(gpu::kImageRectIndices)
                                    indexType:MTLIndexTypeUInt16
                                  indexBuffer:m_imageRectIndexBuffer
                            indexBufferOffset:0];
                }
                else
                {
                    LITE_RTTI_CAST_OR_BREAK(vertexBuffer,
                                            RenderBufferMetalImpl*,
                                            batch.vertexBuffer);
                    LITE_RTTI_CAST_OR_BREAK(
                        uvBuffer, RenderBufferMetalImpl*, batch.uvBuffer);
                    LITE_RTTI_CAST_OR_BREAK(
                        indexBuffer, RenderBufferMetalImpl*, batch.indexBuffer);
                    [encoder setVertexBuffer:vertexBuffer->submittedBuffer()
                                      offset:0
                                     atIndex:0];
                    [encoder setVertexBuffer:uvBuffer->submittedBuffer()
                                      offset:0
                                     atIndex:1];
                    [encoder
                        drawIndexedPrimitives:MTLPrimitiveTypeTriangle
                                   indexCount:batch.elementCount
                                    indexType:MTLIndexTypeUInt16
                                  indexBuffer:indexBuffer->submittedBuffer()
                            indexBufferOffset:batch.baseElement *
                                              sizeof(uint16_t)];
                }
                break;
            }
            case DrawType::renderPassInitialize:
            case DrawType::renderPassResolve:
            {
                assert(desc.interlockMode == gpu::InterlockMode::atomics);
                [encoder setRenderPipelineState:drawPipelineState];
                [encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip
                            vertexStart:0
                            vertexCount:4];
                break;
            }
            case DrawType::msaaStrokes:
            case DrawType::msaaMidpointFanBorrowedCoverage:
            case DrawType::msaaMidpointFans:
            case DrawType::msaaMidpointFanStencilReset:
            case DrawType::msaaMidpointFanPathsStencil:
            case DrawType::msaaMidpointFanPathsCover:
            case DrawType::msaaOuterCubics:
            case DrawType::msaaStencilClipReset:
            {
                RIVE_UNREACHABLE();
            }
        }
    }
    [encoder endEncoding];
}

void RenderContextMetalImpl::postFlush(
    const RenderContext::FlushResources& flushResources)
{
    // Schedule a callback that will unlock the buffers used by this flush,
    // after the GPU has finished rendering with them. This unblocks the CPU
    // from reusing them in a future flush.
    id<MTLCommandBuffer> commandBuffer =
        (__bridge id<MTLCommandBuffer>)flushResources.externalCommandBuffer;
    std::mutex& thisFlushLock = m_bufferRingLocks[m_bufferRingIdx];
    [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>) {
      assert(!thisFlushLock.try_lock()); // The mutex should already be locked.
      thisFlushLock.unlock();
    }];
}
} // namespace rive::gpu
