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

#include "src/gpu/mtl/GrMtlOpsRenderPass.h"

#include "src/gpu/GrBackendUtils.h"
#include "src/gpu/GrColor.h"
#include "src/gpu/GrRenderTarget.h"
#include "src/gpu/mtl/GrMtlCommandBuffer.h"
#include "src/gpu/mtl/GrMtlPipelineState.h"
#include "src/gpu/mtl/GrMtlPipelineStateBuilder.h"
#include "src/gpu/mtl/GrMtlRenderCommandEncoder.h"
#include "src/gpu/mtl/GrMtlRenderTarget.h"
#include "src/gpu/mtl/GrMtlTexture.h"

#if !__has_feature(objc_arc)
#error This file must be compiled with Arc. Use -fobjc-arc flag
#endif

GR_NORETAIN_BEGIN

GrMtlOpsRenderPass::GrMtlOpsRenderPass(GrMtlGpu* gpu, GrRenderTarget* rt,
                                       sk_sp<GrMtlFramebuffer> framebuffer, GrSurfaceOrigin origin,
                                       const GrOpsRenderPass::LoadAndStoreInfo& colorInfo,
                                       const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo)
        : INHERITED(rt, origin)
        , fGpu(gpu)
        , fFramebuffer(std::move(framebuffer)) {
    this->setupRenderPass(colorInfo, stencilInfo);
}

GrMtlOpsRenderPass::~GrMtlOpsRenderPass() {
}

void GrMtlOpsRenderPass::submit() {
    if (!fFramebuffer) {
        return;
    }
    SkIRect iBounds;
    fBounds.roundOut(&iBounds);
    fGpu->submitIndirectCommandBuffer(fRenderTarget, fOrigin, &iBounds);
    fActiveRenderCmdEncoder = nil;
}

static MTLPrimitiveType gr_to_mtl_primitive(GrPrimitiveType primitiveType) {
    const static MTLPrimitiveType mtlPrimitiveType[] {
        MTLPrimitiveTypeTriangle,
        MTLPrimitiveTypeTriangleStrip,
        MTLPrimitiveTypePoint,
        MTLPrimitiveTypeLine,
        MTLPrimitiveTypeLineStrip
    };
    static_assert((int)GrPrimitiveType::kTriangles == 0);
    static_assert((int)GrPrimitiveType::kTriangleStrip == 1);
    static_assert((int)GrPrimitiveType::kPoints == 2);
    static_assert((int)GrPrimitiveType::kLines == 3);
    static_assert((int)GrPrimitiveType::kLineStrip == 4);

    SkASSERT(primitiveType <= GrPrimitiveType::kLineStrip);
    return mtlPrimitiveType[static_cast<int>(primitiveType)];
}

bool GrMtlOpsRenderPass::onBindPipeline(const GrProgramInfo& programInfo,
                                        const SkRect& drawBounds) {
    const GrMtlCaps& caps = fGpu->mtlCaps();
    GrProgramDesc programDesc = caps.makeDesc(fRenderTarget, programInfo,
                                              GrCaps::ProgramDescOverrideFlags::kNone);
    if (!programDesc.isValid()) {
        return false;
    }

    fActivePipelineState = fGpu->resourceProvider().findOrCreateCompatiblePipelineState(
            programDesc, programInfo);
    if (!fActivePipelineState) {
        return false;
    }

    fActivePipelineState->setData(fFramebuffer.get(), programInfo);
    fCurrentVertexStride = programInfo.geomProc().vertexStride();

    if (!fActiveRenderCmdEncoder) {
        fActiveRenderCmdEncoder =
                fGpu->commandBuffer()->getRenderCommandEncoder(fRenderPassDesc,
                                                               fActivePipelineState, this);
        fGpu->commandBuffer()->addGrSurface(
                sk_ref_sp<GrMtlAttachment>(fFramebuffer->colorAttachment()));
    }

    fActiveRenderCmdEncoder->setRenderPipelineState(
            fActivePipelineState->pipeline()->mtlPipelineState());
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    if (!fDebugGroupActive) {
        fActiveRenderCmdEncoder->pushDebugGroup(@"bindAndDraw");
        fDebugGroupActive = true;
    }
#endif
    fActivePipelineState->setDrawState(fActiveRenderCmdEncoder,
                                       programInfo.pipeline().writeSwizzle(),
                                       programInfo.pipeline().getXferProcessor());
    if (this->gpu()->caps()->wireframeMode() || programInfo.pipeline().isWireframe()) {
        fActiveRenderCmdEncoder->setTriangleFillMode(MTLTriangleFillModeLines);
    } else {
        fActiveRenderCmdEncoder->setTriangleFillMode(MTLTriangleFillModeFill);
    }

    if (!programInfo.pipeline().isScissorTestEnabled()) {
        // "Disable" scissor by setting it to the full pipeline bounds.
        SkISize dimensions = fFramebuffer->colorAttachment()->dimensions();
        GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder,
                                                       dimensions, fOrigin,
                                                       SkIRect::MakeWH(dimensions.width(),
                                                                       dimensions.height()));
    }

    fActivePrimitiveType = gr_to_mtl_primitive(programInfo.primitiveType());
    fBounds.join(drawBounds);
    return true;
}

