• 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// 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
17namespace rx
18{
19
20namespace
21{
22
23// Start with a fairly small buffer size. We can increase this dynamically as we convert more data.
24constexpr size_t kConvertedElementArrayBufferInitialSize = 1024 * 8;
25
26template <typename IndexType>
27angle::Result GetFirstLastIndices(const IndexType *indices,
28                                  size_t count,
29                                  std::pair<uint32_t, uint32_t> *outIndices)
30{
31    IndexType first, last;
32    // Use memcpy to avoid unaligned memory access crash:
33    memcpy(&first, &indices[0], sizeof(first));
34    memcpy(&last, &indices[count - 1], sizeof(last));
35
36    outIndices->first  = first;
37    outIndices->second = last;
38
39    return angle::Result::Continue;
40}
41
42}  // namespace
43
44// ConversionBufferMtl implementation.
45ConversionBufferMtl::ConversionBufferMtl(ContextMtl *contextMtl,
46                                         size_t initialSize,
47                                         size_t alignment)
48    : dirty(true), convertedBuffer(nullptr), convertedOffset(0)
49{
50    data.initialize(contextMtl, initialSize, alignment, 0);
51}
52
53ConversionBufferMtl::~ConversionBufferMtl() = default;
54
55// IndexConversionBufferMtl implementation.
56IndexConversionBufferMtl::IndexConversionBufferMtl(ContextMtl *context,
57                                                   gl::DrawElementsType elemTypeIn,
58                                                   bool primitiveRestartEnabledIn,
59                                                   size_t offsetIn)
60    : ConversionBufferMtl(context,
61                          kConvertedElementArrayBufferInitialSize,
62                          mtl::kIndexBufferOffsetAlignment),
63      elemType(elemTypeIn),
64      offset(offsetIn),
65      primitiveRestartEnabled(primitiveRestartEnabledIn)
66{}
67
68IndexRange IndexConversionBufferMtl::getRangeForConvertedBuffer(size_t count)
69{
70    return IndexRange{0, count};
71}
72
73// UniformConversionBufferMtl implementation
74UniformConversionBufferMtl::UniformConversionBufferMtl(ContextMtl *context, size_t offsetIn)
75    : ConversionBufferMtl(context, 0, mtl::kUniformBufferSettingOffsetMinAlignment),
76      offset(offsetIn)
77{}
78
79// VertexConversionBufferMtl implementation.
80VertexConversionBufferMtl::VertexConversionBufferMtl(ContextMtl *context,
81                                                     angle::FormatID formatIDIn,
82                                                     GLuint strideIn,
83                                                     size_t offsetIn)
84    : ConversionBufferMtl(context, 0, mtl::kVertexAttribBufferStrideAlignment),
85      formatID(formatIDIn),
86      stride(strideIn),
87      offset(offsetIn)
88{}
89
90// BufferMtl implementation
91BufferMtl::BufferMtl(const gl::BufferState &state)
92    : BufferImpl(state), mBufferPool(/** alwaysAllocNewBuffer */ true)
93{}
94
95BufferMtl::~BufferMtl() {}
96
97void BufferMtl::destroy(const gl::Context *context)
98{
99    ContextMtl *contextMtl = mtl::GetImpl(context);
100    mShadowCopy.clear();
101    mBufferPool.destroy(contextMtl);
102    mBuffer = nullptr;
103
104    clearConversionBuffers();
105}
106
107angle::Result BufferMtl::setData(const gl::Context *context,
108                                 gl::BufferBinding target,
109                                 const void *data,
110                                 size_t intendedSize,
111                                 gl::BufferUsage usage)
112{
113    return setDataImpl(context, target, data, intendedSize, usage);
114}
115
116angle::Result BufferMtl::setSubData(const gl::Context *context,
117                                    gl::BufferBinding target,
118                                    const void *data,
119                                    size_t size,
120                                    size_t offset)
121{
122    return setSubDataImpl(context, data, size, offset);
123}
124
125angle::Result BufferMtl::copySubData(const gl::Context *context,
126                                     BufferImpl *source,
127                                     GLintptr sourceOffset,
128                                     GLintptr destOffset,
129                                     GLsizeiptr size)
130{
131    if (!source)
132    {
133        return angle::Result::Continue;
134    }
135
136    ContextMtl *contextMtl = mtl::GetImpl(context);
137    auto srcMtl            = GetAs<BufferMtl>(source);
138
139    if (srcMtl->clientShadowCopyDataNeedSync(contextMtl) || mBuffer->isBeingUsedByGPU(contextMtl))
140    {
141        // If shadow copy requires a synchronization then use blit command instead.
142        // It might break a pending render pass, but still faster than synchronization with
143        // GPU.
144        mtl::BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
145        blitEncoder->copyBuffer(srcMtl->getCurrentBuffer(), sourceOffset, mBuffer, destOffset,
146                                size);
147
148        return angle::Result::Continue;
149    }
150    return setSubDataImpl(context, srcMtl->getClientShadowCopyData(contextMtl) + sourceOffset, size,
151                          destOffset);
152}
153
154angle::Result BufferMtl::map(const gl::Context *context, GLenum access, void **mapPtr)
155{
156    GLbitfield mapRangeAccess = 0;
157    if ((access & GL_WRITE_ONLY_OES) != 0 || (access & GL_READ_WRITE) != 0)
158    {
159        mapRangeAccess |= GL_MAP_WRITE_BIT;
160    }
161    return mapRange(context, 0, size(), mapRangeAccess, mapPtr);
162}
163
164angle::Result BufferMtl::mapRange(const gl::Context *context,
165                                  size_t offset,
166                                  size_t length,
167                                  GLbitfield access,
168                                  void **mapPtr)
169{
170    if (access & GL_MAP_INVALIDATE_BUFFER_BIT)
171    {
172        ANGLE_TRY(setDataImpl(context, gl::BufferBinding::InvalidEnum, nullptr, size(),
173                              mState.getUsage()));
174    }
175
176    if (mapPtr)
177    {
178        ContextMtl *contextMtl = mtl::GetImpl(context);
179        if (mBufferPool.getMaxBuffers() == 1)
180        {
181            *mapPtr = mBuffer->mapWithOpt(contextMtl, (access & GL_MAP_WRITE_BIT) == 0,
182                                          access & GL_MAP_UNSYNCHRONIZED_BIT) +
183                      offset;
184        }
185        else
186        {
187            *mapPtr = syncAndObtainShadowCopy(contextMtl) + offset;
188        }
189    }
190
191    return angle::Result::Continue;
192}
193
194angle::Result BufferMtl::unmap(const gl::Context *context, GLboolean *result)
195{
196    ContextMtl *contextMtl = mtl::GetImpl(context);
197    size_t offset          = static_cast<size_t>(mState.getMapOffset());
198    size_t len             = static_cast<size_t>(mState.getMapLength());
199
200    markConversionBuffersDirty();
201
202    if (mBufferPool.getMaxBuffers() == 1)
203    {
204        ASSERT(mBuffer);
205        if (mState.getAccessFlags() & GL_MAP_WRITE_BIT)
206        {
207            mBuffer->unmapAndFlushSubset(contextMtl, offset, len);
208        }
209        else
210        {
211            // Buffer is already mapped with readonly flag, so just unmap it, no flushing will
212            // occur.
213            mBuffer->unmap(contextMtl);
214        }
215    }
216    else
217    {
218        ASSERT(mShadowCopy.size());
219
220        if (mState.getAccessFlags() & GL_MAP_UNSYNCHRONIZED_BIT)
221        {
222            // Copy the mapped region without synchronization with GPU
223            uint8_t *ptr =
224                mBuffer->mapWithOpt(contextMtl, /* readonly */ false, /* noSync */ true) + offset;
225            std::copy(mShadowCopy.data() + offset, mShadowCopy.data() + offset + len, ptr);
226            mBuffer->unmapAndFlushSubset(contextMtl, offset, len);
227        }
228        else
229        {
230            // commit shadow copy data to GPU synchronously
231            ANGLE_TRY(commitShadowCopy(context));
232        }
233    }
234
235    if (result)
236    {
237        *result = true;
238    }
239
240    return angle::Result::Continue;
241}
242
243angle::Result BufferMtl::getIndexRange(const gl::Context *context,
244                                       gl::DrawElementsType type,
245                                       size_t offset,
246                                       size_t count,
247                                       bool primitiveRestartEnabled,
248                                       gl::IndexRange *outRange)
249{
250    const uint8_t *indices = getClientShadowCopyData(mtl::GetImpl(context)) + offset;
251
252    *outRange = gl::ComputeIndexRange(type, indices, count, primitiveRestartEnabled);
253
254    return angle::Result::Continue;
255}
256
257angle::Result BufferMtl::getFirstLastIndices(ContextMtl *contextMtl,
258                                             gl::DrawElementsType type,
259                                             size_t offset,
260                                             size_t count,
261                                             std::pair<uint32_t, uint32_t> *outIndices)
262{
263    const uint8_t *indices = getClientShadowCopyData(contextMtl) + offset;
264
265    switch (type)
266    {
267        case gl::DrawElementsType::UnsignedByte:
268            return GetFirstLastIndices(static_cast<const GLubyte *>(indices), count, outIndices);
269        case gl::DrawElementsType::UnsignedShort:
270            return GetFirstLastIndices(reinterpret_cast<const GLushort *>(indices), count,
271                                       outIndices);
272        case gl::DrawElementsType::UnsignedInt:
273            return GetFirstLastIndices(reinterpret_cast<const GLuint *>(indices), count,
274                                       outIndices);
275        default:
276            UNREACHABLE();
277            return angle::Result::Stop;
278    }
279}
280
281void BufferMtl::onDataChanged()
282{
283    markConversionBuffersDirty();
284}
285
286/* public */
287const uint8_t *BufferMtl::getClientShadowCopyData(ContextMtl *contextMtl)
288{
289    if (mBufferPool.getMaxBuffers() == 1)
290    {
291        // Don't need shadow copy in this case, use the buffer directly
292        return mBuffer->mapReadOnly(contextMtl);
293    }
294    return syncAndObtainShadowCopy(contextMtl);
295}
296
297bool BufferMtl::clientShadowCopyDataNeedSync(ContextMtl *contextMtl)
298{
299    return mBuffer->isCPUReadMemDirty();
300}
301
302void BufferMtl::ensureShadowCopySyncedFromGPU(ContextMtl *contextMtl)
303{
304    if (mBuffer->isCPUReadMemDirty())
305    {
306        const uint8_t *ptr = mBuffer->mapReadOnly(contextMtl);
307        memcpy(mShadowCopy.data(), ptr, size());
308        mBuffer->unmap(contextMtl);
309
310        mBuffer->resetCPUReadMemDirty();
311    }
312}
313uint8_t *BufferMtl::syncAndObtainShadowCopy(ContextMtl *contextMtl)
314{
315    ASSERT(mShadowCopy.size());
316
317    ensureShadowCopySyncedFromGPU(contextMtl);
318
319    return mShadowCopy.data();
320}
321
322ConversionBufferMtl *BufferMtl::getVertexConversionBuffer(ContextMtl *context,
323                                                          angle::FormatID formatID,
324                                                          GLuint stride,
325                                                          size_t offset)
326{
327    for (VertexConversionBufferMtl &buffer : mVertexConversionBuffers)
328    {
329        if (buffer.formatID == formatID && buffer.stride == stride && buffer.offset <= offset &&
330            buffer.offset % buffer.stride == offset % stride)
331        {
332            return &buffer;
333        }
334    }
335
336    mVertexConversionBuffers.emplace_back(context, formatID, stride, offset);
337    ConversionBufferMtl *conv        = &mVertexConversionBuffers.back();
338    const angle::Format &angleFormat = angle::Format::Get(formatID);
339    conv->data.updateAlignment(context, angleFormat.pixelBytes);
340
341    return conv;
342}
343
344IndexConversionBufferMtl *BufferMtl::getIndexConversionBuffer(ContextMtl *context,
345                                                              gl::DrawElementsType elemType,
346                                                              bool primitiveRestartEnabled,
347                                                              size_t offset)
348{
349    for (auto &buffer : mIndexConversionBuffers)
350    {
351        if (buffer.elemType == elemType && buffer.offset == offset &&
352            buffer.primitiveRestartEnabled == primitiveRestartEnabled)
353        {
354            return &buffer;
355        }
356    }
357
358    mIndexConversionBuffers.emplace_back(context, elemType, primitiveRestartEnabled, offset);
359    return &mIndexConversionBuffers.back();
360}
361
362ConversionBufferMtl *BufferMtl::getUniformConversionBuffer(ContextMtl *context, size_t offset)
363{
364    for (UniformConversionBufferMtl &buffer : mUniformConversionBuffers)
365    {
366        if (buffer.offset == offset)
367        {
368            return &buffer;
369        }
370    }
371
372    mUniformConversionBuffers.emplace_back(context, offset);
373    return &mUniformConversionBuffers.back();
374}
375
376void BufferMtl::markConversionBuffersDirty()
377{
378    for (VertexConversionBufferMtl &buffer : mVertexConversionBuffers)
379    {
380        buffer.dirty           = true;
381        buffer.convertedBuffer = nullptr;
382        buffer.convertedOffset = 0;
383    }
384
385    for (IndexConversionBufferMtl &buffer : mIndexConversionBuffers)
386    {
387        buffer.dirty           = true;
388        buffer.convertedBuffer = nullptr;
389        buffer.convertedOffset = 0;
390    }
391
392    for (UniformConversionBufferMtl &buffer : mUniformConversionBuffers)
393    {
394        buffer.dirty           = true;
395        buffer.convertedBuffer = nullptr;
396        buffer.convertedOffset = 0;
397    }
398    mRestartRangeCache.markDirty();
399}
400
401void BufferMtl::clearConversionBuffers()
402{
403    mVertexConversionBuffers.clear();
404    mIndexConversionBuffers.clear();
405    mUniformConversionBuffers.clear();
406    mRestartRangeCache.markDirty();
407}
408
409template <typename T>
410static std::vector<IndexRange> calculateRestartRanges(ContextMtl *ctx, mtl::BufferRef idxBuffer)
411{
412    std::vector<IndexRange> result;
413    const T *bufferData       = reinterpret_cast<const T *>(idxBuffer->mapReadOnly(ctx));
414    const size_t numIndices   = idxBuffer->size() / sizeof(T);
415    constexpr T restartMarker = std::numeric_limits<T>::max();
416    for (size_t i = 0; i < numIndices; ++i)
417    {
418        // Find the start of the restart range, i.e. first index with value of restart marker.
419        if (bufferData[i] != restartMarker)
420            continue;
421        size_t restartBegin = i;
422        // Find the end of the restart range, i.e. last index with value of restart marker.
423        do
424        {
425            ++i;
426        } while (i < numIndices && bufferData[i] == restartMarker);
427        result.emplace_back(restartBegin, i - 1);
428    }
429    idxBuffer->unmap(ctx);
430    return result;
431}
432
433const std::vector<IndexRange> &BufferMtl::getRestartIndices(ContextMtl *ctx,
434                                                            gl::DrawElementsType indexType)
435{
436    if (!mRestartRangeCache || mRestartRangeCache.indexType != indexType)
437    {
438        mRestartRangeCache.markDirty();
439        std::vector<IndexRange> ranges;
440        switch (indexType)
441        {
442            case gl::DrawElementsType::UnsignedByte:
443                ranges = calculateRestartRanges<uint8_t>(ctx, getCurrentBuffer());
444                break;
445            case gl::DrawElementsType::UnsignedShort:
446                ranges = calculateRestartRanges<uint16_t>(ctx, getCurrentBuffer());
447                break;
448            case gl::DrawElementsType::UnsignedInt:
449                ranges = calculateRestartRanges<uint32_t>(ctx, getCurrentBuffer());
450                break;
451            default:
452                ASSERT(false);
453        }
454        mRestartRangeCache = RestartRangeCache(std::move(ranges), indexType);
455    }
456    return mRestartRangeCache.ranges;
457}
458
459const std::vector<IndexRange> BufferMtl::getRestartIndicesFromClientData(
460    ContextMtl *ctx,
461    gl::DrawElementsType indexType,
462    mtl::BufferRef idxBuffer)
463{
464    std::vector<IndexRange> restartIndices;
465    switch (indexType)
466    {
467        case gl::DrawElementsType::UnsignedByte:
468            restartIndices = calculateRestartRanges<uint8_t>(ctx, idxBuffer);
469            break;
470        case gl::DrawElementsType::UnsignedShort:
471            restartIndices = calculateRestartRanges<uint16_t>(ctx, idxBuffer);
472            break;
473        case gl::DrawElementsType::UnsignedInt:
474            restartIndices = calculateRestartRanges<uint32_t>(ctx, idxBuffer);
475            break;
476        default:
477            ASSERT(false);
478    }
479    return restartIndices;
480}
481
482angle::Result BufferMtl::setDataImpl(const gl::Context *context,
483                                     gl::BufferBinding target,
484                                     const void *data,
485                                     size_t intendedSize,
486                                     gl::BufferUsage usage)
487{
488    ContextMtl *contextMtl = mtl::GetImpl(context);
489
490    // Invalidate conversion buffers
491    if (mState.getSize() != static_cast<GLint64>(intendedSize))
492    {
493        clearConversionBuffers();
494    }
495    else
496    {
497        markConversionBuffersDirty();
498    }
499
500    size_t adjustedSize = std::max<size_t>(1, intendedSize);
501
502    // Ensures no validation layer issues in std140 with data types like vec3 being 12 bytes vs 16
503    // in MSL.
504    if (target == gl::BufferBinding::Uniform)
505    {
506        adjustedSize = roundUpPow2(adjustedSize, (size_t)16);
507    }
508
509    size_t maxBuffers;
510    switch (usage)
511    {
512        case gl::BufferUsage::StaticCopy:
513        case gl::BufferUsage::StaticDraw:
514        case gl::BufferUsage::StaticRead:
515        case gl::BufferUsage::DynamicRead:
516        case gl::BufferUsage::StreamRead:
517            maxBuffers = 1;  // static/read buffer doesn't need high speed data update
518            mBufferPool.setAlwaysUseGPUMem();
519            break;
520        default:
521            // dynamic buffer, allow up to 10 update per frame/encoding without
522            // waiting for GPU.
523            if (adjustedSize <= mtl::kSharedMemBufferMaxBufSizeHint)
524            {
525                maxBuffers = 10;
526                mBufferPool.setAlwaysUseSharedMem();
527            }
528            else
529            {
530                maxBuffers = 1;
531                mBufferPool.setAlwaysUseGPUMem();
532            }
533            break;
534    }
535
536    // Re-create the buffer
537    mBuffer = nullptr;
538    ANGLE_TRY(mBufferPool.reset(contextMtl, adjustedSize, 1, maxBuffers));
539
540    if (maxBuffers > 1)
541    {
542        // We use shadow copy to maintain consistent data between buffers in pool
543        ANGLE_MTL_CHECK(contextMtl, mShadowCopy.resize(adjustedSize), GL_OUT_OF_MEMORY);
544
545        if (data)
546        {
547            // Transfer data to shadow copy buffer
548            auto ptr = static_cast<const uint8_t *>(data);
549            std::copy(ptr, ptr + intendedSize, mShadowCopy.data());
550
551            // Transfer data from shadow copy buffer to GPU buffer.
552            ANGLE_TRY(commitShadowCopy(context, adjustedSize));
553        }
554        else
555        {
556            // This is needed so that first buffer pointer could be available
557            ANGLE_TRY(commitShadowCopy(context, 0));
558        }
559    }
560    else
561    {
562        // We don't need shadow copy if there will be only one buffer in the pool.
563        ANGLE_MTL_CHECK(contextMtl, mShadowCopy.resize(0), GL_OUT_OF_MEMORY);
564
565        // Allocate one buffer to use
566        ANGLE_TRY(
567            mBufferPool.allocate(contextMtl, adjustedSize, nullptr, &mBuffer, nullptr, nullptr));
568
569        if (data)
570        {
571            ANGLE_TRY(setSubDataImpl(context, data, intendedSize, 0));
572        }
573    }
574
575#ifndef NDEBUG
576    ANGLE_MTL_OBJC_SCOPE
577    {
578        mBuffer->get().label = [NSString stringWithFormat:@"BufferMtl=%p", this];
579    }
580#endif
581
582    return angle::Result::Continue;
583}
584
585angle::Result BufferMtl::setSubDataImpl(const gl::Context *context,
586                                        const void *data,
587                                        size_t size,
588                                        size_t offset)
589{
590    if (!data)
591    {
592        return angle::Result::Continue;
593    }
594
595    ASSERT(mBuffer);
596
597    ContextMtl *contextMtl = mtl::GetImpl(context);
598
599    ANGLE_MTL_TRY(contextMtl, offset <= mBuffer->size());
600
601    auto srcPtr     = static_cast<const uint8_t *>(data);
602    auto sizeToCopy = std::min<size_t>(size, mBuffer->size() - offset);
603
604    markConversionBuffersDirty();
605
606    if (mBufferPool.getMaxBuffers() == 1)
607    {
608        ASSERT(mBuffer);
609        uint8_t *ptr = mBuffer->map(contextMtl);
610        std::copy(srcPtr, srcPtr + sizeToCopy, ptr + offset);
611        mBuffer->unmapAndFlushSubset(contextMtl, offset, sizeToCopy);
612    }
613    else
614    {
615        ASSERT(mShadowCopy.size());
616
617        // 1. Before copying data from client, we need to synchronize modified data from GPU to
618        // shadow copy first.
619        ensureShadowCopySyncedFromGPU(contextMtl);
620
621        // 2. Copy data from client to shadow copy.
622        std::copy(srcPtr, srcPtr + sizeToCopy, mShadowCopy.data() + offset);
623
624        // 3. Copy data from shadow copy to GPU.
625        ANGLE_TRY(commitShadowCopy(context));
626    }
627
628    return angle::Result::Continue;
629}
630
631angle::Result BufferMtl::commitShadowCopy(const gl::Context *context)
632{
633    return commitShadowCopy(context, size());
634}
635
636angle::Result BufferMtl::commitShadowCopy(const gl::Context *context, size_t size)
637{
638    ContextMtl *contextMtl = mtl::GetImpl(context);
639
640    if (!size)
641    {
642        // Skip mapping if size to commit is zero.
643        // zero size is passed to allocate buffer only.
644        ANGLE_TRY(mBufferPool.allocate(contextMtl, mShadowCopy.size(), nullptr, &mBuffer, nullptr,
645                                       nullptr));
646    }
647    else
648    {
649        uint8_t *ptr = nullptr;
650        mBufferPool.releaseInFlightBuffers(contextMtl);
651        ANGLE_TRY(
652            mBufferPool.allocate(contextMtl, mShadowCopy.size(), &ptr, &mBuffer, nullptr, nullptr));
653
654        std::copy(mShadowCopy.data(), mShadowCopy.data() + size, ptr);
655    }
656
657    ANGLE_TRY(mBufferPool.commit(contextMtl));
658
659    return angle::Result::Continue;
660}
661
662// SimpleWeakBufferHolderMtl implementation
663SimpleWeakBufferHolderMtl::SimpleWeakBufferHolderMtl()
664{
665    mIsWeak = true;
666}
667
668}  // namespace rx
669