• 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#include "libANGLE/renderer/metal/BufferMtl.h"
12#include "libANGLE/renderer/metal/ContextMtl.h"
13#include "libANGLE/renderer/metal/DisplayMtl.h"
14#include "libANGLE/renderer/metal/mtl_format_utils.h"
15
16#include "common/debug.h"
17
18namespace rx
19{
20namespace
21{
22constexpr size_t kDynamicIndexDataSize = 1024 * 8;
23
24angle::Result StreamVertexData(ContextMtl *contextMtl,
25                               mtl::BufferPool *dynamicBuffer,
26                               const uint8_t *sourceData,
27                               size_t bytesToAllocate,
28                               size_t destOffset,
29                               size_t vertexCount,
30                               size_t stride,
31                               VertexCopyFunction vertexLoadFunction,
32                               SimpleWeakBufferHolderMtl *bufferHolder,
33                               size_t *bufferOffsetOut)
34{
35    ANGLE_CHECK(contextMtl, vertexLoadFunction, "Unsupported format conversion", GL_INVALID_ENUM);
36    uint8_t *dst = nullptr;
37    mtl::BufferRef newBuffer;
38    ANGLE_TRY(dynamicBuffer->allocate(contextMtl, bytesToAllocate, &dst, &newBuffer,
39                                      bufferOffsetOut, nullptr));
40    bufferHolder->set(newBuffer);
41    dst += destOffset;
42    vertexLoadFunction(sourceData, stride, vertexCount, dst);
43
44    ANGLE_TRY(dynamicBuffer->commit(contextMtl));
45    return angle::Result::Continue;
46}
47
48template <typename SizeT>
49const mtl::VertexFormat &GetVertexConversionFormat(ContextMtl *contextMtl,
50                                                   angle::FormatID originalFormat,
51                                                   SizeT *strideOut)
52{
53    // Convert to tightly packed format
54    const mtl::VertexFormat &packedFormat = contextMtl->getVertexFormat(originalFormat, true);
55    *strideOut                            = packedFormat.actualAngleFormat().pixelBytes;
56    return packedFormat;
57}
58
59size_t GetIndexConvertedBufferSize(gl::DrawElementsType indexType, size_t indexCount)
60{
61    size_t elementSize = gl::GetDrawElementsTypeSize(indexType);
62    if (indexType == gl::DrawElementsType::UnsignedByte)
63    {
64        // 8-bit indices are not supported by Metal, so they are promoted to
65        // 16-bit indices below
66        elementSize = sizeof(GLushort);
67    }
68
69    const size_t amount = elementSize * indexCount;
70
71    return amount;
72}
73
74angle::Result StreamIndexData(ContextMtl *contextMtl,
75                              mtl::BufferPool *dynamicBuffer,
76                              const uint8_t *sourcePointer,
77                              gl::DrawElementsType indexType,
78                              size_t indexCount,
79                              mtl::BufferRef *bufferOut,
80                              size_t *bufferOffsetOut)
81{
82    dynamicBuffer->releaseInFlightBuffers(contextMtl);
83
84    const size_t amount = GetIndexConvertedBufferSize(indexType, indexCount);
85    GLubyte *dst        = nullptr;
86
87    ANGLE_TRY(
88        dynamicBuffer->allocate(contextMtl, amount, &dst, bufferOut, bufferOffsetOut, nullptr));
89
90    if (indexType == gl::DrawElementsType::UnsignedByte)
91    {
92        // Unsigned bytes don't have direct support in Metal so we have to expand the
93        // memory to a GLushort.
94        const GLubyte *in     = static_cast<const GLubyte *>(sourcePointer);
95        GLushort *expandedDst = reinterpret_cast<GLushort *>(dst);
96
97        // NOTE(hqle): May need to handle primitive restart index in future when ES 3.0
98        // is supported.
99        // Fast path for common case.
100        for (size_t index = 0; index < indexCount; index++)
101        {
102            expandedDst[index] = static_cast<GLushort>(in[index]);
103        }
104    }
105    else
106    {
107        memcpy(dst, sourcePointer, amount);
108    }
109    ANGLE_TRY(dynamicBuffer->commit(contextMtl));
110
111    return angle::Result::Continue;
112}
113
114size_t GetVertexCount(BufferMtl *srcBuffer,
115                      const gl::VertexBinding &binding,
116                      uint32_t srcFormatSize)
117{
118    // Bytes usable for vertex data.
119    GLint64 bytes = srcBuffer->size() - binding.getOffset();
120    if (bytes < srcFormatSize)
121        return 0;
122
123    // Count the last vertex.  It may occupy less than a full stride.
124    size_t numVertices = 1;
125    bytes -= srcFormatSize;
126
127    // Count how many strides fit remaining space.
128    if (bytes > 0)
129        numVertices += static_cast<size_t>(bytes) / binding.getStride();
130
131    return numVertices;
132}
133
134inline size_t GetIndexCount(BufferMtl *srcBuffer, size_t offset, gl::DrawElementsType indexType)
135{
136    size_t elementSize = gl::GetDrawElementsTypeSize(indexType);
137    return (srcBuffer->size() - offset) / elementSize;
138}
139
140inline void SetDefaultVertexBufferLayout(mtl::VertexBufferLayoutDesc *layout)
141{
142    layout->stepFunction = MTLVertexStepFunctionConstant;
143    layout->stepRate     = 0;
144    layout->stride       = 0;
145}
146
147}  // namespace
148
149// VertexArrayMtl implementation
150VertexArrayMtl::VertexArrayMtl(const gl::VertexArrayState &state, ContextMtl *context)
151    : VertexArrayImpl(state),
152      // Due to Metal's strict requirement for offset and stride, we need to always allocate new
153      // buffer for every conversion.
154      mDynamicVertexData(true)
155{
156    reset(context);
157
158    mDynamicVertexData.initialize(context, 0, mtl::kVertexAttribBufferStrideAlignment,
159                                  mtl::kMaxVertexAttribs);
160
161    mDynamicIndexData.initialize(context, kDynamicIndexDataSize, mtl::kIndexBufferOffsetAlignment);
162}
163VertexArrayMtl::~VertexArrayMtl() {}
164
165void VertexArrayMtl::destroy(const gl::Context *context)
166{
167    ContextMtl *contextMtl = mtl::GetImpl(context);
168
169    reset(contextMtl);
170
171    mDynamicVertexData.destroy(contextMtl);
172    mDynamicIndexData.destroy(contextMtl);
173}
174
175void VertexArrayMtl::reset(ContextMtl *context)
176{
177    for (BufferHolderMtl *&buffer : mCurrentArrayBuffers)
178    {
179        buffer = nullptr;
180    }
181    for (size_t &offset : mCurrentArrayBufferOffsets)
182    {
183        offset = 0;
184    }
185    for (GLuint &stride : mCurrentArrayBufferStrides)
186    {
187        stride = 0;
188    }
189    for (const mtl::VertexFormat *&format : mCurrentArrayBufferFormats)
190    {
191        format = &context->getVertexFormat(angle::FormatID::R32G32B32A32_FLOAT, false);
192    }
193
194    mVertexArrayDirty = true;
195}
196
197angle::Result VertexArrayMtl::syncState(const gl::Context *context,
198                                        const gl::VertexArray::DirtyBits &dirtyBits,
199                                        gl::VertexArray::DirtyAttribBitsArray *attribBits,
200                                        gl::VertexArray::DirtyBindingBitsArray *bindingBits)
201{
202    const std::vector<gl::VertexAttribute> &attribs = mState.getVertexAttributes();
203    const std::vector<gl::VertexBinding> &bindings  = mState.getVertexBindings();
204
205    for (size_t dirtyBit : dirtyBits)
206    {
207        switch (dirtyBit)
208        {
209            case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER:
210            case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA:
211            {
212                break;
213            }
214
215#define ANGLE_VERTEX_DIRTY_ATTRIB_FUNC(INDEX)                                                     \
216    case gl::VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX:                                             \
217        ANGLE_TRY(syncDirtyAttrib(context, attribs[INDEX], bindings[attribs[INDEX].bindingIndex], \
218                                  INDEX));                                                        \
219        mVertexArrayDirty = true;                                                                 \
220        (*attribBits)[INDEX].reset();                                                             \
221        break;
222
223                ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_ATTRIB_FUNC)
224
225#define ANGLE_VERTEX_DIRTY_BINDING_FUNC(INDEX)                                                    \
226    case gl::VertexArray::DIRTY_BIT_BINDING_0 + INDEX:                                            \
227        ANGLE_TRY(syncDirtyAttrib(context, attribs[INDEX], bindings[attribs[INDEX].bindingIndex], \
228                                  INDEX));                                                        \
229        mVertexArrayDirty = true;                                                                 \
230        (*bindingBits)[INDEX].reset();                                                            \
231        break;
232
233                ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BINDING_FUNC)
234
235#define ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC(INDEX)                                                \
236    case gl::VertexArray::DIRTY_BIT_BUFFER_DATA_0 + INDEX:                                        \
237        ANGLE_TRY(syncDirtyAttrib(context, attribs[INDEX], bindings[attribs[INDEX].bindingIndex], \
238                                  INDEX));                                                        \
239        mVertexArrayDirty = true;                                                                 \
240        break;
241
242                ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC)
243
244            default:
245                UNREACHABLE();
246                break;
247        }
248    }
249
250    return angle::Result::Continue;
251}
252
253// vertexDescChanged is both input and output, the input value if is true, will force new
254// mtl::VertexDesc to be returned via vertexDescOut. Otherwise, it is only returned when the
255// vertex array is dirty
256angle::Result VertexArrayMtl::setupDraw(const gl::Context *glContext,
257                                        mtl::RenderCommandEncoder *cmdEncoder,
258                                        bool *vertexDescChanged,
259                                        mtl::VertexDesc *vertexDescOut)
260{
261    bool dirty = mVertexArrayDirty || *vertexDescChanged;
262
263    if (dirty)
264    {
265        mVertexArrayDirty = false;
266
267        const std::vector<gl::VertexAttribute> &attribs = mState.getVertexAttributes();
268        const std::vector<gl::VertexBinding> &bindings  = mState.getVertexBindings();
269
270        mtl::VertexDesc &desc = *vertexDescOut;
271
272        desc.numAttribs       = mtl::kMaxVertexAttribs;
273        desc.numBufferLayouts = mtl::kMaxVertexAttribs;
274
275        // Initialize the buffer layouts with constant step rate
276        for (uint32_t b = 0; b < mtl::kMaxVertexAttribs; ++b)
277        {
278            SetDefaultVertexBufferLayout(&desc.layouts[b]);
279        }
280
281        for (uint32_t v = 0; v < mtl::kMaxVertexAttribs; ++v)
282        {
283            const auto &attrib               = attribs[v];
284            const gl::VertexBinding &binding = bindings[attrib.bindingIndex];
285
286            const angle::Format &angleFormat = mCurrentArrayBufferFormats[v]->actualAngleFormat();
287            desc.attributes[v].format        = mCurrentArrayBufferFormats[v]->metalFormat;
288
289            bool attribEnabled = attrib.enabled;
290            if (attribEnabled && !mCurrentArrayBuffers[v])
291            {
292                // Disable it to avoid crash.
293                attribEnabled = false;
294            }
295
296            if (attribEnabled)
297            {
298                uint32_t bufferIdx    = mtl::kVboBindingIndexStart + v;
299                uint32_t bufferOffset = static_cast<uint32_t>(mCurrentArrayBufferOffsets[v]);
300
301                desc.attributes[v].bufferIndex = bufferIdx;
302                desc.attributes[v].offset      = 0;
303                ASSERT((bufferOffset % angleFormat.pixelBytes) == 0);
304
305                ASSERT(bufferIdx < mtl::kMaxVertexAttribs);
306                if (binding.getDivisor() == 0)
307                {
308                    desc.layouts[bufferIdx].stepFunction = MTLVertexStepFunctionPerVertex;
309                    desc.layouts[bufferIdx].stepRate     = 1;
310                }
311                else
312                {
313                    desc.layouts[bufferIdx].stepFunction = MTLVertexStepFunctionPerInstance;
314                    desc.layouts[bufferIdx].stepRate     = binding.getDivisor();
315                }
316                desc.layouts[bufferIdx].stride = mCurrentArrayBufferStrides[v];
317
318                cmdEncoder->setVertexBuffer(mCurrentArrayBuffers[v]->getCurrentBuffer(),
319                                            bufferOffset, bufferIdx);
320            }
321            else
322            {
323                desc.attributes[v].bufferIndex = mtl::kDefaultAttribsBindingIndex;
324                desc.attributes[v].offset      = v * mtl::kDefaultAttributeSize;
325            }
326        }
327    }
328
329    *vertexDescChanged = dirty;
330
331    return angle::Result::Continue;
332}
333
334angle::Result VertexArrayMtl::updateClientAttribs(const gl::Context *context,
335                                                  GLint firstVertex,
336                                                  GLsizei vertexOrIndexCount,
337                                                  GLsizei instanceCount,
338                                                  gl::DrawElementsType indexTypeOrInvalid,
339                                                  const void *indices)
340{
341    ContextMtl *contextMtl                  = mtl::GetImpl(context);
342    const gl::AttributesMask &clientAttribs = context->getStateCache().getActiveClientAttribsMask();
343
344    ASSERT(clientAttribs.any());
345
346    GLint startVertex;
347    size_t vertexCount;
348    ANGLE_TRY(GetVertexRangeInfo(context, firstVertex, vertexOrIndexCount, indexTypeOrInvalid,
349                                 indices, 0, &startVertex, &vertexCount));
350
351    mDynamicVertexData.releaseInFlightBuffers(contextMtl);
352
353    const std::vector<gl::VertexAttribute> &attribs = mState.getVertexAttributes();
354    const std::vector<gl::VertexBinding> &bindings  = mState.getVertexBindings();
355
356    for (size_t attribIndex : clientAttribs)
357    {
358        const gl::VertexAttribute &attrib = attribs[attribIndex];
359        const gl::VertexBinding &binding  = bindings[attrib.bindingIndex];
360        ASSERT(attrib.enabled && binding.getBuffer().get() == nullptr);
361
362        GLuint stride;
363        const mtl::VertexFormat &vertexFormat =
364            GetVertexConversionFormat(contextMtl, attrib.format->id, &stride);
365
366        const uint8_t *src = static_cast<const uint8_t *>(attrib.pointer);
367        ASSERT(src);
368
369        GLint startElement;
370        size_t elementCount;
371        if (binding.getDivisor() == 0)
372        {
373            // Per vertex attribute
374            startElement = startVertex;
375            elementCount = vertexCount;
376        }
377        else
378        {
379            // Per instance attribute
380            startElement = 0;
381            elementCount = UnsignedCeilDivide(instanceCount, binding.getDivisor());
382        }
383        // Allocate space for startElement + elementCount so indexing will work.  If we don't
384        // start at zero all the indices will be off.
385        // Only elementCount vertices will be used by the upcoming draw so that is all we copy.
386        size_t bytesToAllocate = (startElement + elementCount) * stride;
387        src += startElement * binding.getStride();
388        size_t destOffset = startElement * stride;
389
390        ANGLE_TRY(StreamVertexData(
391            contextMtl, &mDynamicVertexData, src, bytesToAllocate, destOffset, elementCount,
392            binding.getStride(), vertexFormat.vertexLoadFunction,
393            &mConvertedArrayBufferHolders[attribIndex], &mCurrentArrayBufferOffsets[attribIndex]));
394
395        mCurrentArrayBuffers[attribIndex]       = &mConvertedArrayBufferHolders[attribIndex];
396        mCurrentArrayBufferFormats[attribIndex] = &vertexFormat;
397        mCurrentArrayBufferStrides[attribIndex] = stride;
398    }
399
400    mVertexArrayDirty = true;
401
402    return angle::Result::Continue;
403}
404
405angle::Result VertexArrayMtl::syncDirtyAttrib(const gl::Context *glContext,
406                                              const gl::VertexAttribute &attrib,
407                                              const gl::VertexBinding &binding,
408                                              size_t attribIndex)
409{
410    ContextMtl *contextMtl = mtl::GetImpl(glContext);
411    ASSERT(mtl::kMaxVertexAttribs > attribIndex);
412
413    if (attrib.enabled)
414    {
415        gl::Buffer *bufferGL            = binding.getBuffer().get();
416        const mtl::VertexFormat &format = contextMtl->getVertexFormat(attrib.format->id, false);
417
418        if (bufferGL)
419        {
420            BufferMtl *bufferMtl = mtl::GetImpl(bufferGL);
421            bool needConversion =
422                format.actualFormatId != format.intendedFormatId ||
423                (binding.getOffset() % format.actualAngleFormat().pixelBytes) != 0 ||
424                (binding.getStride() % mtl::kVertexAttribBufferStrideAlignment) != 0;
425
426            if (needConversion)
427            {
428                ANGLE_TRY(convertVertexBuffer(glContext, bufferMtl, binding, attribIndex, format));
429            }
430            else
431            {
432                mCurrentArrayBuffers[attribIndex]       = bufferMtl;
433                mCurrentArrayBufferOffsets[attribIndex] = binding.getOffset();
434                mCurrentArrayBufferStrides[attribIndex] = binding.getStride();
435
436                mCurrentArrayBufferFormats[attribIndex] = &format;
437            }
438        }
439        else
440        {
441            // ContextMtl must feed the client data using updateClientAttribs()
442        }
443    }
444    else
445    {
446        // Tell ContextMtl to update default attribute value
447        contextMtl->invalidateDefaultAttribute(attribIndex);
448
449        mCurrentArrayBuffers[attribIndex]       = nullptr;
450        mCurrentArrayBufferOffsets[attribIndex] = 0;
451        mCurrentArrayBufferStrides[attribIndex] = 0;
452        // NOTE(hqle): We only support ES 2.0 atm. So default attribute type should always
453        // be float.
454        mCurrentArrayBufferFormats[attribIndex] =
455            &contextMtl->getVertexFormat(angle::FormatID::R32G32B32A32_FLOAT, false);
456    }
457
458    return angle::Result::Continue;
459}
460
461angle::Result VertexArrayMtl::getIndexBuffer(const gl::Context *context,
462                                             gl::DrawElementsType type,
463                                             size_t count,
464                                             const void *indices,
465                                             mtl::BufferRef *idxBufferOut,
466                                             size_t *idxBufferOffsetOut,
467                                             gl::DrawElementsType *indexTypeOut)
468{
469    const gl::Buffer *glElementArrayBuffer = getState().getElementArrayBuffer();
470
471    size_t convertedOffset = reinterpret_cast<size_t>(indices);
472    if (!glElementArrayBuffer)
473    {
474        ANGLE_TRY(streamIndexBufferFromClient(context, type, count, indices, idxBufferOut,
475                                              idxBufferOffsetOut));
476    }
477    else
478    {
479        bool needConversion = type == gl::DrawElementsType::UnsignedByte ||
480                              (convertedOffset % mtl::kIndexBufferOffsetAlignment) != 0;
481        if (needConversion)
482        {
483            ANGLE_TRY(convertIndexBuffer(context, type, convertedOffset, idxBufferOut,
484                                         idxBufferOffsetOut));
485        }
486        else
487        {
488            // No conversion needed:
489            BufferMtl *bufferMtl = mtl::GetImpl(glElementArrayBuffer);
490            *idxBufferOut        = bufferMtl->getCurrentBuffer();
491            *idxBufferOffsetOut  = convertedOffset;
492        }
493    }
494
495    *indexTypeOut = type;
496    if (type == gl::DrawElementsType::UnsignedByte)
497    {
498        // This buffer is already converted to ushort indices above
499        *indexTypeOut = gl::DrawElementsType::UnsignedShort;
500    }
501
502    return angle::Result::Continue;
503}
504
505angle::Result VertexArrayMtl::convertIndexBuffer(const gl::Context *glContext,
506                                                 gl::DrawElementsType indexType,
507                                                 size_t offset,
508                                                 mtl::BufferRef *idxBufferOut,
509                                                 size_t *idxBufferOffsetOut)
510{
511    ASSERT((offset % mtl::kIndexBufferOffsetAlignment) != 0 ||
512           indexType == gl::DrawElementsType::UnsignedByte);
513
514    BufferMtl *idxBuffer = mtl::GetImpl(getState().getElementArrayBuffer());
515
516    IndexConversionBufferMtl *conversion =
517        idxBuffer->getIndexConversionBuffer(glContext, indexType, offset);
518
519    // Has the content of the buffer has changed since last conversion?
520    if (!conversion->dirty)
521    {
522        // reuse the converted buffer
523        *idxBufferOut       = conversion->convertedBuffer;
524        *idxBufferOffsetOut = conversion->convertedOffset;
525        return angle::Result::Continue;
526    }
527
528    size_t indexCount = GetIndexCount(idxBuffer, offset, indexType);
529
530    ANGLE_TRY(
531        convertIndexBufferGPU(glContext, indexType, idxBuffer, offset, indexCount, conversion));
532
533    *idxBufferOut       = conversion->convertedBuffer;
534    *idxBufferOffsetOut = conversion->convertedOffset;
535
536    return angle::Result::Continue;
537}
538
539angle::Result VertexArrayMtl::convertIndexBufferGPU(const gl::Context *glContext,
540                                                    gl::DrawElementsType indexType,
541                                                    BufferMtl *idxBuffer,
542                                                    size_t offset,
543                                                    size_t indexCount,
544                                                    IndexConversionBufferMtl *conversion)
545{
546    ContextMtl *contextMtl = mtl::GetImpl(glContext);
547    DisplayMtl *display    = contextMtl->getDisplay();
548
549    const size_t amount = GetIndexConvertedBufferSize(indexType, indexCount);
550
551    // Allocate new buffer, save it in conversion struct so that we can reuse it when the content
552    // of the original buffer is not dirty.
553    conversion->data.releaseInFlightBuffers(contextMtl);
554    ANGLE_TRY(conversion->data.allocate(contextMtl, amount, nullptr, &conversion->convertedBuffer,
555                                        &conversion->convertedOffset));
556
557    // Do the conversion on GPU.
558    ANGLE_TRY(display->getUtils().convertIndexBufferGPU(
559        mtl::GetImpl(glContext),
560        {indexType, static_cast<uint32_t>(indexCount), idxBuffer->getCurrentBuffer(),
561         static_cast<uint32_t>(offset), conversion->convertedBuffer,
562         static_cast<uint32_t>(conversion->convertedOffset)}));
563
564    ANGLE_TRY(conversion->data.commit(contextMtl));
565
566    ASSERT(conversion->dirty);
567    conversion->dirty = false;
568
569    return angle::Result::Continue;
570}
571
572angle::Result VertexArrayMtl::streamIndexBufferFromClient(const gl::Context *context,
573                                                          gl::DrawElementsType indexType,
574                                                          size_t indexCount,
575                                                          const void *sourcePointer,
576                                                          mtl::BufferRef *idxBufferOut,
577                                                          size_t *idxBufferOffsetOut)
578{
579    ASSERT(getState().getElementArrayBuffer() == nullptr);
580    ContextMtl *contextMtl = mtl::GetImpl(context);
581
582    auto srcData = static_cast<const uint8_t *>(sourcePointer);
583    ANGLE_TRY(StreamIndexData(contextMtl, &mDynamicIndexData, srcData, indexType, indexCount,
584                              idxBufferOut, idxBufferOffsetOut));
585
586    return angle::Result::Continue;
587}
588
589angle::Result VertexArrayMtl::convertVertexBuffer(const gl::Context *glContext,
590                                                  BufferMtl *srcBuffer,
591                                                  const gl::VertexBinding &binding,
592                                                  size_t attribIndex,
593                                                  const mtl::VertexFormat &srcVertexFormat)
594{
595    const angle::Format &intendedAngleFormat = srcVertexFormat.intendedAngleFormat();
596
597    ConversionBufferMtl *conversion = srcBuffer->getVertexConversionBuffer(
598        glContext, intendedAngleFormat.id, binding.getStride(), binding.getOffset());
599
600    // Has the content of the buffer has changed since last conversion?
601    if (!conversion->dirty)
602    {
603        ContextMtl *contextMtl = mtl::GetImpl(glContext);
604
605        // Buffer's data hasn't been changed. Re-use last converted results
606        GLuint stride;
607        const mtl::VertexFormat &vertexFormat =
608            GetVertexConversionFormat(contextMtl, intendedAngleFormat.id, &stride);
609
610        mConvertedArrayBufferHolders[attribIndex].set(conversion->convertedBuffer);
611        mCurrentArrayBufferOffsets[attribIndex] = conversion->convertedOffset;
612
613        mCurrentArrayBuffers[attribIndex]       = &mConvertedArrayBufferHolders[attribIndex];
614        mCurrentArrayBufferFormats[attribIndex] = &vertexFormat;
615        mCurrentArrayBufferStrides[attribIndex] = stride;
616        return angle::Result::Continue;
617    }
618
619    // NOTE(hqle): Do the conversion on GPU.
620    return convertVertexBufferCPU(glContext, srcBuffer, binding, attribIndex, srcVertexFormat,
621                                  conversion);
622}
623
624angle::Result VertexArrayMtl::convertVertexBufferCPU(const gl::Context *glContext,
625                                                     BufferMtl *srcBuffer,
626                                                     const gl::VertexBinding &binding,
627                                                     size_t attribIndex,
628                                                     const mtl::VertexFormat &srcVertexFormat,
629                                                     ConversionBufferMtl *conversion)
630{
631    ContextMtl *contextMtl = mtl::GetImpl(glContext);
632
633    // Convert to tightly packed format
634    GLuint stride;
635    const mtl::VertexFormat &vertexFormat =
636        GetVertexConversionFormat(contextMtl, srcVertexFormat.intendedFormatId, &stride);
637    unsigned srcFormatSize = vertexFormat.intendedAngleFormat().pixelBytes;
638
639    conversion->data.releaseInFlightBuffers(contextMtl);
640
641    size_t numVertices = GetVertexCount(srcBuffer, binding, srcFormatSize);
642    if (numVertices == 0)
643    {
644        return angle::Result::Continue;
645    }
646
647    const uint8_t *srcBytes = srcBuffer->getClientShadowCopyData(glContext);
648    ANGLE_CHECK_GL_ALLOC(contextMtl, srcBytes);
649
650    srcBytes += binding.getOffset();
651
652    ANGLE_TRY(StreamVertexData(contextMtl, &conversion->data, srcBytes, numVertices * stride, 0,
653                               numVertices, binding.getStride(), vertexFormat.vertexLoadFunction,
654                               &mConvertedArrayBufferHolders[attribIndex],
655                               &mCurrentArrayBufferOffsets[attribIndex]));
656
657    mCurrentArrayBuffers[attribIndex]       = &mConvertedArrayBufferHolders[attribIndex];
658    mCurrentArrayBufferFormats[attribIndex] = &vertexFormat;
659    mCurrentArrayBufferStrides[attribIndex] = stride;
660
661    // Cache the last converted results to be re-used later if the buffer's content won't ever be
662    // changed.
663    conversion->convertedBuffer = mConvertedArrayBufferHolders[attribIndex].getCurrentBuffer();
664    conversion->convertedOffset = mCurrentArrayBufferOffsets[attribIndex];
665
666#ifndef NDEBUG
667    ANGLE_MTL_OBJC_SCOPE
668    {
669        mConvertedArrayBufferHolders[attribIndex].getCurrentBuffer()->get().label =
670            [NSString stringWithFormat:@"Converted from %p offset=%zu stride=%u", srcBuffer,
671                                       binding.getOffset(), binding.getStride()];
672    }
673#endif
674
675    ASSERT(conversion->dirty);
676    conversion->dirty = false;
677
678    return angle::Result::Continue;
679}
680}
681