void GrMtlOpsRenderPass::onSetScissorRect(const SkIRect& scissor) {
    SkASSERT(fActivePipelineState);
    SkASSERT(fActiveRenderCmdEncoder);
    GrMtlPipelineState::SetDynamicScissorRectState(fActiveRenderCmdEncoder,
                                                   fFramebuffer->colorAttachment()->dimensions(),
                                                   fOrigin, scissor);
}

bool GrMtlOpsRenderPass::onBindTextures(const GrGeometryProcessor& geomProc,
                                        const GrSurfaceProxy* const geomProcTextures[],
                                        const GrPipeline& pipeline) {
    SkASSERT(fActivePipelineState);
    SkASSERT(fActiveRenderCmdEncoder);
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    if (!fDebugGroupActive) {
        fActiveRenderCmdEncoder->pushDebugGroup(@"bindAndDraw");
        fDebugGroupActive = true;
    }
#endif
    fActivePipelineState->setTextures(geomProc, pipeline, geomProcTextures);
    fActivePipelineState->bindTextures(fActiveRenderCmdEncoder);
    return true;
}

void GrMtlOpsRenderPass::onClear(const GrScissorState& scissor, std::array<float, 4> color) {
    // Partial clears are not supported
    SkASSERT(!scissor.enabled());

    // Ideally we should never end up here since all clears should either be done as draws or
    // load ops in metal. However, if a client inserts a wait op we need to handle it.
    auto colorAttachment = fRenderPassDesc.colorAttachments[0];
    colorAttachment.clearColor = MTLClearColorMake(color[0], color[1], color[2], color[3]);
    colorAttachment.loadAction = MTLLoadActionClear;
    fActiveRenderCmdEncoder =
            fGpu->commandBuffer()->getRenderCommandEncoder(fRenderPassDesc, nullptr, this);
}

void GrMtlOpsRenderPass::onClearStencilClip(const GrScissorState& scissor, bool insideStencilMask) {
    // Partial clears are not supported
    SkASSERT(!scissor.enabled());

    GrAttachment* sb = fFramebuffer->stencilAttachment();
    // this should only be called internally when we know we have a
    // stencil buffer.
    SkASSERT(sb);
    int stencilBitCount = GrBackendFormatStencilBits(sb->backendFormat());

    // The contract with the callers does not guarantee that we preserve all bits in the stencil
    // during this clear. Thus we will clear the entire stencil to the desired value.
    auto stencilAttachment = fRenderPassDesc.stencilAttachment;
    if (insideStencilMask) {
        stencilAttachment.clearStencil = (1 << (stencilBitCount - 1));
    } else {
        stencilAttachment.clearStencil = 0;
    }

    stencilAttachment.loadAction = MTLLoadActionClear;
    fActiveRenderCmdEncoder =
            fGpu->commandBuffer()->getRenderCommandEncoder(fRenderPassDesc, nullptr, this);
}

