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