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