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