xref: /aosp_15_r20/external/angle/src/libANGLE/renderer/metal/VertexArrayMtl.mm (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1//
2// Copyright 2019 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6// VertexArrayMtl.mm:
7//    Implements the class methods for VertexArrayMtl.
8//
9
10#include "libANGLE/renderer/metal/VertexArrayMtl.h"
11
12#include <TargetConditionals.h>
13
14#include "libANGLE/renderer/metal/BufferMtl.h"
15#include "libANGLE/renderer/metal/ContextMtl.h"
16#include "libANGLE/renderer/metal/DisplayMtl.h"
17#include "libANGLE/renderer/metal/mtl_format_utils.h"
18
19#include "common/debug.h"
20#include "common/utilities.h"
21
22namespace rx
23{
24namespace
25{
26constexpr size_t kDynamicIndexDataSize = 1024 * 8;
27
28angle::Result StreamVertexData(ContextMtl *contextMtl,
29                               mtl::BufferPool *dynamicBuffer,
30                               const uint8_t *sourceData,
31                               size_t bytesToAllocate,
32                               size_t destOffset,
33                               size_t vertexCount,
34                               size_t stride,
35                               VertexCopyFunction vertexLoadFunction,
36                               SimpleWeakBufferHolderMtl *bufferHolder,
37                               size_t *bufferOffsetOut)
38{
39    ANGLE_CHECK(contextMtl, vertexLoadFunction, "Unsupported format conversion", GL_INVALID_ENUM);
40    uint8_t *dst = nullptr;
41    mtl::BufferRef newBuffer;
42    ANGLE_TRY(dynamicBuffer->allocate(contextMtl, bytesToAllocate, &dst, &newBuffer,
43                                      bufferOffsetOut, nullptr));
44    bufferHolder->set(newBuffer);
45    dst += destOffset;
46    vertexLoadFunction(sourceData, stride, vertexCount, dst);
47
48    ANGLE_TRY(dynamicBuffer->commit(contextMtl));
49    return angle::Result::Continue;
50}
51
52template <typename SizeT>
53const mtl::VertexFormat &GetVertexConversionFormat(ContextMtl *contextMtl,
54                                                   angle::FormatID originalFormat,
55                                                   SizeT *strideOut)
56{
57    // Convert to tightly packed format
58    const mtl::VertexFormat &packedFormat = contextMtl->getVertexFormat(originalFormat, true);
59    *strideOut                            = packedFormat.actualAngleFormat().pixelBytes;
60    return packedFormat;
61}
62
63size_t GetIndexConvertedBufferSize(gl::DrawElementsType indexType, size_t indexCount)
64{
65    size_t elementSize = gl::GetDrawElementsTypeSize(indexType);
66    if (indexType == gl::DrawElementsType::UnsignedByte)
67    {
68        // 8-bit indices are not supported by Metal, so they are promoted to
69        // 16-bit indices below
70        elementSize = sizeof(GLushort);
71    }
72
73    const size_t amount = elementSize * indexCount;
74
75    return amount;
76}
77
78angle::Result StreamIndexData(ContextMtl *contextMtl,
79                              mtl::BufferPool *dynamicBuffer,
80                              const uint8_t *sourcePointer,
81                              gl::DrawElementsType indexType,
82                              size_t indexCount,
83                              bool primitiveRestartEnabled,
84                              mtl::BufferRef *bufferOut,
85                              size_t *bufferOffsetOut)
86{
87    dynamicBuffer->releaseInFlightBuffers(contextMtl);
88    const size_t amount = GetIndexConvertedBufferSize(indexType, indexCount);
89    GLubyte *dst        = nullptr;
90    ANGLE_TRY(
91        dynamicBuffer->allocate(contextMtl, amount, &dst, bufferOut, bufferOffsetOut, nullptr));
92
93    if (indexType == gl::DrawElementsType::UnsignedByte)
94    {
95        // Unsigned bytes don't have direct support in Metal so we have to expand the
96        // memory to a GLushort.
97        const GLubyte *in     = static_cast<const GLubyte *>(sourcePointer);
98        GLushort *expandedDst = reinterpret_cast<GLushort *>(dst);
99
100        if (primitiveRestartEnabled)
101        {
102            for (size_t index = 0; index < indexCount; index++)
103            {
104                if (in[index] == 0xFF)
105                {
106                    expandedDst[index] = 0xFFFF;
107                }
108                else
109                {
110                    expandedDst[index] = static_cast<GLushort>(in[index]);
111                }
112            }
113        }  // if (primitiveRestartEnabled)
114        else
115        {
116            for (size_t index = 0; index < indexCount; index++)
117            {
118                expandedDst[index] = static_cast<GLushort>(in[index]);
119            }
120        }  // if (primitiveRestartEnabled)
121    }
122    else
123    {
124        memcpy(dst, sourcePointer, amount);
125    }
126    ANGLE_TRY(dynamicBuffer->commit(contextMtl));
127
128    return angle::Result::Continue;
129}
130
131size_t GetVertexCount(BufferMtl *srcBuffer,
132                      const gl::VertexBinding &binding,
133                      uint32_t srcFormatSize)
134{
135    // Bytes usable for vertex data.
136    GLint64 bytes = srcBuffer->size() - binding.getOffset();
137    if (bytes < srcFormatSize)
138        return 0;
139
140    // Count the last vertex.  It may occupy less than a full stride.
141    size_t numVertices = 1;
142    bytes -= srcFormatSize;
143
144    // Count how many strides fit remaining space.
145    if (bytes > 0)
146        numVertices += static_cast<size_t>(bytes) / binding.getStride();
147
148    return numVertices;
149}
150
151size_t GetVertexCountWithConversion(BufferMtl *srcBuffer,
152                                    VertexConversionBufferMtl *conversionBuffer,
153                                    const gl::VertexBinding &binding,
154                                    uint32_t srcFormatSize)
155{
156    // Bytes usable for vertex data.
157    GLint64 bytes = srcBuffer->size() -
158                    MIN(static_cast<GLintptr>(conversionBuffer->offset), binding.getOffset());
159    if (bytes < srcFormatSize)
160        return 0;
161
162    // Count the last vertex.  It may occupy less than a full stride.
163    size_t numVertices = 1;
164    bytes -= srcFormatSize;
165
166    // Count how many strides fit remaining space.
167    if (bytes > 0)
168        numVertices += static_cast<size_t>(bytes) / binding.getStride();
169
170    return numVertices;
171}
172inline size_t GetIndexCount(BufferMtl *srcBuffer, size_t offset, gl::DrawElementsType indexType)
173{
174    size_t elementSize = gl::GetDrawElementsTypeSize(indexType);
175    return (srcBuffer->size() - offset) / elementSize;
176}
177
178inline void SetDefaultVertexBufferLayout(mtl::VertexBufferLayoutDesc *layout)
179{
180    layout->stepFunction = mtl::kVertexStepFunctionInvalid;
181    layout->stepRate     = 0;
182    layout->stride       = 0;
183}
184
185inline MTLVertexFormat GetCurrentAttribFormat(GLenum type)
186{
187    switch (type)
188    {
189        case GL_INT:
190        case GL_INT_VEC2:
191        case GL_INT_VEC3:
192        case GL_INT_VEC4:
193            return MTLVertexFormatInt4;
194        case GL_UNSIGNED_INT:
195        case GL_UNSIGNED_INT_VEC2:
196        case GL_UNSIGNED_INT_VEC3:
197        case GL_UNSIGNED_INT_VEC4:
198            return MTLVertexFormatUInt4;
199        default:
200            return MTLVertexFormatFloat4;
201    }
202}
203
204}  // namespace
205
206// VertexArrayMtl implementation
207VertexArrayMtl::VertexArrayMtl(const gl::VertexArrayState &state, ContextMtl *context)
208    : VertexArrayImpl(state),
209      mDefaultFloatVertexFormat(
210          context->getVertexFormat(angle::FormatID::R32G32B32A32_FLOAT, false))
211{
212    reset(context);
213
214    mDynamicVertexData.initialize(context, 0, mtl::kVertexAttribBufferStrideAlignment,
215                                  /** maxBuffers */ 10 * mtl::kMaxVertexAttribs);
216
217    mDynamicIndexData.initialize(context, kDynamicIndexDataSize, mtl::kIndexBufferOffsetAlignment,
218                                 0);
219}
220VertexArrayMtl::~VertexArrayMtl() {}
221
222void VertexArrayMtl::destroy(const gl::Context *context)
223{
224    ContextMtl *contextMtl = mtl::GetImpl(context);
225
226    reset(contextMtl);
227
228    mDynamicVertexData.destroy(contextMtl);
229    mDynamicIndexData.destroy(contextMtl);
230}
231
232void VertexArrayMtl::reset(ContextMtl *context)
233{
234    for (BufferHolderMtl *&buffer : mCurrentArrayBuffers)
235    {
236        buffer = nullptr;
237    }
238    for (size_t &offset : mCurrentArrayBufferOffsets)
239    {
240        offset = 0;
241    }
242    for (GLuint &stride : mCurrentArrayBufferStrides)
243    {
244        stride = 0;
245    }
246    for (const mtl::VertexFormat *&format : mCurrentArrayBufferFormats)
247    {
248        format = &mDefaultFloatVertexFormat;
249    }
250
251    for (size_t &inlineDataSize : mCurrentArrayInlineDataSizes)
252    {
253        inlineDataSize = 0;
254    }
255
256    for (angle::MemoryBuffer &convertedClientArray : mConvertedClientSmallArrays)
257    {
258        convertedClientArray.clear();
259    }
260
261    for (const uint8_t *&clientPointer : mCurrentArrayInlineDataPointers)
262    {
263        clientPointer = nullptr;
264    }
265
266    if (context->getDisplay()->getFeatures().allowInlineConstVertexData.enabled)
267    {
268        mInlineDataMaxSize = mtl::kInlineConstDataMaxSize;
269    }
270    else
271    {
272        mInlineDataMaxSize = 0;
273    }
274
275    mVertexArrayDirty = true;
276}
277
278angle::Result VertexArrayMtl::syncState(const gl::Context *context,
279                                        const gl::VertexArray::DirtyBits &dirtyBits,
280                                        gl::VertexArray::DirtyAttribBitsArray *attribBits,
281                                        gl::VertexArray::DirtyBindingBitsArray *bindingBits)
282{
283    const std::vector<gl::VertexAttribute> &attribs = mState.getVertexAttributes();
284    const std::vector<gl::VertexBinding> &bindings  = mState.getVertexBindings();
285
286    for (auto iter = dirtyBits.begin(), endIter = dirtyBits.end(); iter != endIter; ++iter)
287    {
288        size_t dirtyBit = *iter;
289        switch (dirtyBit)
290        {
291            case gl::VertexArray::DIRTY_BIT_LOST_OBSERVATION:
292            {
293                // If vertex array was not observing while unbound, we need to check buffer's
294                // internal storage and take action if buffer has changed while not observing.
295                // For now we just simply assume buffer storage has changed and always dirty all
296                // binding points.
297                iter.setLaterBits(
298                    gl::VertexArray::DirtyBits(mState.getBufferBindingMask().to_ulong()
299                                               << gl::VertexArray::DIRTY_BIT_BINDING_0));
300                break;
301            }
302
303            case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER:
304            case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA:
305            {
306                mVertexDataDirty = true;
307                break;
308            }
309
310#define ANGLE_VERTEX_DIRTY_ATTRIB_FUNC(INDEX)                                                     \
311    case gl::VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX:                                             \
312        ANGLE_TRY(syncDirtyAttrib(context, attribs[INDEX], bindings[attribs[INDEX].bindingIndex], \
313                                  INDEX));                                                        \
314        mVertexArrayDirty = true;                                                                 \
315        (*attribBits)[INDEX].reset();                                                             \
316        break;
317
318                ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_ATTRIB_FUNC)
319
320#define ANGLE_VERTEX_DIRTY_BINDING_FUNC(INDEX)                                                    \
321    case gl::VertexArray::DIRTY_BIT_BINDING_0 + INDEX:                                            \
322        ANGLE_TRY(syncDirtyAttrib(context, attribs[INDEX], bindings[attribs[INDEX].bindingIndex], \
323                                  INDEX));                                                        \
324        mVertexArrayDirty = true;                                                                 \
325        (*bindingBits)[INDEX].reset();                                                            \
326        break;
327
328                ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BINDING_FUNC)
329
330#define ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC(INDEX)                                                \
331    case gl::VertexArray::DIRTY_BIT_BUFFER_DATA_0 + INDEX:                                        \
332        ANGLE_TRY(syncDirtyAttrib(context, attribs[INDEX], bindings[attribs[INDEX].bindingIndex], \
333                                  INDEX));                                                        \
334        mVertexDataDirty = true;                                                                  \
335        break;
336
337                ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC)
338
339            default:
340                UNREACHABLE();
341                break;
342        }
343    }
344
345    return angle::Result::Continue;
346}
347
348// vertexDescChanged is both input and output, the input value if is true, will force new
349// mtl::VertexDesc to be returned via vertexDescOut. This typically happens when active shader
350// program is changed.
351// Otherwise, it is only returned when the vertex array is dirty.
352angle::Result VertexArrayMtl::setupDraw(const gl::Context *glContext,
353                                        mtl::RenderCommandEncoder *cmdEncoder,
354                                        bool *vertexDescChanged,
355                                        mtl::VertexDesc *vertexDescOut)
356{
357    // NOTE(hqle): consider only updating dirty attributes
358    bool dirty = mVertexArrayDirty || *vertexDescChanged;
359
360    if (dirty)
361    {
362
363        mVertexArrayDirty = false;
364        mEmulatedInstanceAttribs.clear();
365
366        const gl::ProgramExecutable *executable = glContext->getState().getProgramExecutable();
367        const gl::AttributesMask &programActiveAttribsMask =
368            executable->getActiveAttribLocationsMask();
369
370        const std::vector<gl::VertexAttribute> &attribs = mState.getVertexAttributes();
371        const std::vector<gl::VertexBinding> &bindings  = mState.getVertexBindings();
372
373        mtl::VertexDesc &desc = *vertexDescOut;
374
375        desc.numAttribs       = mtl::kMaxVertexAttribs;
376        desc.numBufferLayouts = mtl::kMaxVertexAttribs;
377
378        // Initialize the buffer layouts with constant step rate
379        for (uint32_t b = 0; b < mtl::kMaxVertexAttribs; ++b)
380        {
381            SetDefaultVertexBufferLayout(&desc.layouts[b]);
382        }
383
384        // Cache vertex shader input types
385        std::array<uint8_t, mtl::kMaxVertexAttribs> currentAttribFormats{};
386        for (auto &input : executable->getProgramInputs())
387        {
388            ASSERT(input.getLocation() != -1);
389            ASSERT(input.getLocation() < static_cast<int>(mtl::kMaxVertexAttribs));
390            currentAttribFormats[input.getLocation()] = GetCurrentAttribFormat(input.getType());
391        }
392        MTLVertexFormat currentAttribFormat = MTLVertexFormatInvalid;
393
394        for (uint32_t v = 0; v < mtl::kMaxVertexAttribs; ++v)
395        {
396            if (!programActiveAttribsMask.test(v))
397            {
398                desc.attributes[v].format      = MTLVertexFormatInvalid;
399                desc.attributes[v].bufferIndex = 0;
400                desc.attributes[v].offset      = 0;
401                continue;
402            }
403
404            const auto &attrib               = attribs[v];
405            const gl::VertexBinding &binding = bindings[attrib.bindingIndex];
406
407            bool attribEnabled = attrib.enabled;
408            if (attribEnabled &&
409                !(mCurrentArrayBuffers[v] && mCurrentArrayBuffers[v]->getCurrentBuffer()) &&
410                !mCurrentArrayInlineDataPointers[v])
411            {
412                // Disable it to avoid crash.
413                attribEnabled = false;
414            }
415
416            if (currentAttribFormats[v] != MTLVertexFormatInvalid)
417            {
418                currentAttribFormat = static_cast<MTLVertexFormat>(currentAttribFormats[v]);
419            }
420            else
421            {
422                // This is a non-first matrix column
423                ASSERT(currentAttribFormat != MTLVertexFormatInvalid);
424            }
425
426            if (!attribEnabled)
427            {
428                // Use default attribute
429                desc.attributes[v].bufferIndex = mtl::kDefaultAttribsBindingIndex;
430                desc.attributes[v].offset      = v * mtl::kDefaultAttributeSize;
431                desc.attributes[v].format      = currentAttribFormat;
432            }
433            else
434            {
435                uint32_t bufferIdx    = mtl::kVboBindingIndexStart + v;
436                uint32_t bufferOffset = static_cast<uint32_t>(mCurrentArrayBufferOffsets[v]);
437
438                desc.attributes[v].format = mCurrentArrayBufferFormats[v]->metalFormat;
439
440                desc.attributes[v].bufferIndex = bufferIdx;
441                desc.attributes[v].offset      = 0;
442                ASSERT((bufferOffset % mtl::kVertexAttribBufferStrideAlignment) == 0);
443
444                ASSERT(bufferIdx < mtl::kMaxVertexAttribs);
445                if (binding.getDivisor() == 0)
446                {
447                    desc.layouts[bufferIdx].stepFunction = MTLVertexStepFunctionPerVertex;
448                    desc.layouts[bufferIdx].stepRate     = 1;
449                }
450                else
451                {
452                    desc.layouts[bufferIdx].stepFunction = MTLVertexStepFunctionPerInstance;
453                    desc.layouts[bufferIdx].stepRate     = binding.getDivisor();
454                }
455
456                // Metal does not allow the sum of the buffer binding
457                // offset and the vertex layout stride to be greater
458                // than the buffer length.
459                // In OpenGL, this is valid only when a draw call accesses just
460                // one vertex, so just replace the stride with the format size.
461                uint32_t stride = mCurrentArrayBufferStrides[v];
462                if (mCurrentArrayBuffers[v])
463                {
464                    const size_t length = mCurrentArrayBuffers[v]->getCurrentBuffer()->size();
465                    const size_t offset = mCurrentArrayBufferOffsets[v];
466                    ASSERT(offset < length);
467                    if (length - offset < stride)
468                    {
469                        stride = mCurrentArrayBufferFormats[v]->actualAngleFormat().pixelBytes;
470                        ASSERT(stride % mtl::kVertexAttribBufferStrideAlignment == 0);
471                    }
472                }
473                desc.layouts[bufferIdx].stride = stride;
474            }
475        }  // for (v)
476    }
477
478    if (dirty || mVertexDataDirty)
479    {
480        mVertexDataDirty                        = false;
481        const gl::ProgramExecutable *executable = glContext->getState().getProgramExecutable();
482        const gl::AttributesMask &programActiveAttribsMask =
483            executable->getActiveAttribLocationsMask();
484
485        for (uint32_t v = 0; v < mtl::kMaxVertexAttribs; ++v)
486        {
487            if (!programActiveAttribsMask.test(v))
488            {
489                continue;
490            }
491            uint32_t bufferIdx    = mtl::kVboBindingIndexStart + v;
492            uint32_t bufferOffset = static_cast<uint32_t>(mCurrentArrayBufferOffsets[v]);
493            if (mCurrentArrayBuffers[v])
494            {
495                cmdEncoder->setVertexBuffer(mCurrentArrayBuffers[v]->getCurrentBuffer(),
496                                            bufferOffset, bufferIdx);
497            }
498            else if (mCurrentArrayInlineDataPointers[v])
499            {
500                // No buffer specified, use the client memory directly as inline constant data
501                ASSERT(mCurrentArrayInlineDataSizes[v] <= mInlineDataMaxSize);
502                cmdEncoder->setVertexBytes(mCurrentArrayInlineDataPointers[v],
503                                           mCurrentArrayInlineDataSizes[v], bufferIdx);
504            }
505        }
506    }
507
508    *vertexDescChanged = dirty;
509
510    return angle::Result::Continue;
511}
512
513angle::Result VertexArrayMtl::updateClientAttribs(const gl::Context *context,
514                                                  GLint firstVertex,
515                                                  GLsizei vertexOrIndexCount,
516                                                  GLsizei instanceCount,
517                                                  gl::DrawElementsType indexTypeOrInvalid,
518                                                  const void *indices)
519{
520    ContextMtl *contextMtl                  = mtl::GetImpl(context);
521    const gl::AttributesMask &clientAttribs = context->getStateCache().getActiveClientAttribsMask();
522
523    ASSERT(clientAttribs.any());
524
525    GLint startVertex;
526    size_t vertexCount;
527    ANGLE_TRY(GetVertexRangeInfo(context, firstVertex, vertexOrIndexCount, indexTypeOrInvalid,
528                                 indices, 0, &startVertex, &vertexCount));
529
530    mDynamicVertexData.releaseInFlightBuffers(contextMtl);
531
532    const std::vector<gl::VertexAttribute> &attribs = mState.getVertexAttributes();
533    const std::vector<gl::VertexBinding> &bindings  = mState.getVertexBindings();
534
535    for (size_t attribIndex : clientAttribs)
536    {
537        const gl::VertexAttribute &attrib = attribs[attribIndex];
538        const gl::VertexBinding &binding  = bindings[attrib.bindingIndex];
539        ASSERT(attrib.enabled && binding.getBuffer().get() == nullptr);
540
541        // Source client memory pointer
542        const uint8_t *src = static_cast<const uint8_t *>(attrib.pointer);
543        ASSERT(src);
544
545        GLint startElement;
546        size_t elementCount;
547        if (binding.getDivisor() == 0)
548        {
549            // Per vertex attribute
550            startElement = startVertex;
551            elementCount = vertexCount;
552        }
553        else
554        {
555            // Per instance attribute
556            startElement = 0;
557            elementCount = UnsignedCeilDivide(instanceCount, binding.getDivisor());
558        }
559        size_t bytesIntendedToUse = (startElement + elementCount) * binding.getStride();
560
561        const mtl::VertexFormat &format = contextMtl->getVertexFormat(attrib.format->id, false);
562        bool needStreaming              = format.actualFormatId != format.intendedFormatId ||
563                             (binding.getStride() % mtl::kVertexAttribBufferStrideAlignment) != 0 ||
564                             (binding.getStride() < format.actualAngleFormat().pixelBytes) ||
565                             bytesIntendedToUse > mInlineDataMaxSize;
566
567        if (!needStreaming)
568        {
569            // Data will be uploaded directly as inline constant data
570            mCurrentArrayBuffers[attribIndex]            = nullptr;
571            mCurrentArrayInlineDataPointers[attribIndex] = src;
572            mCurrentArrayInlineDataSizes[attribIndex]    = bytesIntendedToUse;
573            mCurrentArrayBufferOffsets[attribIndex]      = 0;
574            mCurrentArrayBufferFormats[attribIndex]      = &format;
575            mCurrentArrayBufferStrides[attribIndex]      = binding.getStride();
576        }
577        else
578        {
579            GLuint convertedStride;
580            // Need to stream the client vertex data to a buffer.
581            const mtl::VertexFormat &streamFormat =
582                GetVertexConversionFormat(contextMtl, attrib.format->id, &convertedStride);
583
584            // Allocate space for startElement + elementCount so indexing will work.  If we don't
585            // start at zero all the indices will be off.
586            // Only elementCount vertices will be used by the upcoming draw so that is all we copy.
587            size_t bytesToAllocate = (startElement + elementCount) * convertedStride;
588            src += startElement * binding.getStride();
589            size_t destOffset = startElement * convertedStride;
590
591            mCurrentArrayBufferFormats[attribIndex] = &streamFormat;
592            mCurrentArrayBufferStrides[attribIndex] = convertedStride;
593
594            if (bytesToAllocate <= mInlineDataMaxSize)
595            {
596                // If the data is small enough, use host memory instead of creating GPU buffer. To
597                // avoid synchronizing access to GPU buffer that is still in use.
598                angle::MemoryBuffer &convertedClientArray =
599                    mConvertedClientSmallArrays[attribIndex];
600                if (bytesToAllocate > convertedClientArray.size())
601                {
602                    ANGLE_CHECK_GL_ALLOC(contextMtl, convertedClientArray.resize(bytesToAllocate));
603                }
604
605                ASSERT(streamFormat.vertexLoadFunction);
606                streamFormat.vertexLoadFunction(src, binding.getStride(), elementCount,
607                                                convertedClientArray.data() + destOffset);
608
609                mCurrentArrayBuffers[attribIndex]            = nullptr;
610                mCurrentArrayInlineDataPointers[attribIndex] = convertedClientArray.data();
611                mCurrentArrayInlineDataSizes[attribIndex]    = bytesToAllocate;
612                mCurrentArrayBufferOffsets[attribIndex]      = 0;
613            }
614            else
615            {
616                // Stream the client data to a GPU buffer. Synchronization might happen if buffer is
617                // in use.
618                mDynamicVertexData.updateAlignment(contextMtl,
619                                                   streamFormat.actualAngleFormat().pixelBytes);
620                ANGLE_TRY(StreamVertexData(contextMtl, &mDynamicVertexData, src, bytesToAllocate,
621                                           destOffset, elementCount, binding.getStride(),
622                                           streamFormat.vertexLoadFunction,
623                                           &mConvertedArrayBufferHolders[attribIndex],
624                                           &mCurrentArrayBufferOffsets[attribIndex]));
625                if (contextMtl->getDisplay()->getFeatures().flushAfterStreamVertexData.enabled)
626                {
627                    // WaitUntilScheduled is needed for this workaround. NoWait does not have the
628                    // needed effect.
629                    contextMtl->flushCommandBuffer(mtl::WaitUntilScheduled);
630                }
631
632                mCurrentArrayBuffers[attribIndex] = &mConvertedArrayBufferHolders[attribIndex];
633            }
634        }  // if (needStreaming)
635    }
636
637    mVertexArrayDirty = true;
638
639    return angle::Result::Continue;
640}
641
642angle::Result VertexArrayMtl::syncDirtyAttrib(const gl::Context *glContext,
643                                              const gl::VertexAttribute &attrib,
644                                              const gl::VertexBinding &binding,
645                                              size_t attribIndex)
646{
647    ContextMtl *contextMtl = mtl::GetImpl(glContext);
648    ASSERT(mtl::kMaxVertexAttribs > attribIndex);
649
650    if (attrib.enabled)
651    {
652        gl::Buffer *bufferGL            = binding.getBuffer().get();
653        const mtl::VertexFormat &format = contextMtl->getVertexFormat(attrib.format->id, false);
654
655        if (bufferGL)
656        {
657            BufferMtl *bufferMtl = mtl::GetImpl(bufferGL);
658            // https://bugs.webkit.org/show_bug.cgi?id=236733
659            // even non-converted buffers need to be observed for potential
660            // data rebinds.
661            mContentsObservers->enableForBuffer(bufferGL, static_cast<uint32_t>(attribIndex));
662            bool needConversion =
663                format.actualFormatId != format.intendedFormatId ||
664                (binding.getOffset() % mtl::kVertexAttribBufferStrideAlignment) != 0 ||
665                (binding.getStride() < format.actualAngleFormat().pixelBytes) ||
666                (binding.getStride() % mtl::kVertexAttribBufferStrideAlignment) != 0;
667
668            if (needConversion)
669            {
670                ANGLE_TRY(convertVertexBuffer(glContext, bufferMtl, binding, attribIndex, format));
671            }
672            else
673            {
674                mCurrentArrayBuffers[attribIndex]       = bufferMtl;
675                mCurrentArrayBufferOffsets[attribIndex] = binding.getOffset();
676                mCurrentArrayBufferStrides[attribIndex] = binding.getStride();
677
678                mCurrentArrayBufferFormats[attribIndex] = &format;
679            }
680        }
681        else
682        {
683            // ContextMtl must feed the client data using updateClientAttribs()
684        }
685    }
686    else
687    {
688        // Use default attribute value. Handled in setupDraw().
689        mCurrentArrayBuffers[attribIndex]       = nullptr;
690        mCurrentArrayBufferOffsets[attribIndex] = 0;
691        mCurrentArrayBufferStrides[attribIndex] = 0;
692        mCurrentArrayBufferFormats[attribIndex] =
693            &contextMtl->getVertexFormat(angle::FormatID::NONE, false);
694    }
695
696    return angle::Result::Continue;
697}
698
699angle::Result VertexArrayMtl::getIndexBuffer(const gl::Context *context,
700                                             gl::DrawElementsType type,
701                                             size_t count,
702                                             const void *indices,
703                                             mtl::BufferRef *idxBufferOut,
704                                             size_t *idxBufferOffsetOut,
705                                             gl::DrawElementsType *indexTypeOut)
706{
707    const gl::Buffer *glElementArrayBuffer = getState().getElementArrayBuffer();
708
709    size_t convertedOffset = reinterpret_cast<size_t>(indices);
710    if (!glElementArrayBuffer)
711    {
712        ANGLE_TRY(streamIndexBufferFromClient(context, type, count, indices, idxBufferOut,
713                                              idxBufferOffsetOut));
714    }
715    else
716    {
717        bool needConversion = type == gl::DrawElementsType::UnsignedByte;
718        if (needConversion)
719        {
720            ANGLE_TRY(convertIndexBuffer(context, type, convertedOffset, idxBufferOut,
721                                         idxBufferOffsetOut));
722        }
723        else
724        {
725            // No conversion needed:
726            BufferMtl *bufferMtl = mtl::GetImpl(glElementArrayBuffer);
727            *idxBufferOut        = bufferMtl->getCurrentBuffer();
728            *idxBufferOffsetOut  = convertedOffset;
729        }
730    }
731
732    *indexTypeOut = type;
733    if (type == gl::DrawElementsType::UnsignedByte)
734    {
735        // This buffer is already converted to ushort indices above
736        *indexTypeOut = gl::DrawElementsType::UnsignedShort;
737    }
738
739    return angle::Result::Continue;
740}
741
742std::vector<DrawCommandRange> VertexArrayMtl::getDrawIndices(const gl::Context *glContext,
743                                                             gl::DrawElementsType originalIndexType,
744                                                             gl::DrawElementsType indexType,
745                                                             gl::PrimitiveMode primitiveMode,
746                                                             mtl::BufferRef clientBuffer,
747                                                             uint32_t indexCount,
748                                                             size_t offset)
749{
750    ContextMtl *contextMtl = mtl::GetImpl(glContext);
751    std::vector<DrawCommandRange> drawCommands;
752    // The indexed draw needs to be split to separate draw commands in case primitive restart is
753    // enabled and the drawn primitive supports primitive restart. Otherwise the whole indexed draw
754    // can be sent as one draw command.
755    bool isSimpleType = primitiveMode == gl::PrimitiveMode::Points ||
756                        primitiveMode == gl::PrimitiveMode::Lines ||
757                        primitiveMode == gl::PrimitiveMode::Triangles;
758    if (!isSimpleType || !glContext->getState().isPrimitiveRestartEnabled())
759    {
760        drawCommands.push_back({indexCount, offset});
761        return drawCommands;
762    }
763    const std::vector<IndexRange> *restartIndices;
764    std::vector<IndexRange> clientIndexRange;
765    const gl::Buffer *glElementArrayBuffer = getState().getElementArrayBuffer();
766    if (glElementArrayBuffer)
767    {
768        BufferMtl *idxBuffer = mtl::GetImpl(glElementArrayBuffer);
769        restartIndices       = &idxBuffer->getRestartIndices(contextMtl, originalIndexType);
770    }
771    else
772    {
773        clientIndexRange =
774            BufferMtl::getRestartIndicesFromClientData(contextMtl, indexType, clientBuffer);
775        restartIndices = &clientIndexRange;
776    }
777    // Reminder, offset is in bytes, not elements.
778    // Slice draw commands based off of indices.
779    uint32_t nIndicesPerPrimitive;
780    switch (primitiveMode)
781    {
782        case gl::PrimitiveMode::Points:
783            nIndicesPerPrimitive = 1;
784            break;
785        case gl::PrimitiveMode::Lines:
786            nIndicesPerPrimitive = 2;
787            break;
788        case gl::PrimitiveMode::Triangles:
789            nIndicesPerPrimitive = 3;
790            break;
791        default:
792            UNREACHABLE();
793            return drawCommands;
794    }
795    const GLuint indexTypeBytes = gl::GetDrawElementsTypeSize(indexType);
796    uint32_t indicesLeft        = indexCount;
797    size_t currentIndexOffset   = offset / indexTypeBytes;
798
799    for (auto &range : *restartIndices)
800    {
801        if (range.restartBegin > currentIndexOffset)
802        {
803            int64_t nIndicesInSlice =
804                MIN(((int64_t)range.restartBegin - currentIndexOffset) -
805                        ((int64_t)range.restartBegin - currentIndexOffset) % nIndicesPerPrimitive,
806                    indicesLeft);
807            size_t restartSize = (range.restartEnd - range.restartBegin) + 1;
808            if (nIndicesInSlice >= nIndicesPerPrimitive)
809            {
810                drawCommands.push_back(
811                    {(uint32_t)nIndicesInSlice, currentIndexOffset * indexTypeBytes});
812            }
813            // Account for dropped indices due to incomplete primitives.
814            size_t indicesUsed = ((range.restartBegin + restartSize) - currentIndexOffset);
815            if (indicesLeft <= indicesUsed)
816            {
817                indicesLeft = 0;
818            }
819            else
820            {
821                indicesLeft -= indicesUsed;
822            }
823            currentIndexOffset = (size_t)(range.restartBegin + restartSize);
824        }
825        // If the initial offset into the index buffer is within a restart zone, move to the end of
826        // the restart zone.
827        else if (range.restartEnd >= currentIndexOffset)
828        {
829            size_t restartSize = (range.restartEnd - currentIndexOffset) + 1;
830            if (indicesLeft <= restartSize)
831            {
832                indicesLeft = 0;
833            }
834            else
835            {
836                indicesLeft -= restartSize;
837            }
838            currentIndexOffset = (size_t)(currentIndexOffset + restartSize);
839        }
840    }
841    if (indicesLeft >= nIndicesPerPrimitive)
842        drawCommands.push_back({indicesLeft, currentIndexOffset * indexTypeBytes});
843    return drawCommands;
844}
845
846angle::Result VertexArrayMtl::convertIndexBuffer(const gl::Context *glContext,
847                                                 gl::DrawElementsType indexType,
848                                                 size_t offset,
849                                                 mtl::BufferRef *idxBufferOut,
850                                                 size_t *idxBufferOffsetOut)
851{
852    size_t offsetModulo = offset % mtl::kIndexBufferOffsetAlignment;
853    ASSERT(offsetModulo != 0 || indexType == gl::DrawElementsType::UnsignedByte);
854
855    size_t alignedOffset = offset - offsetModulo;
856    if (indexType == gl::DrawElementsType::UnsignedByte)
857    {
858        // Unsigned byte index will be promoted to unsigned short, thus double its offset.
859        alignedOffset = alignedOffset << 1;
860    }
861
862    ContextMtl *contextMtl   = mtl::GetImpl(glContext);
863    const gl::State &glState = glContext->getState();
864    BufferMtl *idxBuffer     = mtl::GetImpl(getState().getElementArrayBuffer());
865
866    IndexConversionBufferMtl *conversion = idxBuffer->getIndexConversionBuffer(
867        contextMtl, indexType, glState.isPrimitiveRestartEnabled(), offsetModulo);
868
869    // Has the content of the buffer has changed since last conversion?
870    if (!conversion->dirty)
871    {
872        // reuse the converted buffer
873        *idxBufferOut       = conversion->convertedBuffer;
874        *idxBufferOffsetOut = conversion->convertedOffset + alignedOffset;
875        return angle::Result::Continue;
876    }
877
878    size_t indexCount = GetIndexCount(idxBuffer, offsetModulo, indexType);
879    if ((!contextMtl->getDisplay()->getFeatures().hasCheapRenderPass.enabled &&
880         contextMtl->getRenderCommandEncoder()))
881    {
882        // We shouldn't use GPU to convert when we are in a middle of a render pass.
883        ANGLE_TRY(StreamIndexData(contextMtl, &conversion->data,
884                                  idxBuffer->getBufferDataReadOnly(contextMtl) + offsetModulo,
885                                  indexType, indexCount, glState.isPrimitiveRestartEnabled(),
886                                  &conversion->convertedBuffer, &conversion->convertedOffset));
887    }
888    else
889    {
890        ANGLE_TRY(convertIndexBufferGPU(glContext, indexType, idxBuffer, offsetModulo, indexCount,
891                                        conversion));
892    }
893    // Calculate ranges for prim restart simple types.
894    *idxBufferOut       = conversion->convertedBuffer;
895    *idxBufferOffsetOut = conversion->convertedOffset + alignedOffset;
896
897    return angle::Result::Continue;
898}
899
900angle::Result VertexArrayMtl::convertIndexBufferGPU(const gl::Context *glContext,
901                                                    gl::DrawElementsType indexType,
902                                                    BufferMtl *idxBuffer,
903                                                    size_t offset,
904                                                    size_t indexCount,
905                                                    IndexConversionBufferMtl *conversion)
906{
907    ContextMtl *contextMtl = mtl::GetImpl(glContext);
908    DisplayMtl *display    = contextMtl->getDisplay();
909
910    const size_t amount = GetIndexConvertedBufferSize(indexType, indexCount);
911
912    // Allocate new buffer, save it in conversion struct so that we can reuse it when the content
913    // of the original buffer is not dirty.
914    conversion->data.releaseInFlightBuffers(contextMtl);
915    ANGLE_TRY(conversion->data.allocate(contextMtl, amount, nullptr, &conversion->convertedBuffer,
916                                        &conversion->convertedOffset));
917
918    // Do the conversion on GPU.
919    ANGLE_TRY(display->getUtils().convertIndexBufferGPU(
920        contextMtl, {indexType, static_cast<uint32_t>(indexCount), idxBuffer->getCurrentBuffer(),
921                     static_cast<uint32_t>(offset), conversion->convertedBuffer,
922                     static_cast<uint32_t>(conversion->convertedOffset),
923                     glContext->getState().isPrimitiveRestartEnabled()}));
924
925    ANGLE_TRY(conversion->data.commit(contextMtl));
926
927    ASSERT(conversion->dirty);
928    conversion->dirty = false;
929
930    return angle::Result::Continue;
931}
932
933angle::Result VertexArrayMtl::streamIndexBufferFromClient(const gl::Context *context,
934                                                          gl::DrawElementsType indexType,
935                                                          size_t indexCount,
936                                                          const void *sourcePointer,
937                                                          mtl::BufferRef *idxBufferOut,
938                                                          size_t *idxBufferOffsetOut)
939{
940    ASSERT(getState().getElementArrayBuffer() == nullptr);
941    ContextMtl *contextMtl = mtl::GetImpl(context);
942
943    auto srcData = static_cast<const uint8_t *>(sourcePointer);
944    ANGLE_TRY(StreamIndexData(contextMtl, &mDynamicIndexData, srcData, indexType, indexCount,
945                              context->getState().isPrimitiveRestartEnabled(), idxBufferOut,
946                              idxBufferOffsetOut));
947
948    return angle::Result::Continue;
949}
950
951angle::Result VertexArrayMtl::convertVertexBuffer(const gl::Context *glContext,
952                                                  BufferMtl *srcBuffer,
953                                                  const gl::VertexBinding &binding,
954                                                  size_t attribIndex,
955                                                  const mtl::VertexFormat &srcVertexFormat)
956{
957    unsigned srcFormatSize = srcVertexFormat.intendedAngleFormat().pixelBytes;
958
959    size_t numVertices = GetVertexCount(srcBuffer, binding, srcFormatSize);
960    if (numVertices == 0)
961    {
962        // Out of bound buffer access, can return any values.
963        // See KHR_robust_buffer_access_behavior
964        mCurrentArrayBuffers[attribIndex]       = srcBuffer;
965        mCurrentArrayBufferFormats[attribIndex] = &srcVertexFormat;
966        mCurrentArrayBufferOffsets[attribIndex] = 0;
967        mCurrentArrayBufferStrides[attribIndex] = 16;
968        return angle::Result::Continue;
969    }
970
971    ContextMtl *contextMtl = mtl::GetImpl(glContext);
972
973    // Convert to tightly packed format
974    GLuint stride;
975    const mtl::VertexFormat &convertedFormat =
976        GetVertexConversionFormat(contextMtl, srcVertexFormat.intendedFormatId, &stride);
977
978    ConversionBufferMtl *conversion = srcBuffer->getVertexConversionBuffer(
979        contextMtl, srcVertexFormat.intendedFormatId, binding.getStride(), binding.getOffset());
980
981    // Has the content of the buffer has changed since last conversion?
982    if (!conversion->dirty)
983    {
984        VertexConversionBufferMtl *vertexConversionMtl =
985            static_cast<VertexConversionBufferMtl *>(conversion);
986        ASSERT((binding.getOffset() - vertexConversionMtl->offset) % binding.getStride() == 0);
987        mConvertedArrayBufferHolders[attribIndex].set(conversion->convertedBuffer);
988        mCurrentArrayBufferOffsets[attribIndex] =
989            conversion->convertedOffset +
990            stride * ((binding.getOffset() - vertexConversionMtl->offset) / binding.getStride());
991
992        mCurrentArrayBuffers[attribIndex]       = &mConvertedArrayBufferHolders[attribIndex];
993        mCurrentArrayBufferFormats[attribIndex] = &convertedFormat;
994        mCurrentArrayBufferStrides[attribIndex] = stride;
995        return angle::Result::Continue;
996    }
997    numVertices = GetVertexCountWithConversion(
998        srcBuffer, static_cast<VertexConversionBufferMtl *>(conversion), binding, srcFormatSize);
999
1000    const angle::Format &convertedAngleFormat = convertedFormat.actualAngleFormat();
1001    bool canConvertToFloatOnGPU =
1002        convertedAngleFormat.isFloat() && !convertedAngleFormat.isVertexTypeHalfFloat();
1003
1004    bool canExpandComponentsOnGPU = convertedFormat.actualSameGLType;
1005
1006    conversion->data.releaseInFlightBuffers(contextMtl);
1007    conversion->data.updateAlignment(contextMtl, convertedAngleFormat.pixelBytes);
1008
1009    if (canConvertToFloatOnGPU || canExpandComponentsOnGPU)
1010    {
1011        ANGLE_TRY(convertVertexBufferGPU(glContext, srcBuffer, binding, attribIndex,
1012                                         convertedFormat, stride, numVertices,
1013                                         canExpandComponentsOnGPU, conversion));
1014    }
1015    else
1016    {
1017        ANGLE_TRY(convertVertexBufferCPU(contextMtl, srcBuffer, binding, attribIndex,
1018                                         convertedFormat, stride, numVertices, conversion));
1019    }
1020
1021    mConvertedArrayBufferHolders[attribIndex].set(conversion->convertedBuffer);
1022    mCurrentArrayBufferOffsets[attribIndex] =
1023        conversion->convertedOffset +
1024        stride *
1025            ((binding.getOffset() - static_cast<VertexConversionBufferMtl *>(conversion)->offset) /
1026             binding.getStride());
1027    mCurrentArrayBuffers[attribIndex]       = &mConvertedArrayBufferHolders[attribIndex];
1028    mCurrentArrayBufferFormats[attribIndex] = &convertedFormat;
1029    mCurrentArrayBufferStrides[attribIndex] = stride;
1030
1031    ASSERT(conversion->dirty);
1032    conversion->dirty = false;
1033
1034#ifndef NDEBUG
1035    ANGLE_MTL_OBJC_SCOPE
1036    {
1037        mConvertedArrayBufferHolders[attribIndex].getCurrentBuffer()->get().label =
1038            [NSString stringWithFormat:@"Converted from %p offset=%zu stride=%u", srcBuffer,
1039                                       binding.getOffset(), binding.getStride()];
1040    }
1041#endif
1042
1043    return angle::Result::Continue;
1044}
1045
1046angle::Result VertexArrayMtl::convertVertexBufferCPU(ContextMtl *contextMtl,
1047                                                     BufferMtl *srcBuffer,
1048                                                     const gl::VertexBinding &binding,
1049                                                     size_t attribIndex,
1050                                                     const mtl::VertexFormat &convertedFormat,
1051                                                     GLuint targetStride,
1052                                                     size_t numVertices,
1053                                                     ConversionBufferMtl *conversion)
1054{
1055
1056    const uint8_t *srcBytes = srcBuffer->getBufferDataReadOnly(contextMtl);
1057    ANGLE_CHECK_GL_ALLOC(contextMtl, srcBytes);
1058    VertexConversionBufferMtl *vertexConverison =
1059        static_cast<VertexConversionBufferMtl *>(conversion);
1060    srcBytes += MIN(binding.getOffset(), static_cast<GLintptr>(vertexConverison->offset));
1061    SimpleWeakBufferHolderMtl conversionBufferHolder;
1062    ANGLE_TRY(StreamVertexData(contextMtl, &conversion->data, srcBytes, numVertices * targetStride,
1063                               0, numVertices, binding.getStride(),
1064                               convertedFormat.vertexLoadFunction, &conversionBufferHolder,
1065                               &conversion->convertedOffset));
1066    conversion->convertedBuffer = conversionBufferHolder.getCurrentBuffer();
1067    return angle::Result::Continue;
1068}
1069
1070angle::Result VertexArrayMtl::convertVertexBufferGPU(const gl::Context *glContext,
1071                                                     BufferMtl *srcBuffer,
1072                                                     const gl::VertexBinding &binding,
1073                                                     size_t attribIndex,
1074                                                     const mtl::VertexFormat &convertedFormat,
1075                                                     GLuint targetStride,
1076                                                     size_t numVertices,
1077                                                     bool isExpandingComponents,
1078                                                     ConversionBufferMtl *conversion)
1079{
1080    ContextMtl *contextMtl = mtl::GetImpl(glContext);
1081
1082    mtl::BufferRef newBuffer;
1083    size_t newBufferOffset;
1084    ANGLE_TRY(conversion->data.allocate(contextMtl, numVertices * targetStride, nullptr, &newBuffer,
1085                                        &newBufferOffset));
1086
1087    ANGLE_CHECK_GL_MATH(contextMtl, binding.getOffset() <= std::numeric_limits<uint32_t>::max());
1088    ANGLE_CHECK_GL_MATH(contextMtl, newBufferOffset <= std::numeric_limits<uint32_t>::max());
1089    ANGLE_CHECK_GL_MATH(contextMtl, numVertices <= std::numeric_limits<uint32_t>::max());
1090
1091    mtl::VertexFormatConvertParams params;
1092    VertexConversionBufferMtl *vertexConversion =
1093        static_cast<VertexConversionBufferMtl *>(conversion);
1094    params.srcBuffer            = srcBuffer->getCurrentBuffer();
1095    params.srcBufferStartOffset = static_cast<uint32_t>(
1096        MIN(static_cast<GLintptr>(vertexConversion->offset), binding.getOffset()));
1097    params.srcStride           = binding.getStride();
1098    params.srcDefaultAlphaData = convertedFormat.defaultAlpha;
1099
1100    params.dstBuffer            = newBuffer;
1101    params.dstBufferStartOffset = static_cast<uint32_t>(newBufferOffset);
1102    params.dstStride            = targetStride;
1103    params.dstComponents        = convertedFormat.actualAngleFormat().channelCount;
1104
1105    params.vertexCount = static_cast<uint32_t>(numVertices);
1106
1107    mtl::RenderUtils &utils = contextMtl->getDisplay()->getUtils();
1108
1109    // Compute based buffer conversion.
1110    if (!isExpandingComponents)
1111    {
1112        ANGLE_TRY(utils.convertVertexFormatToFloatCS(
1113            contextMtl, convertedFormat.intendedAngleFormat(), params));
1114    }
1115    else
1116    {
1117        ANGLE_TRY(utils.expandVertexFormatComponentsCS(
1118            contextMtl, convertedFormat.intendedAngleFormat(), params));
1119    }
1120
1121    ANGLE_TRY(conversion->data.commit(contextMtl));
1122
1123    conversion->convertedBuffer = newBuffer;
1124    conversion->convertedOffset = newBufferOffset;
1125
1126    return angle::Result::Continue;
1127}
1128}  // namespace rx
1129