void GrMtlOpsRenderPass::inlineUpload(GrOpFlushState* state, GrDeferredTextureUploadFn& upload) {
    // TODO: could this be more efficient?
    state->doUpload(upload);
    // doUpload() creates a blitCommandEncoder, so if we had a previous render we need to
    // adjust the renderPassDescriptor to load from it.
    if (fActiveRenderCmdEncoder) {
        auto colorAttachment = fRenderPassDesc.colorAttachments[0];
        colorAttachment.loadAction = MTLLoadActionLoad;
        auto mtlStencil = fRenderPassDesc.stencilAttachment;
        mtlStencil.loadAction = MTLLoadActionLoad;
    }
    // If the previous renderCommandEncoder did a resolve without an MSAA store
    // (e.g., if the color attachment is memoryless) we need to copy the contents of
    // the resolve attachment to the MSAA attachment at this point.
    this->setupResolve();

    // It's probably reasonable to create the new encoder at this point, though maybe not necessary.
    fActiveRenderCmdEncoder =
            fGpu->commandBuffer()->getRenderCommandEncoder(fRenderPassDesc, nullptr, this);
}

void GrMtlOpsRenderPass::initRenderState(GrMtlRenderCommandEncoder* encoder) {
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    encoder->pushDebugGroup(@"initRenderState");
#endif
    encoder->setFrontFacingWinding(MTLWindingCounterClockwise);
    SkISize colorAttachmentDimensions = fFramebuffer->colorAttachment()->dimensions();
    // Strictly speaking we shouldn't have to set this, as the default viewport is the size of
    // the drawable used to generate the renderCommandEncoder -- but just in case.
    MTLViewport viewport = { 0.0, 0.0,
                             (double) colorAttachmentDimensions.width(),
                             (double) colorAttachmentDimensions.height(),
                             0.0, 1.0 };
    encoder->setViewport(viewport);
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    encoder->popDebugGroup();
#endif
}

