//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// VertexArrayMtl.h:
//    Defines the class interface for VertexArrayMtl, implementing VertexArrayImpl.
//

#ifndef LIBANGLE_RENDERER_METAL_VERTEXARRAYMTL_H_
#define LIBANGLE_RENDERER_METAL_VERTEXARRAYMTL_H_

#include "libANGLE/renderer/VertexArrayImpl.h"
#include "libANGLE/renderer/metal/BufferMtl.h"
#include "libANGLE/renderer/metal/mtl_buffer_pool.h"
#include "libANGLE/renderer/metal/mtl_command_buffer.h"
#include "libANGLE/renderer/metal/mtl_context_device.h"
#include "libANGLE/renderer/metal/mtl_format_utils.h"
#include "libANGLE/renderer/metal/mtl_resources.h"

namespace rx
{
class ContextMtl;

class VertexArrayMtl : public VertexArrayImpl
{
  public:
    VertexArrayMtl(const gl::VertexArrayState &state, ContextMtl *context);
    ~VertexArrayMtl() override;

    void destroy(const gl::Context *context) override;

    angle::Result syncState(const gl::Context *context,
                            const gl::VertexArray::DirtyBits &dirtyBits,
                            gl::VertexArray::DirtyAttribBitsArray *attribBits,
                            gl::VertexArray::DirtyBindingBitsArray *bindingBits) override;

    // Feed client side's vertex/index data
    angle::Result updateClientAttribs(const gl::Context *context,
                                      GLint firstVertex,
                                      GLsizei vertexOrIndexCount,
                                      GLsizei instanceCount,
                                      gl::DrawElementsType indexTypeOrInvalid,
                                      const void *indices);

    // vertexDescChanged is both input and output, the input value if is true, will force new
    // mtl::VertexDesc to be returned via vertexDescOut. This typically happens when active shader
    // program is changed.
    // Otherwise, it is only returned when the vertex array is dirty.
    angle::Result setupDraw(const gl::Context *glContext,
                            mtl::RenderCommandEncoder *cmdEncoder,
                            bool *vertexDescChanged,
                            mtl::VertexDesc *vertexDescOut);

    angle::Result getIndexBuffer(const gl::Context *glContext,
                                 gl::DrawElementsType indexType,
                                 size_t indexCount,
                                 const void *sourcePointer,
                                 mtl::BufferRef *idxBufferOut,
                                 size_t *idxBufferOffsetOut,
                                 gl::DrawElementsType *indexTypeOut);

    std::vector<DrawCommandRange> getDrawIndices(const gl::Context *glContext,
                                                 gl::DrawElementsType originalIndexType,
                                                 gl::DrawElementsType indexType,
                                                 gl::PrimitiveMode primitiveMode,
                                                 mtl::BufferRef idxBuffer,
                                                 uint32_t indexCount,
                                                 size_t offset);

  private:
    void reset(ContextMtl *context);

    void getVertexAttribFormatAndArraySize(const sh::ShaderVariable &var,
                                           MTLVertexFormat *formatOut,
                                           uint32_t *arraySizeOut);

    angle::Result syncDirtyAttrib(const gl::Context *glContext,
                                  const gl::VertexAttribute &attrib,
                                  const gl::VertexBinding &binding,
                                  size_t attribIndex);

    angle::Result convertIndexBuffer(const gl::Context *glContext,
                                     gl::DrawElementsType indexType,
                                     size_t offset,
                                     mtl::BufferRef *idxBufferOut,
                                     size_t *idxBufferOffsetOut);
    angle::Result streamIndexBufferFromClient(const gl::Context *glContext,
                                              gl::DrawElementsType indexType,
                                              size_t indexCount,
                                              const void *sourcePointer,
                                              mtl::BufferRef *idxBufferOut,
                                              size_t *idxBufferOffsetOut);

    angle::Result convertIndexBufferGPU(const gl::Context *glContext,
                                        gl::DrawElementsType indexType,
                                        BufferMtl *idxBuffer,
                                        size_t offset,
                                        size_t indexCount,
                                        IndexConversionBufferMtl *conversion);

    angle::Result convertVertexBuffer(const gl::Context *glContext,
                                      BufferMtl *srcBuffer,
                                      const gl::VertexBinding &binding,
                                      size_t attribIndex,
                                      const mtl::VertexFormat &vertexFormat);

    angle::Result convertVertexBufferCPU(ContextMtl *contextMtl,
                                         BufferMtl *srcBuffer,
                                         const gl::VertexBinding &binding,
                                         size_t attribIndex,
                                         const mtl::VertexFormat &convertedFormat,
                                         GLuint targetStride,
                                         size_t vertexCount,
                                         ConversionBufferMtl *conversion);
    angle::Result convertVertexBufferGPU(const gl::Context *glContext,
                                         BufferMtl *srcBuffer,
                                         const gl::VertexBinding &binding,
                                         size_t attribIndex,
                                         const mtl::VertexFormat &convertedFormat,
                                         GLuint targetStride,
                                         size_t vertexCount,
                                         bool isExpandingComponents,
                                         ConversionBufferMtl *conversion);

    // These can point to real BufferMtl or converted buffer in mConvertedArrayBufferHolders
    gl::AttribArray<BufferHolderMtl *> mCurrentArrayBuffers;
    gl::AttribArray<SimpleWeakBufferHolderMtl> mConvertedArrayBufferHolders;
    gl::AttribArray<size_t> mCurrentArrayBufferOffsets;

    // Size to be uploaded as inline constant data. Used for client vertex attribute's data that
    // is small enough that we can send directly as inline constant data instead of streaming
    // through a buffer.
    gl::AttribArray<size_t> mCurrentArrayInlineDataSizes;
    // Array of host buffers storing converted data for client attributes that are small enough.
    gl::AttribArray<angle::MemoryBuffer> mConvertedClientSmallArrays;
    gl::AttribArray<const uint8_t *> mCurrentArrayInlineDataPointers;
    // Max size of inline constant data that can be used for client vertex attribute.
    size_t mInlineDataMaxSize;

    // Stride per vertex attribute
    gl::AttribArray<GLuint> mCurrentArrayBufferStrides;
    // Format per vertex attribute
    gl::AttribArray<const mtl::VertexFormat *> mCurrentArrayBufferFormats;

    const mtl::VertexFormat &mDefaultFloatVertexFormat;
    const mtl::VertexFormat &mDefaultIntVertexFormat;
    const mtl::VertexFormat &mDefaultUIntVertexFormat;

    mtl::BufferPool mDynamicVertexData;
    mtl::BufferPool mDynamicIndexData;

    std::vector<uint32_t> mEmulatedInstanceAttribs;

    bool mVertexArrayDirty = true;
    bool mVertexDataDirty  = true;
};
}  // namespace rx

#endif /* LIBANGLE_RENDERER_METAL_VERTEXARRAYMTL_H_ */
