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