void GrMtlOpsRenderPass::setupRenderPass(
        const GrOpsRenderPass::LoadAndStoreInfo& colorInfo,
        const GrOpsRenderPass::StencilLoadAndStoreInfo& stencilInfo) {
    const static MTLLoadAction mtlLoadAction[] {
        MTLLoadActionLoad,
        MTLLoadActionClear,
        MTLLoadActionDontCare
    };
    static_assert((int)GrLoadOp::kLoad == 0);
    static_assert((int)GrLoadOp::kClear == 1);
    static_assert((int)GrLoadOp::kDiscard == 2);
    SkASSERT(colorInfo.fLoadOp <= GrLoadOp::kDiscard);
    SkASSERT(stencilInfo.fLoadOp <= GrLoadOp::kDiscard);

    const static MTLStoreAction mtlStoreAction[] {
        MTLStoreActionStore,
        MTLStoreActionDontCare
    };
    static_assert((int)GrStoreOp::kStore == 0);
    static_assert((int)GrStoreOp::kDiscard == 1);
    SkASSERT(colorInfo.fStoreOp <= GrStoreOp::kDiscard);
    SkASSERT(stencilInfo.fStoreOp <= GrStoreOp::kDiscard);

    fRenderPassDesc = [MTLRenderPassDescriptor new];
    auto colorAttachment = fRenderPassDesc.colorAttachments[0];
    auto color = fFramebuffer->colorAttachment();
    colorAttachment.texture = color->mtlTexture();
    const std::array<float, 4>& clearColor = colorInfo.fClearColor;
    colorAttachment.clearColor =
            MTLClearColorMake(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
    colorAttachment.loadAction = mtlLoadAction[static_cast<int>(colorInfo.fLoadOp)];
    colorAttachment.storeAction = mtlStoreAction[static_cast<int>(colorInfo.fStoreOp)];

    auto stencil = fFramebuffer->stencilAttachment();
    auto mtlStencil = fRenderPassDesc.stencilAttachment;
    if (stencil) {
        mtlStencil.texture = stencil->mtlTexture();
    }
    mtlStencil.clearStencil = 0;
    mtlStencil.loadAction = mtlLoadAction[static_cast<int>(stencilInfo.fLoadOp)];
    mtlStencil.storeAction = mtlStoreAction[static_cast<int>(stencilInfo.fStoreOp)];

    this->setupResolve();

    // Manage initial clears
    if (colorInfo.fLoadOp == GrLoadOp::kClear || stencilInfo.fLoadOp == GrLoadOp::kClear)  {
        fBounds = SkRect::MakeWH(color->dimensions().width(),
                                 color->dimensions().height());
        fActiveRenderCmdEncoder =
                fGpu->commandBuffer()->getRenderCommandEncoder(fRenderPassDesc, nullptr, this);
    } else {
        fBounds.setEmpty();
        // For now, we lazily create the renderCommandEncoder because we may have no draws,
        // and an empty renderCommandEncoder can still produce output. This can cause issues
        // when we've cleared a texture upon creation -- we'll subsequently discard the contents.
        // This can be removed when that ordering is fixed.
        fActiveRenderCmdEncoder = nil;
    }
}

void GrMtlOpsRenderPass::setupResolve() {
    auto resolve = fFramebuffer->resolveAttachment();
    if (resolve) {
        auto colorAttachment = fRenderPassDesc.colorAttachments[0];
        colorAttachment.resolveTexture = resolve->mtlTexture();
        // TODO: For framebufferOnly attachments we should do StoreAndMultisampleResolve if
        // the storeAction is Store. But for the moment they don't take this path.
        colorAttachment.storeAction = MTLStoreActionMultisampleResolve;
        if (colorAttachment.loadAction == MTLLoadActionLoad) {
            auto color = fFramebuffer->colorAttachment();
            auto dimensions = color->dimensions();
            // for now use the full bounds
            auto nativeBounds = GrNativeRect::MakeIRectRelativeTo(
                    fOrigin, dimensions.height(), SkIRect::MakeSize(dimensions));
            fGpu->loadMSAAFromResolve(color, resolve, nativeBounds,
                                      fRenderPassDesc.stencilAttachment);
        }
    }
}

void GrMtlOpsRenderPass::onBindBuffers(sk_sp<const GrBuffer> indexBuffer,
                                       sk_sp<const GrBuffer> instanceBuffer,
                                       sk_sp<const GrBuffer> vertexBuffer,
                                       GrPrimitiveRestart primRestart) {
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    if (!fDebugGroupActive) {
        fActiveRenderCmdEncoder->pushDebugGroup(@"bindAndDraw");
        fDebugGroupActive = true;
    }
#endif
    SkASSERT(GrPrimitiveRestart::kNo == primRestart);
    int inputBufferIndex = 0;
    if (vertexBuffer) {
        SkASSERT(!vertexBuffer->isCpuBuffer());
        SkASSERT(!static_cast<const GrGpuBuffer*>(vertexBuffer.get())->isMapped());
        fActiveVertexBuffer = std::move(vertexBuffer);
        fGpu->commandBuffer()->addGrBuffer(fActiveVertexBuffer);
        ++inputBufferIndex;
    }
    if (instanceBuffer) {
        SkASSERT(!instanceBuffer->isCpuBuffer());
        SkASSERT(!static_cast<const GrGpuBuffer*>(instanceBuffer.get())->isMapped());
        this->setVertexBuffer(fActiveRenderCmdEncoder, instanceBuffer.get(), 0, inputBufferIndex++);
        fActiveInstanceBuffer = std::move(instanceBuffer);
        fGpu->commandBuffer()->addGrBuffer(fActiveInstanceBuffer);
    }
    if (indexBuffer) {
        SkASSERT(!indexBuffer->isCpuBuffer());
        SkASSERT(!static_cast<const GrGpuBuffer*>(indexBuffer.get())->isMapped());
        fActiveIndexBuffer = std::move(indexBuffer);
        fGpu->commandBuffer()->addGrBuffer(fActiveIndexBuffer);
    }
}

void GrMtlOpsRenderPass::onDraw(int vertexCount, int baseVertex) {
    SkASSERT(fActivePipelineState);
    SkASSERT(nil != fActiveRenderCmdEncoder);
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    if (!fDebugGroupActive) {
        fActiveRenderCmdEncoder->pushDebugGroup(@"bindAndDraw");
        fDebugGroupActive = true;
    }
#endif
    this->setVertexBuffer(fActiveRenderCmdEncoder, fActiveVertexBuffer.get(), 0, 0);

    fActiveRenderCmdEncoder->drawPrimitives(fActivePrimitiveType, baseVertex, vertexCount);
    fGpu->stats()->incNumDraws();
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    SkASSERT(fDebugGroupActive);
    fActiveRenderCmdEncoder->popDebugGroup();
    fDebugGroupActive = false;
#endif
}

void GrMtlOpsRenderPass::onDrawIndexed(int indexCount, int baseIndex, uint16_t minIndexValue,
                                       uint16_t maxIndexValue, int baseVertex) {
    SkASSERT(fActivePipelineState);
    SkASSERT(nil != fActiveRenderCmdEncoder);
    SkASSERT(fActiveIndexBuffer);
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    if (!fDebugGroupActive) {
        fActiveRenderCmdEncoder->pushDebugGroup(@"bindAndDraw");
        fDebugGroupActive = true;
    }
#endif
    this->setVertexBuffer(fActiveRenderCmdEncoder, fActiveVertexBuffer.get(),
                          fCurrentVertexStride * baseVertex, 0);

    auto mtlIndexBuffer = static_cast<const GrMtlBuffer*>(fActiveIndexBuffer.get());
    size_t indexOffset = sizeof(uint16_t) * baseIndex;
    id<MTLBuffer> indexBuffer = mtlIndexBuffer->mtlBuffer();
    fActiveRenderCmdEncoder->drawIndexedPrimitives(fActivePrimitiveType, indexCount,
                                                   MTLIndexTypeUInt16, indexBuffer, indexOffset);
    fGpu->stats()->incNumDraws();
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    SkASSERT(fDebugGroupActive);
    fActiveRenderCmdEncoder->popDebugGroup();
    fDebugGroupActive = false;
#endif
}

void GrMtlOpsRenderPass::onDrawInstanced(int instanceCount, int baseInstance, int vertexCount,
                                         int baseVertex) {
    SkASSERT(fActivePipelineState);
    SkASSERT(nil != fActiveRenderCmdEncoder);
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    if (!fDebugGroupActive) {
        fActiveRenderCmdEncoder->pushDebugGroup(@"bindAndDraw");
        fDebugGroupActive = true;
    }
#endif
    this->setVertexBuffer(fActiveRenderCmdEncoder, fActiveVertexBuffer.get(), 0, 0);

    if (@available(macOS 10.11, iOS 9.0, *)) {
        fActiveRenderCmdEncoder->drawPrimitives(fActivePrimitiveType, baseVertex, vertexCount,
                                                instanceCount, baseInstance);
    } else {
        SkASSERT(false);
    }
    fGpu->stats()->incNumDraws();
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    SkASSERT(fDebugGroupActive);
    fActiveRenderCmdEncoder->popDebugGroup();
    fDebugGroupActive = false;
#endif
}

void GrMtlOpsRenderPass::onDrawIndexedInstanced(
        int indexCount, int baseIndex, int instanceCount, int baseInstance, int baseVertex) {
    SkASSERT(fActivePipelineState);
    SkASSERT(nil != fActiveRenderCmdEncoder);
    SkASSERT(fActiveIndexBuffer);
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    if (!fDebugGroupActive) {
        fActiveRenderCmdEncoder->pushDebugGroup(@"bindAndDraw");
        fDebugGroupActive = true;
    }
#endif
    this->setVertexBuffer(fActiveRenderCmdEncoder, fActiveVertexBuffer.get(), 0, 0);

    auto mtlIndexBuffer = static_cast<const GrMtlBuffer*>(fActiveIndexBuffer.get());
    size_t indexOffset = sizeof(uint16_t) * baseIndex;
    if (@available(macOS 10.11, iOS 9.0, *)) {
        fActiveRenderCmdEncoder->drawIndexedPrimitives(fActivePrimitiveType, indexCount,
                                                       MTLIndexTypeUInt16,
                                                       mtlIndexBuffer->mtlBuffer(), indexOffset,
                                                       instanceCount, baseVertex, baseInstance);
    } else {
        SkASSERT(false);
    }
    fGpu->stats()->incNumDraws();
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    SkASSERT(fDebugGroupActive);
    fActiveRenderCmdEncoder->popDebugGroup();
    fDebugGroupActive = false;
#endif
}

void GrMtlOpsRenderPass::onDrawIndirect(const GrBuffer* drawIndirectBuffer,
                                        size_t bufferOffset,
                                        int drawCount) {
    SkASSERT(fGpu->caps()->nativeDrawIndirectSupport());
    SkASSERT(fActivePipelineState);
    SkASSERT(nil != fActiveRenderCmdEncoder);
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    if (!fDebugGroupActive) {
        fActiveRenderCmdEncoder->pushDebugGroup(@"bindAndDraw");
        fDebugGroupActive = true;
    }
#endif
    this->setVertexBuffer(fActiveRenderCmdEncoder, fActiveVertexBuffer.get(), 0, 0);

    auto mtlIndirectBuffer = static_cast<const GrMtlBuffer*>(drawIndirectBuffer);
    const size_t stride = sizeof(GrDrawIndirectCommand);
    while (drawCount >= 1) {
        if (@available(macOS 10.11, iOS 9.0, *)) {
            fActiveRenderCmdEncoder->drawPrimitives(fActivePrimitiveType,
                                                    mtlIndirectBuffer->mtlBuffer(), bufferOffset);
        } else {
            SkASSERT(false);
        }
        drawCount--;
        bufferOffset += stride;
        fGpu->stats()->incNumDraws();
    }
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    SkASSERT(fDebugGroupActive);
    fActiveRenderCmdEncoder->popDebugGroup();
    fDebugGroupActive = false;
#endif
}

void GrMtlOpsRenderPass::onDrawIndexedIndirect(const GrBuffer* drawIndirectBuffer,
                                               size_t bufferOffset,
                                               int drawCount) {
    SkASSERT(fGpu->caps()->nativeDrawIndirectSupport());
    SkASSERT(fActivePipelineState);
    SkASSERT(nil != fActiveRenderCmdEncoder);
    SkASSERT(fActiveIndexBuffer);
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    if (!fDebugGroupActive) {
        fActiveRenderCmdEncoder->pushDebugGroup(@"bindAndDraw");
        fDebugGroupActive = true;
    }
#endif
    this->setVertexBuffer(fActiveRenderCmdEncoder, fActiveVertexBuffer.get(), 0, 0);

    auto mtlIndexBuffer = static_cast<const GrMtlBuffer*>(fActiveIndexBuffer.get());
    auto mtlIndirectBuffer = static_cast<const GrMtlBuffer*>(drawIndirectBuffer);
    size_t indexOffset = 0;

    const size_t stride = sizeof(GrDrawIndexedIndirectCommand);
    while (drawCount >= 1) {
        if (@available(macOS 10.11, iOS 9.0, *)) {
            fActiveRenderCmdEncoder->drawIndexedPrimitives(fActivePrimitiveType,
                                                           MTLIndexTypeUInt16,
                                                           mtlIndexBuffer->mtlBuffer(),
                                                           indexOffset,
                                                           mtlIndirectBuffer->mtlBuffer(),
                                                           bufferOffset);
        } else {
            SkASSERT(false);
        }
        drawCount--;
        bufferOffset += stride;
        fGpu->stats()->incNumDraws();
    }
#ifdef SK_ENABLE_MTL_DEBUG_INFO
    SkASSERT(fDebugGroupActive);
    fActiveRenderCmdEncoder->popDebugGroup();
    fDebugGroupActive = false;
#endif
}

void GrMtlOpsRenderPass::setVertexBuffer(GrMtlRenderCommandEncoder* encoder,
                                         const GrBuffer* buffer,
                                         size_t vertexOffset,
                                         size_t inputBufferIndex) {
    if (!buffer) {
        return;
    }

    constexpr static int kFirstBufferBindingIdx = GrMtlUniformHandler::kLastUniformBinding + 1;
    int index = inputBufferIndex + kFirstBufferBindingIdx;
    SkASSERT(index < 4);
    auto mtlBuffer = static_cast<const GrMtlBuffer*>(buffer);
    id<MTLBuffer> mtlVertexBuffer = mtlBuffer->mtlBuffer();
    SkASSERT(mtlVertexBuffer);
    size_t offset = vertexOffset;
    encoder->setVertexBuffer(mtlVertexBuffer, offset, index);
}

GR_NORETAIN_END
