xref: /aosp_15_r20/external/angle/src/libANGLE/renderer/metal/BufferMtl.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// BufferMtl.mm:
7//    Implements the class methods for BufferMtl.
8//
9
10#include "libANGLE/renderer/metal/BufferMtl.h"
11
12#include "common/debug.h"
13#include "common/utilities.h"
14#include "libANGLE/renderer/metal/ContextMtl.h"
15#include "libANGLE/renderer/metal/DisplayMtl.h"
16#include "libANGLE/renderer/metal/mtl_buffer_manager.h"
17
18namespace rx
19{
20
21namespace
22{
23
24// Start with a fairly small buffer size. We can increase this dynamically as we convert more data.
25constexpr size_t kConvertedElementArrayBufferInitialSize = 1024 * 8;
26
27template <typename IndexType>
28angle::Result GetFirstLastIndices(const IndexType *indices,
29                                  size_t count,
30                                  std::pair<uint32_t, uint32_t> *outIndices)
31{
32    IndexType first, last;
33    // Use memcpy to avoid unaligned memory access crash:
34    memcpy(&first, &indices[0], sizeof(first));
35    memcpy(&last, &indices[count - 1], sizeof(last));
36
37    outIndices->first  = first;
38    outIndices->second = last;
39
40    return angle::Result::Continue;
41}
42
43bool isOffsetAndSizeMetalBlitCompatible(size_t offset, size_t size)
44{
45    // Metal requires offset and size to be multiples of 4
46    return offset % 4 == 0 && size % 4 == 0;
47}
48
49}  // namespace
50
51// ConversionBufferMtl implementation.
52ConversionBufferMtl::ConversionBufferMtl(ContextMtl *contextMtl,
53                                         size_t initialSize,
54                                         size_t alignment)
55    : dirty(true), convertedBuffer(nullptr), convertedOffset(0)
56{
57    data.initialize(contextMtl, initialSize, alignment, 0);
58}
59
60ConversionBufferMtl::~ConversionBufferMtl() = default;
61
62// IndexConversionBufferMtl implementation.
63IndexConversionBufferMtl::IndexConversionBufferMtl(ContextMtl *context,
64                                                   gl::DrawElementsType elemTypeIn,
65                                                   bool primitiveRestartEnabledIn,
66                                                   size_t offsetIn)
67    : ConversionBufferMtl(context,
68                          kConvertedElementArrayBufferInitialSize,
69                          mtl::kIndexBufferOffsetAlignment),
70      elemType(elemTypeIn),
71      offset(offsetIn),
72      primitiveRestartEnabled(primitiveRestartEnabledIn)
73{}
74
75IndexRange IndexConversionBufferMtl::getRangeForConvertedBuffer(size_t count)
76{
77    return IndexRange{0, count};
78}
79
80// UniformConversionBufferMtl implementation
81UniformConversionBufferMtl::UniformConversionBufferMtl(ContextMtl *context,
82                                                       std::pair<size_t, size_t> offsetIn,
83                                                       size_t uniformBufferBlockSize)
84    : ConversionBufferMtl(context, 0, mtl::kUniformBufferSettingOffsetMinAlignment),
85      uniformBufferBlockSize(uniformBufferBlockSize),
86      offset(offsetIn)
87{}
88
89// VertexConversionBufferMtl implementation.
90VertexConversionBufferMtl::VertexConversionBufferMtl(ContextMtl *context,
91                                                     angle::FormatID formatIDIn,
92                                                     GLuint strideIn,
93                                                     size_t offsetIn)
94    : ConversionBufferMtl(context, 0, mtl::kVertexAttribBufferStrideAlignment),
95      formatID(formatIDIn),
96      stride(strideIn),
97      offset(offsetIn)
98{}
99
100// BufferMtl implementation
101BufferMtl::BufferMtl(const gl::BufferState &state) : BufferImpl(state) {}
102
103BufferMtl::~BufferMtl() {}
104
105void BufferMtl::destroy(const gl::Context *context)
106{
107    ContextMtl *contextMtl = mtl::GetImpl(context);
108    mShadowCopy.clear();
109
110    // if there's a buffer, give it back to the buffer manager
111    if (mBuffer)
112    {
113        contextMtl->getBufferManager().returnBuffer(contextMtl, mBuffer);
114        mBuffer = nullptr;
115    }
116
117    clearConversionBuffers();
118}
119
120angle::Result BufferMtl::setData(const gl::Context *context,
121                                 gl::BufferBinding target,
122                                 const void *data,
123                                 size_t intendedSize,
124                                 gl::BufferUsage usage)
125{
126    return setDataImpl(context, target, data, intendedSize, usage);
127}
128
129angle::Result BufferMtl::setSubData(const gl::Context *context,
130                                    gl::BufferBinding target,
131                                    const void *data,
132                                    size_t size,
133                                    size_t offset)
134{
135    return setSubDataImpl(context, data, size, offset);
136}
137
138angle::Result BufferMtl::copySubData(const gl::Context *context,
139                                     BufferImpl *source,
140                                     GLintptr sourceOffset,
141                                     GLintptr destOffset,
142                                     GLsizeiptr size)
143{
144    if (!source)
145    {
146        return angle::Result::Continue;
147    }
148
149    ContextMtl *contextMtl = mtl::GetImpl(context);
150    auto srcMtl            = GetAs<BufferMtl>(source);
151
152    markConversionBuffersDirty();
153
154    if (mShadowCopy.size() > 0)
155    {
156        if (srcMtl->clientShadowCopyDataNeedSync(contextMtl) ||
157            mBuffer->isBeingUsedByGPU(contextMtl))
158        {
159            // If shadow copy requires a synchronization then use blit command instead.
160            // It might break a pending render pass, but still faster than synchronization with
161            // GPU.
162            mtl::BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
163            blitEncoder->copyBuffer(srcMtl->getCurrentBuffer(), sourceOffset, mBuffer, destOffset,
164                                    size);
165
166            return angle::Result::Continue;
167        }
168        return setSubDataImpl(context, srcMtl->getBufferDataReadOnly(contextMtl) + sourceOffset,
169                              size, destOffset);
170    }
171
172    mtl::BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
173    blitEncoder->copyBuffer(srcMtl->getCurrentBuffer(), sourceOffset, mBuffer, destOffset, size);
174
175    return angle::Result::Continue;
176}
177
178angle::Result BufferMtl::map(const gl::Context *context, GLenum access, void **mapPtr)
179{
180    GLbitfield mapRangeAccess = 0;
181    if ((access & GL_WRITE_ONLY_OES) != 0 || (access & GL_READ_WRITE) != 0)
182    {
183        mapRangeAccess |= GL_MAP_WRITE_BIT;
184    }
185    return mapRange(context, 0, size(), mapRangeAccess, mapPtr);
186}
187
188angle::Result BufferMtl::mapRange(const gl::Context *context,
189                                  size_t offset,
190                                  size_t length,
191                                  GLbitfield access,
192                                  void **mapPtr)
193{
194    if (access & GL_MAP_INVALIDATE_BUFFER_BIT)
195    {
196        ANGLE_TRY(setDataImpl(context, gl::BufferBinding::InvalidEnum, nullptr, size(),
197                              mState.getUsage()));
198    }
199
200    if (mapPtr)
201    {
202        ContextMtl *contextMtl = mtl::GetImpl(context);
203        if (mShadowCopy.size() == 0)
204        {
205            *mapPtr = mBuffer->mapWithOpt(contextMtl, (access & GL_MAP_WRITE_BIT) == 0,
206                                          access & GL_MAP_UNSYNCHRONIZED_BIT) +
207                      offset;
208        }
209        else
210        {
211            *mapPtr = syncAndObtainShadowCopy(contextMtl) + offset;
212        }
213    }
214
215    return angle::Result::Continue;
216}
217
218angle::Result BufferMtl::unmap(const gl::Context *context, GLboolean *result)
219{
220    ContextMtl *contextMtl = mtl::GetImpl(context);
221    size_t offset          = static_cast<size_t>(mState.getMapOffset());
222    size_t len             = static_cast<size_t>(mState.getMapLength());
223
224    markConversionBuffersDirty();
225
226    if (mShadowCopy.size() == 0)
227    {
228        ASSERT(mBuffer);
229        if (mState.getAccessFlags() & GL_MAP_WRITE_BIT)
230        {
231            mBuffer->unmapAndFlushSubset(contextMtl, offset, len);
232        }
233        else
234        {
235            // Buffer is already mapped with readonly flag, so just unmap it, no flushing will
236            // occur.
237            mBuffer->unmap(contextMtl);
238        }
239    }
240    else
241    {
242        if (mState.getAccessFlags() & GL_MAP_UNSYNCHRONIZED_BIT)
243        {
244            // Copy the mapped region without synchronization with GPU
245            uint8_t *ptr =
246                mBuffer->mapWithOpt(contextMtl, /* readonly */ false, /* noSync */ true) + offset;
247            std::copy(mShadowCopy.data() + offset, mShadowCopy.data() + offset + len, ptr);
248            mBuffer->unmapAndFlushSubset(contextMtl, offset, len);
249        }
250        else
251        {
252            // commit shadow copy data to GPU synchronously
253            ANGLE_TRY(commitShadowCopy(contextMtl));
254        }
255    }
256
257    if (result)
258    {
259        *result = true;
260    }
261
262    return angle::Result::Continue;
263}
264
265angle::Result BufferMtl::getIndexRange(const gl::Context *context,
266                                       gl::DrawElementsType type,
267                                       size_t offset,
268                                       size_t count,
269                                       bool primitiveRestartEnabled,
270                                       gl::IndexRange *outRange)
271{
272    const uint8_t *indices = getBufferDataReadOnly(mtl::GetImpl(context)) + offset;
273
274    *outRange = gl::ComputeIndexRange(type, indices, count, primitiveRestartEnabled);
275
276    return angle::Result::Continue;
277}
278
279angle::Result BufferMtl::getFirstLastIndices(ContextMtl *contextMtl,
280                                             gl::DrawElementsType type,
281                                             size_t offset,
282                                             size_t count,
283                                             std::pair<uint32_t, uint32_t> *outIndices)
284{
285    const uint8_t *indices = getBufferDataReadOnly(contextMtl) + offset;
286
287    switch (type)
288    {
289        case gl::DrawElementsType::UnsignedByte:
290            return GetFirstLastIndices(static_cast<const GLubyte *>(indices), count, outIndices);
291        case gl::DrawElementsType::UnsignedShort:
292            return GetFirstLastIndices(reinterpret_cast<const GLushort *>(indices), count,
293                                       outIndices);
294        case gl::DrawElementsType::UnsignedInt:
295            return GetFirstLastIndices(reinterpret_cast<const GLuint *>(indices), count,
296                                       outIndices);
297        default:
298            UNREACHABLE();
299            return angle::Result::Stop;
300    }
301}
302
303void BufferMtl::onDataChanged()
304{
305    markConversionBuffersDirty();
306}
307
308const uint8_t *BufferMtl::getBufferDataReadOnly(ContextMtl *contextMtl)
309{
310    if (mShadowCopy.size() == 0)
311    {
312        // Don't need shadow copy in this case, use the buffer directly
313        return mBuffer->mapReadOnly(contextMtl);
314    }
315    return syncAndObtainShadowCopy(contextMtl);
316}
317
318bool BufferMtl::clientShadowCopyDataNeedSync(ContextMtl *contextMtl)
319{
320    return mBuffer->isCPUReadMemDirty();
321}
322
323void BufferMtl::ensureShadowCopySyncedFromGPU(ContextMtl *contextMtl)
324{
325    if (mBuffer->isCPUReadMemDirty())
326    {
327        const uint8_t *ptr = mBuffer->mapReadOnly(contextMtl);
328        memcpy(mShadowCopy.data(), ptr, size());
329        mBuffer->unmap(contextMtl);
330
331        mBuffer->resetCPUReadMemDirty();
332    }
333}
334uint8_t *BufferMtl::syncAndObtainShadowCopy(ContextMtl *contextMtl)
335{
336    ASSERT(mShadowCopy.size());
337
338    ensureShadowCopySyncedFromGPU(contextMtl);
339
340    return mShadowCopy.data();
341}
342
343ConversionBufferMtl *BufferMtl::getVertexConversionBuffer(ContextMtl *context,
344                                                          angle::FormatID formatID,
345                                                          GLuint stride,
346                                                          size_t offset)
347{
348    for (VertexConversionBufferMtl &buffer : mVertexConversionBuffers)
349    {
350        if (buffer.formatID == formatID && buffer.stride == stride && buffer.offset <= offset &&
351            buffer.offset % buffer.stride == offset % stride)
352        {
353            return &buffer;
354        }
355    }
356
357    mVertexConversionBuffers.emplace_back(context, formatID, stride, offset);
358    ConversionBufferMtl *conv        = &mVertexConversionBuffers.back();
359    const angle::Format &angleFormat = angle::Format::Get(formatID);
360    conv->data.updateAlignment(context, angleFormat.pixelBytes);
361
362    return conv;
363}
364
365IndexConversionBufferMtl *BufferMtl::getIndexConversionBuffer(ContextMtl *context,
366                                                              gl::DrawElementsType elemType,
367                                                              bool primitiveRestartEnabled,
368                                                              size_t offset)
369{
370    for (auto &buffer : mIndexConversionBuffers)
371    {
372        if (buffer.elemType == elemType && buffer.offset == offset &&
373            buffer.primitiveRestartEnabled == primitiveRestartEnabled)
374        {
375            return &buffer;
376        }
377    }
378
379    mIndexConversionBuffers.emplace_back(context, elemType, primitiveRestartEnabled, offset);
380    return &mIndexConversionBuffers.back();
381}
382
383ConversionBufferMtl *BufferMtl::getUniformConversionBuffer(ContextMtl *context,
384                                                           std::pair<size_t, size_t> offset,
385                                                           size_t stdSize)
386{
387    for (UniformConversionBufferMtl &buffer : mUniformConversionBuffers)
388    {
389        if (buffer.offset.first == offset.first && buffer.uniformBufferBlockSize == stdSize)
390        {
391            if (buffer.offset.second <= offset.second &&
392                (offset.second - buffer.offset.second) % buffer.uniformBufferBlockSize == 0)
393                return &buffer;
394        }
395    }
396
397    mUniformConversionBuffers.emplace_back(context, offset, stdSize);
398    return &mUniformConversionBuffers.back();
399}
400
401void BufferMtl::markConversionBuffersDirty()
402{
403    for (VertexConversionBufferMtl &buffer : mVertexConversionBuffers)
404    {
405        buffer.dirty           = true;
406        buffer.convertedBuffer = nullptr;
407        buffer.convertedOffset = 0;
408    }
409
410    for (IndexConversionBufferMtl &buffer : mIndexConversionBuffers)
411    {
412        buffer.dirty           = true;
413        buffer.convertedBuffer = nullptr;
414        buffer.convertedOffset = 0;
415    }
416
417    for (UniformConversionBufferMtl &buffer : mUniformConversionBuffers)
418    {
419        buffer.dirty           = true;
420        buffer.convertedBuffer = nullptr;
421        buffer.convertedOffset = 0;
422    }
423    mRestartRangeCache.reset();
424}
425
426void BufferMtl::clearConversionBuffers()
427{
428    mVertexConversionBuffers.clear();
429    mIndexConversionBuffers.clear();
430    mUniformConversionBuffers.clear();
431    mRestartRangeCache.reset();
432}
433
434template <typename T>
435static std::vector<IndexRange> calculateRestartRanges(ContextMtl *ctx, mtl::BufferRef idxBuffer)
436{
437    std::vector<IndexRange> result;
438    const T *bufferData       = reinterpret_cast<const T *>(idxBuffer->mapReadOnly(ctx));
439    const size_t numIndices   = idxBuffer->size() / sizeof(T);
440    constexpr T restartMarker = std::numeric_limits<T>::max();
441    for (size_t i = 0; i < numIndices; ++i)
442    {
443        // Find the start of the restart range, i.e. first index with value of restart marker.
444        if (bufferData[i] != restartMarker)
445            continue;
446        size_t restartBegin = i;
447        // Find the end of the restart range, i.e. last index with value of restart marker.
448        do
449        {
450            ++i;
451        } while (i < numIndices && bufferData[i] == restartMarker);
452        result.emplace_back(restartBegin, i - 1);
453    }
454    idxBuffer->unmap(ctx);
455    return result;
456}
457
458const std::vector<IndexRange> &BufferMtl::getRestartIndices(ContextMtl *ctx,
459                                                            gl::DrawElementsType indexType)
460{
461    if (!mRestartRangeCache || mRestartRangeCache->indexType != indexType)
462    {
463        mRestartRangeCache.reset();
464        std::vector<IndexRange> ranges;
465        switch (indexType)
466        {
467            case gl::DrawElementsType::UnsignedByte:
468                ranges = calculateRestartRanges<uint8_t>(ctx, getCurrentBuffer());
469                break;
470            case gl::DrawElementsType::UnsignedShort:
471                ranges = calculateRestartRanges<uint16_t>(ctx, getCurrentBuffer());
472                break;
473            case gl::DrawElementsType::UnsignedInt:
474                ranges = calculateRestartRanges<uint32_t>(ctx, getCurrentBuffer());
475                break;
476            default:
477                ASSERT(false);
478        }
479        mRestartRangeCache.emplace(std::move(ranges), indexType);
480    }
481    return mRestartRangeCache->ranges;
482}
483
484const std::vector<IndexRange> BufferMtl::getRestartIndicesFromClientData(
485    ContextMtl *ctx,
486    gl::DrawElementsType indexType,
487    mtl::BufferRef idxBuffer)
488{
489    std::vector<IndexRange> restartIndices;
490    switch (indexType)
491    {
492        case gl::DrawElementsType::UnsignedByte:
493            restartIndices = calculateRestartRanges<uint8_t>(ctx, idxBuffer);
494            break;
495        case gl::DrawElementsType::UnsignedShort:
496            restartIndices = calculateRestartRanges<uint16_t>(ctx, idxBuffer);
497            break;
498        case gl::DrawElementsType::UnsignedInt:
499            restartIndices = calculateRestartRanges<uint32_t>(ctx, idxBuffer);
500            break;
501        default:
502            ASSERT(false);
503    }
504    return restartIndices;
505}
506
507angle::Result BufferMtl::allocateNewMetalBuffer(ContextMtl *contextMtl,
508                                                MTLStorageMode storageMode,
509                                                size_t size,
510                                                bool returnOldBufferImmediately)
511{
512    mtl::BufferManager &bufferManager = contextMtl->getBufferManager();
513    if (returnOldBufferImmediately && mBuffer)
514    {
515        // Return the current buffer to the buffer manager
516        // It will not be re-used until it's no longer in use.
517        bufferManager.returnBuffer(contextMtl, mBuffer);
518        mBuffer = nullptr;
519    }
520    ANGLE_TRY(bufferManager.getBuffer(contextMtl, storageMode, size, mBuffer));
521
522    onStateChange(angle::SubjectMessage::InternalMemoryAllocationChanged);
523
524    return angle::Result::Continue;
525}
526
527angle::Result BufferMtl::setDataImpl(const gl::Context *context,
528                                     gl::BufferBinding target,
529                                     const void *data,
530                                     size_t intendedSize,
531                                     gl::BufferUsage usage)
532{
533    ContextMtl *contextMtl             = mtl::GetImpl(context);
534    const angle::FeaturesMtl &features = contextMtl->getDisplay()->getFeatures();
535
536    // Invalidate conversion buffers
537    if (mState.getSize() != static_cast<GLint64>(intendedSize))
538    {
539        clearConversionBuffers();
540    }
541    else
542    {
543        markConversionBuffersDirty();
544    }
545
546    mUsage              = usage;
547    mGLSize             = intendedSize;
548    size_t adjustedSize = std::max<size_t>(1, intendedSize);
549
550    // Ensures no validation layer issues in std140 with data types like vec3 being 12 bytes vs 16
551    // in MSL.
552    if (target == gl::BufferBinding::Uniform)
553    {
554        // This doesn't work! A buffer can be allocated on ARRAY_BUFFER and used in UNIFORM_BUFFER
555        // TODO(anglebug.com/42266052)
556        adjustedSize = roundUpPow2(adjustedSize, (size_t)16);
557    }
558
559    // Re-create the buffer
560    auto storageMode = mtl::Buffer::getStorageModeForUsage(contextMtl, usage);
561    ANGLE_TRY(allocateNewMetalBuffer(contextMtl, storageMode, adjustedSize,
562                                     /*returnOldBufferImmediately=*/true));
563
564#ifndef NDEBUG
565    ANGLE_MTL_OBJC_SCOPE
566    {
567        mBuffer->get().label = [NSString stringWithFormat:@"BufferMtl=%p", this];
568    }
569#endif
570
571    // We may use shadow copy to maintain consistent data between buffers in pool
572    size_t shadowSize = (!features.preferCpuForBuffersubdata.enabled &&
573                         features.useShadowBuffersWhenAppropriate.enabled &&
574                         adjustedSize <= mtl::kSharedMemBufferMaxBufSizeHint)
575                            ? adjustedSize
576                            : 0;
577    ANGLE_MTL_CHECK(contextMtl, mShadowCopy.resize(shadowSize), GL_OUT_OF_MEMORY);
578
579    if (data)
580    {
581        ANGLE_TRY(setSubDataImpl(context, data, intendedSize, 0));
582    }
583
584    return angle::Result::Continue;
585}
586
587// states:
588//  * The buffer is not use
589//
590//    safe = true
591//
592//  * The buffer has a pending blit
593//
594//    In this case, as long as we are only reading from it
595//    via blit to a new buffer our blits will happen after existing
596//    blits
597//
598//    safe = true
599//
600//  * The buffer has pending writes in a commited render encoder
601//
602//    In this case we're encoding commands that will happen after
603//    that encoder
604//
605//    safe = true
606//
607//  * The buffer has pending writes in the current render encoder
608//
609//    in this case we have to split/end the render encoder
610//    before we can use the buffer.
611//
612//    safe = false
613bool BufferMtl::isSafeToReadFromBufferViaBlit(ContextMtl *contextMtl)
614{
615    uint64_t serial   = mBuffer->getLastWritingRenderEncoderSerial();
616    bool isSameSerial = contextMtl->isCurrentRenderEncoderSerial(serial);
617    return !isSameSerial;
618}
619
620angle::Result BufferMtl::updateExistingBufferViaBlitFromStagingBuffer(ContextMtl *contextMtl,
621                                                                      const uint8_t *srcPtr,
622                                                                      size_t sizeToCopy,
623                                                                      size_t offset)
624{
625    ASSERT(isOffsetAndSizeMetalBlitCompatible(offset, sizeToCopy));
626
627    mtl::BufferManager &bufferManager = contextMtl->getBufferManager();
628    return bufferManager.queueBlitCopyDataToBuffer(contextMtl, srcPtr, sizeToCopy, offset, mBuffer);
629}
630
631// * get a new or unused buffer
632// * copy the new data to it
633// * copy any old data not overwriten by the new data to the new buffer
634// * start using the new buffer
635angle::Result BufferMtl::putDataInNewBufferAndStartUsingNewBuffer(ContextMtl *contextMtl,
636                                                                  const uint8_t *srcPtr,
637                                                                  size_t sizeToCopy,
638                                                                  size_t offset)
639{
640    ASSERT(isOffsetAndSizeMetalBlitCompatible(offset, sizeToCopy));
641
642    mtl::BufferRef oldBuffer = mBuffer;
643    auto storageMode         = mtl::Buffer::getStorageModeForUsage(contextMtl, mUsage);
644
645    ANGLE_TRY(allocateNewMetalBuffer(contextMtl, storageMode, mGLSize,
646                                     /*returnOldBufferImmediately=*/false));
647    mBuffer->get().label = [NSString stringWithFormat:@"BufferMtl=%p(%lu)", this, ++mRevisionCount];
648
649    uint8_t *ptr = mBuffer->mapWithOpt(contextMtl, false, true);
650    std::copy(srcPtr, srcPtr + sizeToCopy, ptr + offset);
651    mBuffer->unmapAndFlushSubset(contextMtl, offset, sizeToCopy);
652
653    if (offset > 0 || offset + sizeToCopy < mGLSize)
654    {
655        mtl::BlitCommandEncoder *blitEncoder =
656            contextMtl->getBlitCommandEncoderWithoutEndingRenderEncoder();
657        if (offset > 0)
658        {
659            // copy old data before updated region
660            blitEncoder->copyBuffer(oldBuffer, 0, mBuffer, 0, offset);
661        }
662        if (offset + sizeToCopy < mGLSize)
663        {
664            // copy old data after updated region
665            const size_t endOffset     = offset + sizeToCopy;
666            const size_t endSizeToCopy = mGLSize - endOffset;
667            blitEncoder->copyBuffer(oldBuffer, endOffset, mBuffer, endOffset, endSizeToCopy);
668        }
669    }
670
671    mtl::BufferManager &bufferManager = contextMtl->getBufferManager();
672    bufferManager.returnBuffer(contextMtl, oldBuffer);
673    return angle::Result::Continue;
674}
675
676angle::Result BufferMtl::copyDataToExistingBufferViaCPU(ContextMtl *contextMtl,
677                                                        const uint8_t *srcPtr,
678                                                        size_t sizeToCopy,
679                                                        size_t offset)
680{
681    uint8_t *ptr = mBuffer->map(contextMtl);
682    std::copy(srcPtr, srcPtr + sizeToCopy, ptr + offset);
683    mBuffer->unmapAndFlushSubset(contextMtl, offset, sizeToCopy);
684    return angle::Result::Continue;
685}
686
687angle::Result BufferMtl::updateShadowCopyThenCopyShadowToNewBuffer(ContextMtl *contextMtl,
688                                                                   const uint8_t *srcPtr,
689                                                                   size_t sizeToCopy,
690                                                                   size_t offset)
691{
692    // 1. Before copying data from client, we need to synchronize modified data from GPU to
693    // shadow copy first.
694    ensureShadowCopySyncedFromGPU(contextMtl);
695
696    // 2. Copy data from client to shadow copy.
697    std::copy(srcPtr, srcPtr + sizeToCopy, mShadowCopy.data() + offset);
698
699    // 3. Copy data from shadow copy to GPU.
700    return commitShadowCopy(contextMtl);
701}
702
703angle::Result BufferMtl::setSubDataImpl(const gl::Context *context,
704                                        const void *data,
705                                        size_t size,
706                                        size_t offset)
707{
708    if (!data)
709    {
710        return angle::Result::Continue;
711    }
712
713    ASSERT(mBuffer);
714
715    ContextMtl *contextMtl             = mtl::GetImpl(context);
716    const angle::FeaturesMtl &features = contextMtl->getDisplay()->getFeatures();
717
718    ANGLE_MTL_TRY(contextMtl, offset <= mGLSize);
719
720    auto srcPtr     = static_cast<const uint8_t *>(data);
721    auto sizeToCopy = std::min<size_t>(size, mGLSize - offset);
722
723    markConversionBuffersDirty();
724
725    if (features.preferCpuForBuffersubdata.enabled)
726    {
727        return copyDataToExistingBufferViaCPU(contextMtl, srcPtr, sizeToCopy, offset);
728    }
729
730    if (mShadowCopy.size() > 0)
731    {
732        return updateShadowCopyThenCopyShadowToNewBuffer(contextMtl, srcPtr, sizeToCopy, offset);
733    }
734    else
735    {
736        bool alwaysUseStagedBufferUpdates = features.alwaysUseStagedBufferUpdates.enabled;
737
738        if (isOffsetAndSizeMetalBlitCompatible(offset, size) &&
739            (alwaysUseStagedBufferUpdates || mBuffer->isBeingUsedByGPU(contextMtl)))
740        {
741            if (alwaysUseStagedBufferUpdates || !isSafeToReadFromBufferViaBlit(contextMtl))
742            {
743                // We can't use the buffer now so copy the data
744                // to a staging buffer and blit it in
745                return updateExistingBufferViaBlitFromStagingBuffer(contextMtl, srcPtr, sizeToCopy,
746                                                                    offset);
747            }
748            else
749            {
750                return putDataInNewBufferAndStartUsingNewBuffer(contextMtl, srcPtr, sizeToCopy,
751                                                                offset);
752            }
753        }
754        else
755        {
756            return copyDataToExistingBufferViaCPU(contextMtl, srcPtr, sizeToCopy, offset);
757        }
758    }
759}
760
761angle::Result BufferMtl::commitShadowCopy(ContextMtl *contextMtl)
762{
763    return commitShadowCopy(contextMtl, mGLSize);
764}
765
766angle::Result BufferMtl::commitShadowCopy(ContextMtl *contextMtl, size_t size)
767{
768    auto storageMode = mtl::Buffer::getStorageModeForUsage(contextMtl, mUsage);
769
770    size_t bufferSize = (mGLSize == 0 ? mShadowCopy.size() : mGLSize);
771    ANGLE_TRY(allocateNewMetalBuffer(contextMtl, storageMode, bufferSize,
772                                     /*returnOldBufferImmediately=*/true));
773
774    if (size)
775    {
776        uint8_t *ptr = mBuffer->mapWithOpt(contextMtl, false, true);
777        std::copy(mShadowCopy.data(), mShadowCopy.data() + size, ptr);
778        mBuffer->unmapAndFlushSubset(contextMtl, 0, size);
779    }
780
781    return angle::Result::Continue;
782}
783
784// SimpleWeakBufferHolderMtl implementation
785SimpleWeakBufferHolderMtl::SimpleWeakBufferHolderMtl()
786{
787    mIsWeak = true;
788}
789
790}  // namespace rx
791