| // |
| // Copyright 2019 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // VertexArrayMtl.mm: |
| // Implements the class methods for VertexArrayMtl. |
| // |
| |
| #include "libANGLE/renderer/metal/VertexArrayMtl.h" |
| |
| #include <TargetConditionals.h> |
| |
| #include "libANGLE/ErrorStrings.h" |
| #include "libANGLE/renderer/metal/BufferMtl.h" |
| #include "libANGLE/renderer/metal/ContextMtl.h" |
| #include "libANGLE/renderer/metal/DisplayMtl.h" |
| #include "libANGLE/renderer/metal/mtl_format_utils.h" |
| |
| #include "common/debug.h" |
| #include "common/span_util.h" |
| #include "common/unsafe_buffers.h" |
| #include "common/utilities.h" |
| |
| namespace rx |
| { |
| namespace |
| { |
| constexpr size_t kDynamicIndexDataSize = 1024 * 8; |
| |
| angle::Result StreamVertexData(ContextMtl *contextMtl, |
| mtl::BufferPool *dynamicBuffer, |
| const uint8_t *sourceData, |
| size_t bytesToAllocate, |
| size_t destOffset, |
| size_t vertexCount, |
| size_t stride, |
| VertexCopyFunction vertexLoadFunction, |
| mtl::BufferSlice *outBuffer) |
| { |
| ANGLE_CHECK(contextMtl, vertexLoadFunction, gl::err::kInternalError, GL_INVALID_OPERATION); |
| angle::Span<uint8_t> newBufferData; |
| mtl::BufferSlice newBuffer; |
| ANGLE_TRY( |
| dynamicBuffer->allocateAndMap(contextMtl, bytesToAllocate, &newBufferData, &newBuffer)); |
| vertexLoadFunction(sourceData, stride, vertexCount, newBufferData.subspan(destOffset).data()); |
| |
| ANGLE_TRY(dynamicBuffer->commit(contextMtl)); |
| *outBuffer = std::move(newBuffer); |
| return angle::Result::Continue; |
| } |
| |
| template <typename SizeT> |
| const mtl::VertexFormat &GetVertexConversionFormat(ContextMtl *contextMtl, |
| angle::FormatID originalFormat, |
| SizeT *strideOut) |
| { |
| // Convert to tightly packed format |
| const mtl::VertexFormat &packedFormat = contextMtl->getVertexFormat(originalFormat, true); |
| *strideOut = packedFormat.actualAngleFormat().pixelBytes; |
| return packedFormat; |
| } |
| |
| void StreamIndexData(ContextMtl *contextMtl, |
| gl::DrawElementsType indexType, |
| size_t indexCount, |
| angle::Span<const uint8_t> source, |
| angle::Span<uint8_t> destination, |
| bool primitiveRestartEnabled) |
| { |
| RELEASE_ASSERT(source.size() <= destination.size()); |
| if (indexType == gl::DrawElementsType::UnsignedByte) |
| { |
| // Unsigned bytes don't have direct support in Metal so we have to expand the |
| // memory to a GLushort. |
| angle::Span<uint16_t> expanded = reinterpret_span<uint16_t>(destination); |
| auto s = source.begin(); |
| auto sourceEnd = source.end(); |
| auto d = expanded.begin(); |
| if (primitiveRestartEnabled) |
| { |
| for (; s != sourceEnd; ANGLE_UNSAFE_TODO(s++), ANGLE_UNSAFE_TODO(d++)) |
| { |
| uint8_t value = *s; |
| *d = value == 0xFF ? 0xFFFF : static_cast<uint16_t>(value); |
| } |
| } |
| else |
| { |
| for (; s != sourceEnd; ANGLE_UNSAFE_TODO(s++), ANGLE_UNSAFE_TODO(d++)) |
| { |
| *d = static_cast<uint16_t>(*s); |
| } |
| } // if (primitiveRestartEnabled) |
| } |
| else |
| { |
| SpanMemcpy(destination, source); |
| } |
| } |
| |
| size_t GetVertexCount(BufferMtl *srcBuffer, |
| const gl::VertexBinding &binding, |
| uint32_t srcFormatSize) |
| { |
| // Bytes usable for vertex data. |
| GLint64 bytes = srcBuffer->size() - binding.getOffset(); |
| if (bytes < srcFormatSize) |
| return 0; |
| |
| // Count the last vertex. It may occupy less than a full stride. |
| size_t numVertices = 1; |
| bytes -= srcFormatSize; |
| |
| // Count how many strides fit remaining space. |
| if (bytes > 0) |
| numVertices += static_cast<size_t>(bytes) / binding.getStride(); |
| |
| return numVertices; |
| } |
| |
| size_t GetVertexCountWithConversion(BufferMtl *srcBuffer, |
| VertexConversionBufferMtl *conversionBuffer, |
| const gl::VertexBinding &binding, |
| uint32_t srcFormatSize) |
| { |
| // Bytes usable for vertex data. |
| GLint64 bytes = srcBuffer->size() - |
| MIN(static_cast<uintptr_t>(conversionBuffer->offset), binding.getOffset()); |
| if (bytes < srcFormatSize) |
| return 0; |
| |
| // Count the last vertex. It may occupy less than a full stride. |
| size_t numVertices = 1; |
| bytes -= srcFormatSize; |
| |
| // Count how many strides fit remaining space. |
| if (bytes > 0) |
| numVertices += static_cast<size_t>(bytes) / binding.getStride(); |
| |
| return numVertices; |
| } |
| |
| inline MTLVertexFormat GetCurrentAttribFormat(GLenum type) |
| { |
| switch (type) |
| { |
| case GL_INT: |
| case GL_INT_VEC2: |
| case GL_INT_VEC3: |
| case GL_INT_VEC4: |
| return MTLVertexFormatInt4; |
| case GL_UNSIGNED_INT: |
| case GL_UNSIGNED_INT_VEC2: |
| case GL_UNSIGNED_INT_VEC3: |
| case GL_UNSIGNED_INT_VEC4: |
| return MTLVertexFormatUInt4; |
| default: |
| return MTLVertexFormatFloat4; |
| } |
| } |
| |
| } // namespace |
| |
| // VertexArrayMtl implementation |
| VertexArrayMtl::VertexArrayMtl(const gl::VertexArrayState &state, |
| const gl::VertexArrayBuffers &vertexArrayBuffers, |
| ContextMtl *context) |
| : VertexArrayImpl(state, vertexArrayBuffers), |
| mDefaultFloatVertexFormat( |
| context->getVertexFormat(angle::FormatID::R32G32B32A32_FLOAT, false)) |
| { |
| reset(context); |
| |
| mDynamicVertexData.initialize(context, 0, mtl::kVertexAttribBufferStrideAlignment, |
| /** maxBuffers */ 10 * mtl::kMaxVertexAttribs); |
| |
| mDynamicIndexData.initialize(context, kDynamicIndexDataSize, mtl::kIndexBufferOffsetAlignment, |
| 0); |
| } |
| VertexArrayMtl::~VertexArrayMtl() {} |
| |
| void VertexArrayMtl::destroy(const gl::Context *context) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| |
| reset(contextMtl); |
| |
| mDynamicVertexData.destroy(contextMtl); |
| mDynamicIndexData.destroy(contextMtl); |
| } |
| |
| void VertexArrayMtl::reset(ContextMtl *context) |
| { |
| for (BufferHolderMtl *&buffer : mCurrentArrayBuffers) |
| { |
| buffer = nullptr; |
| } |
| for (size_t &offset : mCurrentArrayBufferOffsets) |
| { |
| offset = 0; |
| } |
| for (GLuint &stride : mCurrentArrayBufferStrides) |
| { |
| stride = 0; |
| } |
| for (const mtl::VertexFormat *&format : mCurrentArrayBufferFormats) |
| { |
| format = &mDefaultFloatVertexFormat; |
| } |
| |
| for (size_t &inlineDataSize : mCurrentArrayInlineDataSizes) |
| { |
| inlineDataSize = 0; |
| } |
| |
| for (angle::MemoryBuffer &convertedClientArray : mConvertedClientSmallArrays) |
| { |
| convertedClientArray.clear(); |
| } |
| |
| for (const uint8_t *&clientPointer : mCurrentArrayInlineDataPointers) |
| { |
| clientPointer = nullptr; |
| } |
| |
| if (context->getDisplay()->getFeatures().allowInlineConstVertexData.enabled) |
| { |
| mInlineDataMaxSize = mtl::kInlineConstDataMaxSize; |
| } |
| else |
| { |
| mInlineDataMaxSize = 0; |
| } |
| |
| mVertexArrayDirty = true; |
| } |
| |
| angle::Result VertexArrayMtl::syncState(const gl::Context *context, |
| const gl::VertexArray::DirtyBits &dirtyBits, |
| gl::VertexArray::DirtyAttribBitsArray *attribBits, |
| gl::VertexArray::DirtyBindingBitsArray *bindingBits) |
| { |
| const std::vector<gl::VertexAttribute> &attribs = mState.getVertexAttributes(); |
| const std::vector<gl::VertexBinding> &bindings = mState.getVertexBindings(); |
| |
| for (auto iter = dirtyBits.begin(), endIter = dirtyBits.end(); iter != endIter; ++iter) |
| { |
| size_t dirtyBit = *iter; |
| switch (dirtyBit) |
| { |
| case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER: |
| case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA: |
| { |
| mVertexDataDirty = true; |
| break; |
| } |
| |
| #define ANGLE_VERTEX_DIRTY_ATTRIB_FUNC(INDEX) \ |
| case gl::VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX: \ |
| ANGLE_TRY(syncDirtyAttrib(context, attribs[INDEX], bindings[attribs[INDEX].bindingIndex], \ |
| INDEX)); \ |
| mVertexArrayDirty = true; \ |
| (*attribBits)[INDEX].reset(); \ |
| break; |
| |
| ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_ATTRIB_FUNC) |
| |
| #define ANGLE_VERTEX_DIRTY_BINDING_FUNC(INDEX) \ |
| case gl::VertexArray::DIRTY_BIT_BINDING_0 + INDEX: \ |
| ANGLE_TRY(syncDirtyAttrib(context, attribs[INDEX], bindings[attribs[INDEX].bindingIndex], \ |
| INDEX)); \ |
| mVertexArrayDirty = true; \ |
| (*bindingBits)[INDEX].reset(); \ |
| break; |
| |
| ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BINDING_FUNC) |
| |
| #define ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC(INDEX) \ |
| case gl::VertexArray::DIRTY_BIT_BUFFER_DATA_0 + INDEX: \ |
| ANGLE_TRY(syncDirtyAttrib(context, attribs[INDEX], bindings[attribs[INDEX].bindingIndex], \ |
| INDEX)); \ |
| mVertexDataDirty = true; \ |
| break; |
| |
| ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC) |
| |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| // vertexDescChanged is both input and output, the input value if is true, will force new |
| // mtl::VertexDesc to be returned via vertexDescOut. This typically happens when active shader |
| // program is changed. |
| // Otherwise, it is only returned when the vertex array is dirty. |
| angle::Result VertexArrayMtl::setupDraw(const gl::Context *glContext, |
| mtl::RenderCommandEncoder *cmdEncoder, |
| bool *vertexDescChanged, |
| mtl::VertexDesc *vertexDescOut) |
| { |
| // NOTE(hqle): consider only updating dirty attributes |
| bool dirty = mVertexArrayDirty || *vertexDescChanged; |
| |
| if (dirty) |
| { |
| |
| mVertexArrayDirty = false; |
| mEmulatedInstanceAttribs.clear(); |
| |
| const gl::ProgramExecutable *executable = glContext->getState().getProgramExecutable(); |
| const gl::AttributesMask &programActiveAttribsMask = |
| executable->getActiveAttribLocationsMask(); |
| |
| const std::vector<gl::VertexAttribute> &attribs = mState.getVertexAttributes(); |
| const std::vector<gl::VertexBinding> &bindings = mState.getVertexBindings(); |
| |
| mtl::VertexDesc &desc = *vertexDescOut; |
| |
| desc.numAttribs = mtl::kMaxVertexAttribs; |
| desc.numBufferLayouts = mtl::kMaxVertexAttribs; |
| |
| // Initialize the buffer layouts with constant step rate |
| for (mtl::VertexBufferLayoutDesc &layout : desc.layouts) |
| { |
| layout = {0, 0, mtl::kVertexStepFunctionInvalid}; |
| } |
| |
| // Cache vertex shader input types |
| std::array<uint8_t, mtl::kMaxVertexAttribs> currentAttribFormats{}; |
| for (auto &input : executable->getProgramInputs()) |
| { |
| if (input.isBuiltIn()) |
| { |
| continue; |
| } |
| |
| ASSERT(input.getLocation() != -1); |
| ASSERT(input.getLocation() < static_cast<int>(mtl::kMaxVertexAttribs)); |
| currentAttribFormats[input.getLocation()] = GetCurrentAttribFormat(input.getType()); |
| } |
| MTLVertexFormat currentAttribFormat = MTLVertexFormatInvalid; |
| |
| for (uint32_t v = 0; v < mtl::kMaxVertexAttribs; ++v) |
| { |
| if (!programActiveAttribsMask.test(v)) |
| { |
| ANGLE_UNSAFE_TODO(desc.attributes[v]) = {MTLVertexFormatInvalid, 0, 0}; |
| continue; |
| } |
| |
| const auto &attrib = attribs[v]; |
| const gl::VertexBinding &binding = bindings[attrib.bindingIndex]; |
| |
| bool attribEnabled = attrib.enabled; |
| if (attribEnabled && |
| !(mCurrentArrayBuffers[v] && mCurrentArrayBuffers[v]->getCurrentBuffer()) && |
| !mCurrentArrayInlineDataPointers[v]) |
| { |
| // Disable it to avoid crash. |
| attribEnabled = false; |
| } |
| |
| if (currentAttribFormats[v] != MTLVertexFormatInvalid) |
| { |
| currentAttribFormat = static_cast<MTLVertexFormat>(currentAttribFormats[v]); |
| } |
| else |
| { |
| // This is a non-first matrix column |
| ASSERT(currentAttribFormat != MTLVertexFormatInvalid); |
| } |
| |
| if (!attribEnabled) |
| { |
| // Use default attribute |
| ANGLE_UNSAFE_TODO(desc.attributes[v]) = { |
| currentAttribFormat, |
| /*offset*/ v * mtl::kDefaultAttributeSize, |
| /*bufferIndex*/ mtl::kDefaultAttribsBindingIndex}; |
| } |
| else |
| { |
| uint32_t bufferIdx = mtl::kVboBindingIndexStart + v; |
| ASSERT(bufferIdx < mtl::kMaxVertexAttribs); |
| uint32_t bufferOffset = static_cast<uint32_t>(mCurrentArrayBufferOffsets[v]); |
| ASSERT((bufferOffset % mtl::kVertexAttribBufferStrideAlignment) == 0); |
| ANGLE_UNSAFE_TODO(desc.attributes[v]) = {mCurrentArrayBufferFormats[v]->metalFormat, |
| /*offset*/ 0, bufferIdx}; |
| MTLVertexStepFunction stepFunction = MTLVertexStepFunctionPerVertex; |
| uint32_t stepRate = 1; |
| if (binding.getDivisor() != 0) |
| { |
| stepFunction = MTLVertexStepFunctionPerInstance; |
| stepRate = binding.getDivisor(); |
| } |
| |
| // Metal does not allow the sum of the buffer binding |
| // offset and the vertex layout stride to be greater |
| // than the buffer length. |
| // In OpenGL, this is valid only when a draw call accesses just |
| // one vertex, so just replace the stride with the format size. |
| uint32_t stride = mCurrentArrayBufferStrides[v]; |
| if (mCurrentArrayBuffers[v]) |
| { |
| const size_t length = mCurrentArrayBuffers[v]->getCurrentBuffer()->size(); |
| const size_t offset = mCurrentArrayBufferOffsets[v]; |
| ASSERT(offset < length); |
| if (length - offset < stride) |
| { |
| stride = mCurrentArrayBufferFormats[v]->actualAngleFormat().pixelBytes; |
| ASSERT(stride % mtl::kVertexAttribBufferStrideAlignment == 0); |
| } |
| } |
| ANGLE_UNSAFE_TODO(desc.layouts[bufferIdx]) = {stepRate, stride, stepFunction}; |
| } |
| } // for (v) |
| } |
| |
| if (dirty || mVertexDataDirty) |
| { |
| mVertexDataDirty = false; |
| const gl::ProgramExecutable *executable = glContext->getState().getProgramExecutable(); |
| const gl::AttributesMask &programActiveAttribsMask = |
| executable->getActiveAttribLocationsMask(); |
| |
| for (uint32_t v = 0; v < mtl::kMaxVertexAttribs; ++v) |
| { |
| if (!programActiveAttribsMask.test(v)) |
| { |
| continue; |
| } |
| uint32_t bufferIdx = mtl::kVboBindingIndexStart + v; |
| if (mCurrentArrayBuffers[v]) |
| { |
| size_t bufferOffset = mCurrentArrayBufferOffsets[v]; |
| cmdEncoder->setVertexBuffer(mCurrentArrayBuffers[v]->getCurrentBuffer(), |
| bufferOffset, bufferIdx); |
| } |
| else if (mCurrentArrayInlineDataPointers[v]) |
| { |
| // No buffer specified, use the client memory directly as inline constant data |
| ASSERT(mCurrentArrayInlineDataSizes[v] <= mInlineDataMaxSize); |
| cmdEncoder->setVertexBytes(mCurrentArrayInlineDataPointers[v], |
| mCurrentArrayInlineDataSizes[v], bufferIdx); |
| } |
| } |
| } |
| |
| *vertexDescChanged = dirty; |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result VertexArrayMtl::updateClientAttribs(const gl::Context *context, |
| GLint firstVertex, |
| GLsizei vertexOrIndexCount, |
| GLsizei instanceCount, |
| gl::DrawElementsType indexTypeOrInvalid, |
| const void *indices) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| const gl::AttributesMask &clientAttribs = context->getActiveClientAttribsMask(); |
| |
| ASSERT(clientAttribs.any()); |
| |
| GLint startVertex; |
| size_t vertexCount; |
| ANGLE_TRY(GetVertexRangeInfo(context, firstVertex, vertexOrIndexCount, indexTypeOrInvalid, |
| indices, 0, &startVertex, &vertexCount)); |
| |
| mDynamicVertexData.releaseInFlightBuffers(contextMtl); |
| |
| const std::vector<gl::VertexAttribute> &attribs = mState.getVertexAttributes(); |
| const std::vector<gl::VertexBinding> &bindings = mState.getVertexBindings(); |
| |
| for (size_t attribIndex : clientAttribs) |
| { |
| const gl::VertexAttribute &attrib = attribs[attribIndex]; |
| const gl::VertexBinding &binding = bindings[attrib.bindingIndex]; |
| ASSERT(attrib.enabled && getVertexArrayBuffer(attrib.bindingIndex) == nullptr); |
| |
| // Source client memory pointer |
| const uint8_t *src = static_cast<const uint8_t *>(attrib.pointer); |
| ASSERT(src); |
| |
| GLint startElement; |
| size_t elementCount; |
| if (binding.getDivisor() == 0) |
| { |
| // Per vertex attribute |
| startElement = startVertex; |
| elementCount = vertexCount; |
| } |
| else |
| { |
| // Per instance attribute |
| startElement = 0; |
| elementCount = UnsignedCeilDivide(instanceCount, binding.getDivisor()); |
| } |
| size_t bytesIntendedToUse = (startElement + elementCount) * binding.getStride(); |
| |
| const mtl::VertexFormat &format = contextMtl->getVertexFormat(attrib.format->id, false); |
| bool needStreaming = format.actualFormatId != format.intendedFormatId || |
| (binding.getStride() % mtl::kVertexAttribBufferStrideAlignment) != 0 || |
| (binding.getStride() < format.actualAngleFormat().pixelBytes) || |
| bytesIntendedToUse > mInlineDataMaxSize; |
| |
| if (!needStreaming) |
| { |
| // Data will be uploaded directly as inline constant data |
| mCurrentArrayBuffers[attribIndex] = nullptr; |
| mCurrentArrayInlineDataPointers[attribIndex] = src; |
| mCurrentArrayInlineDataSizes[attribIndex] = bytesIntendedToUse; |
| mCurrentArrayBufferOffsets[attribIndex] = 0; |
| mCurrentArrayBufferFormats[attribIndex] = &format; |
| mCurrentArrayBufferStrides[attribIndex] = binding.getStride(); |
| } |
| else |
| { |
| GLuint convertedStride; |
| // Need to stream the client vertex data to a buffer. |
| const mtl::VertexFormat &streamFormat = |
| GetVertexConversionFormat(contextMtl, attrib.format->id, &convertedStride); |
| |
| // Allocate space for startElement + elementCount so indexing will work. If we don't |
| // start at zero all the indices will be off. |
| // Only elementCount vertices will be used by the upcoming draw so that is all we copy. |
| size_t bytesToAllocate = (startElement + elementCount) * convertedStride; |
| ANGLE_UNSAFE_TODO(src += startElement * binding.getStride()); |
| size_t destOffset = startElement * convertedStride; |
| |
| mCurrentArrayBufferFormats[attribIndex] = &streamFormat; |
| mCurrentArrayBufferStrides[attribIndex] = convertedStride; |
| |
| if (bytesToAllocate <= mInlineDataMaxSize) |
| { |
| // If the data is small enough, use host memory instead of creating GPU buffer. To |
| // avoid synchronizing access to GPU buffer that is still in use. |
| angle::MemoryBuffer &convertedClientArray = |
| mConvertedClientSmallArrays[attribIndex]; |
| if (bytesToAllocate > convertedClientArray.size()) |
| { |
| ANGLE_CHECK_GL_ALLOC(contextMtl, convertedClientArray.resize(bytesToAllocate)); |
| } |
| |
| ASSERT(streamFormat.vertexLoadFunction); |
| streamFormat.vertexLoadFunction( |
| src, binding.getStride(), elementCount, |
| ANGLE_UNSAFE_TODO(convertedClientArray.data() + destOffset)); |
| |
| mCurrentArrayBuffers[attribIndex] = nullptr; |
| mCurrentArrayInlineDataPointers[attribIndex] = convertedClientArray.data(); |
| mCurrentArrayInlineDataSizes[attribIndex] = bytesToAllocate; |
| mCurrentArrayBufferOffsets[attribIndex] = 0; |
| } |
| else |
| { |
| // Stream the client data to a GPU buffer. Synchronization might happen if buffer is |
| // in use. |
| mDynamicVertexData.updateAlignment(contextMtl, |
| streamFormat.actualAngleFormat().pixelBytes); |
| mtl::BufferSlice streamedBuffer; |
| ANGLE_TRY(StreamVertexData(contextMtl, &mDynamicVertexData, src, bytesToAllocate, |
| destOffset, elementCount, binding.getStride(), |
| streamFormat.vertexLoadFunction, &streamedBuffer)); |
| if (contextMtl->getDisplay()->getFeatures().flushAfterStreamVertexData.enabled) |
| { |
| // WaitUntilScheduled is needed for this workaround. NoWait does not have the |
| // needed effect. |
| contextMtl->flushCommandBuffer(mtl::WaitUntilScheduled); |
| } |
| |
| mConvertedArrayBufferHolders[attribIndex].set(streamedBuffer.buffer()); |
| mCurrentArrayBufferOffsets[attribIndex] = streamedBuffer.offset(); |
| mCurrentArrayBuffers[attribIndex] = &mConvertedArrayBufferHolders[attribIndex]; |
| } |
| } // if (needStreaming) |
| } |
| |
| mVertexArrayDirty = true; |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result VertexArrayMtl::syncDirtyAttrib(const gl::Context *glContext, |
| const gl::VertexAttribute &attrib, |
| const gl::VertexBinding &binding, |
| size_t attribIndex) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(glContext); |
| ASSERT(mtl::kMaxVertexAttribs > attribIndex); |
| mContentsObserverBindingsMask.reset(attrib.bindingIndex); |
| |
| if (attrib.enabled) |
| { |
| gl::Buffer *bufferGL = getVertexArrayBuffer(attrib.bindingIndex); |
| const mtl::VertexFormat &format = contextMtl->getVertexFormat(attrib.format->id, false); |
| |
| if (bufferGL) |
| { |
| BufferMtl *bufferMtl = mtl::GetImpl(bufferGL); |
| // https://bugs.webkit.org/show_bug.cgi?id=236733 |
| // even non-converted buffers need to be observed for potential |
| // data rebinds. |
| mContentsObserverBindingsMask.set(attrib.bindingIndex); |
| bool needConversion = |
| format.actualFormatId != format.intendedFormatId || |
| (binding.getOffset() % mtl::kVertexAttribBufferStrideAlignment) != 0 || |
| (binding.getStride() < format.actualAngleFormat().pixelBytes) || |
| (binding.getStride() % mtl::kVertexAttribBufferStrideAlignment) != 0; |
| |
| if (needConversion) |
| { |
| ANGLE_TRY(convertVertexBuffer(glContext, bufferMtl, binding, attribIndex, format)); |
| } |
| else |
| { |
| mCurrentArrayBuffers[attribIndex] = bufferMtl; |
| mCurrentArrayBufferOffsets[attribIndex] = binding.getOffset(); |
| mCurrentArrayBufferStrides[attribIndex] = binding.getStride(); |
| |
| mCurrentArrayBufferFormats[attribIndex] = &format; |
| } |
| } |
| else |
| { |
| // ContextMtl must feed the client data using updateClientAttribs() |
| } |
| } |
| else |
| { |
| // Use default attribute value. Handled in setupDraw(). |
| mCurrentArrayBuffers[attribIndex] = nullptr; |
| mCurrentArrayBufferOffsets[attribIndex] = 0; |
| mCurrentArrayBufferStrides[attribIndex] = 0; |
| mCurrentArrayBufferFormats[attribIndex] = |
| &contextMtl->getVertexFormat(angle::FormatID::NONE, false); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| template <size_t indexRewind> |
| static void AppendDrawCommandsTemplate(std::vector<DrawCommandRange> &drawCommands, |
| size_t count, |
| size_t firstIndex, |
| size_t indexSize) |
| { |
| // Break the draw into hunks of 100'663'290 to avoid overflowing index count uint32_t. |
| // Preserves primitive boundaries as the limit is divisible by all the possible per primitive |
| // counts. |
| constexpr size_t perCommandIndexCount = 0xffffff * 2 * 3; |
| while (count > 0) |
| { |
| size_t offset = firstIndex * indexSize; |
| size_t commandCount = std::min(count, perCommandIndexCount); |
| drawCommands.emplace_back(static_cast<uint32_t>(commandCount), offset); |
| count -= commandCount; |
| firstIndex += commandCount - indexRewind; // Underflow ok, loop will terminate. |
| } |
| } |
| |
| static void AppendDrawCommands(std::vector<DrawCommandRange> &drawCommands, |
| gl::PrimitiveMode mode, |
| size_t count, |
| size_t firstIndex, |
| gl::PrimitiveMode drawMode, |
| gl::DrawElementsType type) |
| { |
| size_t elementSize = gl::GetDrawElementsTypeSize(type); |
| uint32_t perPrimitiveIndexCount; |
| switch (drawMode) |
| { |
| case gl::PrimitiveMode::Points: |
| perPrimitiveIndexCount = 1; |
| break; |
| case gl::PrimitiveMode::Lines: |
| case gl::PrimitiveMode::LineStrip: |
| perPrimitiveIndexCount = 2; |
| break; |
| case gl::PrimitiveMode::Triangles: |
| case gl::PrimitiveMode::TriangleStrip: |
| perPrimitiveIndexCount = 3; |
| break; |
| default: |
| UNREACHABLE(); |
| return; |
| } |
| if (count < perPrimitiveIndexCount) |
| { |
| return; |
| } |
| if (mode != drawMode) |
| { |
| firstIndex *= perPrimitiveIndexCount; |
| count = (count - perPrimitiveIndexCount + 1) * perPrimitiveIndexCount; |
| } |
| switch (drawMode) |
| { |
| case gl::PrimitiveMode::Points: |
| AppendDrawCommandsTemplate<0>(drawCommands, count, firstIndex, elementSize); |
| break; |
| case gl::PrimitiveMode::Lines: |
| AppendDrawCommandsTemplate<0>(drawCommands, count, firstIndex, elementSize); |
| break; |
| case gl::PrimitiveMode::LineStrip: |
| AppendDrawCommandsTemplate<1>(drawCommands, count, firstIndex, elementSize); |
| break; |
| case gl::PrimitiveMode::Triangles: |
| AppendDrawCommandsTemplate<0>(drawCommands, count, firstIndex, elementSize); |
| break; |
| case gl::PrimitiveMode::TriangleStrip: |
| AppendDrawCommandsTemplate<2>(drawCommands, count, firstIndex, elementSize); |
| break; |
| default: |
| UNREACHABLE(); |
| return; |
| } |
| } |
| |
| // Computes draw command ranges from draw index ranges for primitive restart. |
| // The draw index ranges are in source buffer element space. The output draw commands |
| // are in draw buffer space, accounting for any expansion from mode conversion |
| // (e.g., TriangleStrip to Triangles via provoking vertex). |
| // |
| // drawIndexRanges: pre-computed ranges of consecutive non-restart indices in the source buffer |
| // firstIndex: first element index in the source buffer to draw from |
| // count: number of source elements in the draw window |
| // mode: source primitive mode, may be strip. |
| // drawMode: output primitive mode. Always a simple type: Points, Lines, Triangles. |
| // indexBufferType: type of the output buffer elements. |
| void AppendSimpleDrawCommandRanges(std::vector<DrawCommandRange> &drawCommands, |
| gl::PrimitiveMode mode, |
| uint32_t count, |
| size_t firstIndex, |
| const std::vector<DrawIndexRange> &drawIndexRanges, |
| gl::PrimitiveMode drawMode, |
| gl::DrawElementsType indexBufferType) |
| { |
| uint32_t perPrimitiveIndexCount; |
| switch (drawMode) |
| { |
| case gl::PrimitiveMode::Points: |
| perPrimitiveIndexCount = 1; |
| break; |
| case gl::PrimitiveMode::Lines: |
| perPrimitiveIndexCount = 2; |
| break; |
| case gl::PrimitiveMode::Triangles: |
| perPrimitiveIndexCount = 3; |
| break; |
| default: |
| UNREACHABLE(); |
| return; |
| } |
| if (count < perPrimitiveIndexCount) |
| { |
| return; |
| } |
| const size_t drawIndexSize = gl::GetDrawElementsTypeSize(indexBufferType); |
| const size_t lastIndex = firstIndex + count - 1; |
| |
| for (const auto &range : drawIndexRanges) |
| { |
| if (range.end < firstIndex) |
| { |
| continue; |
| } |
| if (range.begin > lastIndex) |
| { |
| break; |
| } |
| DrawIndexRange clippedRange{std::max(range.begin, firstIndex), |
| std::min(range.end, lastIndex)}; |
| size_t indexCount = clippedRange.end - clippedRange.begin + 1; |
| |
| if (indexCount < perPrimitiveIndexCount) |
| { |
| continue; |
| } |
| |
| size_t drawIndexCount; |
| size_t drawBeginIndex; |
| if (mode == drawMode) |
| { |
| drawIndexCount = indexCount - (indexCount % perPrimitiveIndexCount); |
| drawBeginIndex = clippedRange.begin; |
| } |
| else |
| { |
| // Expanded modes: `N` source indices produce `(N - perPrimitiveIndexCount + 1)` |
| // primitives that produce `perPrimitiveIndexCount` indices. |
| drawIndexCount = (indexCount - perPrimitiveIndexCount + 1) * perPrimitiveIndexCount; |
| drawBeginIndex = clippedRange.begin * perPrimitiveIndexCount; |
| } |
| AppendDrawCommandsTemplate<0>(drawCommands, drawIndexCount, drawBeginIndex, drawIndexSize); |
| } |
| } |
| |
| angle::Result VertexArrayMtl::resolveDrawElementsDraw( |
| const gl::Context *glContext, |
| gl::PrimitiveMode mode, |
| gl::DrawElementsType type, |
| GLsizei count, |
| const void *indices, |
| bool rewriteProvokingVertex, |
| bool isPrimitiveRestartEnabled, |
| gl::PrimitiveMode *outNewMode, |
| std::vector<DrawCommandRange> *outDrawCommands, |
| mtl::BufferSlice *outIndexBuffer, |
| gl::DrawElementsType *outIndexBufferType) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(glContext); |
| |
| // Resulting draw is either `uint16_t` or `uint32_t`. |
| gl::DrawElementsType indexBufferType = |
| type == gl::DrawElementsType::UnsignedByte ? gl::DrawElementsType::UnsignedShort : type; |
| |
| // Resulting draw is made with `newMode`. |
| gl::PrimitiveMode newMode = mode; |
| if (rewriteProvokingVertex) |
| { |
| // Provoking vertex will convert strips to their simple equivalents. |
| switch (mode) |
| { |
| case gl::PrimitiveMode::Triangles: |
| case gl::PrimitiveMode::TriangleStrip: |
| newMode = gl::PrimitiveMode::Triangles; |
| break; |
| case gl::PrimitiveMode::Lines: |
| case gl::PrimitiveMode::LineStrip: |
| newMode = gl::PrimitiveMode::Lines; |
| break; |
| default: |
| UNREACHABLE(); |
| return angle::Result::Stop; |
| } |
| } |
| |
| size_t firstIndex; |
| mtl::BufferSlice indexBuffer; |
| |
| // Step 1: Get the index buffer of supported type and resolve the first index to |
| // process. |
| const gl::Buffer *glElementArrayBuffer = getElementArrayBuffer(); |
| if (glElementArrayBuffer == nullptr) |
| { |
| firstIndex = 0; |
| ANGLE_TRY(streamIndexBufferFromClient(glContext, type, count, indices, &indexBuffer)); |
| } |
| else |
| { |
| firstIndex = static_cast<size_t>(reinterpret_cast<uintptr_t>(indices)) / |
| gl::GetDrawElementsTypeSize(type); |
| if (type != indexBufferType) |
| { |
| ANGLE_TRY(convertIndexBuffer(glContext, type, 0, &indexBuffer)); |
| } |
| else |
| { |
| BufferMtl *bufferMtl = mtl::GetImpl(glElementArrayBuffer); |
| indexBuffer = mtl::BufferSlice(bufferMtl->getCurrentBuffer()); |
| } |
| } |
| |
| // Step 2: GL draw range is firstIndex, count. In case Metal primitive topology for |
| // `newMode` does not support primitive restart, compute the list of draw ranges |
| // containing the primitives. |
| const std::vector<DrawIndexRange> *indexRanges; |
| std::vector<DrawIndexRange> indexRangesStorage; |
| |
| bool isSimpleType = newMode == gl::PrimitiveMode::Points || |
| newMode == gl::PrimitiveMode::Lines || |
| newMode == gl::PrimitiveMode::Triangles; |
| |
| if (isPrimitiveRestartEnabled && isSimpleType) |
| { |
| if (glElementArrayBuffer != nullptr) |
| { |
| BufferMtl *idxBuffer = mtl::GetImpl(glElementArrayBuffer); |
| indexRanges = &idxBuffer->getDrawIndexRanges(contextMtl, type); |
| } |
| else |
| { |
| indexRangesStorage = BufferMtl::GetDrawIndexRangesFromClientData(type, count, indices); |
| indexRanges = &indexRangesStorage; |
| } |
| } |
| else |
| { |
| // No primitive restart splitting. Inject a single full range. |
| indexRangesStorage.emplace_back(firstIndex, firstIndex + count - 1); |
| indexRanges = &indexRangesStorage; |
| } |
| |
| // Step 3: Conditionally rewrite the index buffer for provoking vertex. |
| // preconditionIndexBuffer dispatches per-range, so with primitive restart it only |
| // processes the non-restart runs (not the full buffer). |
| if (rewriteProvokingVertex) |
| { |
| ANGLE_TRY(contextMtl->getProvokingVertexHelper().preconditionIndexBuffer( |
| contextMtl, count, mode, firstIndex, isPrimitiveRestartEnabled, *indexRanges, |
| std::move(indexBuffer), indexBufferType, &indexBuffer)); |
| } |
| |
| // Step 4: Compute draw command ranges (handles primitive restart splitting and large draw |
| // chunking). Reuses the same index ranges computed in Step 2. |
| std::vector<DrawCommandRange> drawCommands; |
| |
| if (isPrimitiveRestartEnabled && isSimpleType) |
| { |
| AppendSimpleDrawCommandRanges(drawCommands, mode, static_cast<uint32_t>(count), firstIndex, |
| *indexRanges, newMode, indexBufferType); |
| } |
| else |
| { |
| AppendDrawCommands(drawCommands, mode, static_cast<uint32_t>(count), firstIndex, newMode, |
| indexBufferType); |
| } |
| |
| *outNewMode = newMode; |
| *outDrawCommands = std::move(drawCommands); |
| *outIndexBuffer = std::move(indexBuffer); |
| *outIndexBufferType = indexBufferType; |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result VertexArrayMtl::convertIndexBuffer(const gl::Context *glContext, |
| gl::DrawElementsType indexType, |
| size_t offset, |
| mtl::BufferSlice *outIdxBuffer) |
| { |
| size_t offsetModulo = offset % mtl::kIndexBufferOffsetAlignment; |
| ASSERT(offsetModulo != 0 || indexType == gl::DrawElementsType::UnsignedByte); |
| |
| size_t alignedOffset = offset - offsetModulo; |
| if (indexType == gl::DrawElementsType::UnsignedByte) |
| { |
| // Unsigned byte index will be promoted to unsigned short, thus double its offset. |
| alignedOffset = alignedOffset << 1; |
| } |
| |
| ContextMtl *contextMtl = mtl::GetImpl(glContext); |
| const gl::State &glState = glContext->getState(); |
| BufferMtl *idxBuffer = mtl::GetImpl(getElementArrayBuffer()); |
| |
| IndexConversionBufferMtl *conversion = idxBuffer->getIndexConversionBuffer( |
| contextMtl, indexType, glState.isPrimitiveRestartEnabled(), offsetModulo); |
| |
| // Has the content of the buffer has changed since last conversion? |
| if (!conversion->dirty) |
| { |
| // reuse the converted buffer |
| *outIdxBuffer = conversion->buffer.subslice(alignedOffset); |
| return angle::Result::Continue; |
| } |
| |
| DisplayMtl *display = contextMtl->getDisplay(); |
| |
| const size_t elementSize = gl::GetDrawElementsTypeSize(indexType); |
| const size_t indexCount = (idxBuffer->size() - offsetModulo) / elementSize; |
| const size_t indexSize = indexCount * elementSize; |
| const size_t convertedIndexSize = |
| indexType == gl::DrawElementsType::UnsignedByte ? indexSize * 2 : indexSize; |
| conversion->bufferPool.releaseInFlightBuffers(contextMtl); |
| mtl::BufferSlice converted; |
| |
| if ((!display->getFeatures().hasCheapRenderPass.enabled && |
| contextMtl->getRenderCommandEncoder())) |
| { |
| angle::Span<uint8_t> destination; |
| ANGLE_TRY(conversion->bufferPool.allocateAndMap(contextMtl, convertedIndexSize, |
| &destination, &converted)); |
| |
| // We shouldn't use GPU to convert when we are in a middle of a render pass. |
| angle::Span<const uint8_t> source = |
| idxBuffer->getBufferDataReadOnly(contextMtl, offsetModulo).first(indexSize); |
| StreamIndexData(contextMtl, indexType, indexCount, source, destination, |
| glState.isPrimitiveRestartEnabled()); |
| } |
| else |
| { |
| mtl::BufferSlice source = |
| mtl::BufferSlice(idxBuffer->getCurrentBuffer()).subslice(offsetModulo); |
| ANGLE_TRY(conversion->bufferPool.allocate(contextMtl, convertedIndexSize, &converted)); |
| ANGLE_TRY(display->getUtils().convertIndexBufferGPU( |
| contextMtl, indexType, static_cast<uint32_t>(indexCount), source, converted, |
| glState.isPrimitiveRestartEnabled())); |
| } |
| ANGLE_TRY(conversion->bufferPool.commit(contextMtl)); |
| conversion->dirty = false; |
| conversion->buffer = std::move(converted); |
| // Calculate ranges for prim restart simple types. |
| *outIdxBuffer = conversion->buffer.subslice(alignedOffset); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result VertexArrayMtl::streamIndexBufferFromClient(const gl::Context *context, |
| gl::DrawElementsType indexType, |
| size_t indexCount, |
| const void *sourcePointer, |
| mtl::BufferSlice *outIdxBuffer) |
| { |
| ASSERT(getElementArrayBuffer() == nullptr); |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| |
| const size_t elementSize = gl::GetDrawElementsTypeSize(indexType); |
| const size_t indexSize = indexCount * elementSize; |
| auto source = ANGLE_UNSAFE_TODO( |
| angle::Span<const uint8_t>(static_cast<const uint8_t *>(sourcePointer), indexSize)); |
| const size_t convertedIndexSize = |
| indexType == gl::DrawElementsType::UnsignedByte ? indexSize * 2 : indexSize; |
| mDynamicIndexData.releaseInFlightBuffers(contextMtl); |
| mtl::BufferSlice converted; |
| angle::Span<uint8_t> dstData; |
| ANGLE_TRY( |
| mDynamicIndexData.allocateAndMap(contextMtl, convertedIndexSize, &dstData, &converted)); |
| StreamIndexData(contextMtl, indexType, indexCount, source, dstData, |
| context->getState().isPrimitiveRestartEnabled()); |
| ANGLE_TRY(mDynamicIndexData.commit(contextMtl)); |
| *outIdxBuffer = std::move(converted); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result VertexArrayMtl::convertVertexBuffer(const gl::Context *glContext, |
| BufferMtl *srcBuffer, |
| const gl::VertexBinding &binding, |
| size_t attribIndex, |
| const mtl::VertexFormat &srcVertexFormat) |
| { |
| unsigned srcFormatSize = srcVertexFormat.intendedAngleFormat().pixelBytes; |
| |
| size_t numVertices = GetVertexCount(srcBuffer, binding, srcFormatSize); |
| if (numVertices == 0) |
| { |
| // Out of bound buffer access, can return any values. |
| // See KHR_robust_buffer_access_behavior |
| mCurrentArrayBuffers[attribIndex] = srcBuffer; |
| mCurrentArrayBufferFormats[attribIndex] = &srcVertexFormat; |
| mCurrentArrayBufferOffsets[attribIndex] = 0; |
| mCurrentArrayBufferStrides[attribIndex] = 16; |
| return angle::Result::Continue; |
| } |
| |
| ContextMtl *contextMtl = mtl::GetImpl(glContext); |
| |
| // Convert to tightly packed format |
| GLuint stride; |
| const mtl::VertexFormat &convertedFormat = |
| GetVertexConversionFormat(contextMtl, srcVertexFormat.intendedFormatId, &stride); |
| |
| ConversionBufferMtl *conversion = srcBuffer->getVertexConversionBuffer( |
| contextMtl, srcVertexFormat.intendedFormatId, binding.getStride(), binding.getOffset()); |
| |
| // Has the content of the buffer has changed since last conversion? |
| if (!conversion->dirty) |
| { |
| VertexConversionBufferMtl *vertexConversionMtl = |
| static_cast<VertexConversionBufferMtl *>(conversion); |
| ASSERT((binding.getOffset() - vertexConversionMtl->offset) % binding.getStride() == 0); |
| mConvertedArrayBufferHolders[attribIndex].set(conversion->buffer.buffer()); |
| mCurrentArrayBufferOffsets[attribIndex] = |
| conversion->buffer.offset() + |
| stride * ((binding.getOffset() - vertexConversionMtl->offset) / binding.getStride()); |
| |
| mCurrentArrayBuffers[attribIndex] = &mConvertedArrayBufferHolders[attribIndex]; |
| mCurrentArrayBufferFormats[attribIndex] = &convertedFormat; |
| mCurrentArrayBufferStrides[attribIndex] = stride; |
| return angle::Result::Continue; |
| } |
| numVertices = GetVertexCountWithConversion( |
| srcBuffer, static_cast<VertexConversionBufferMtl *>(conversion), binding, srcFormatSize); |
| |
| const angle::Format &convertedAngleFormat = convertedFormat.actualAngleFormat(); |
| bool canConvertToFloatOnGPU = |
| convertedAngleFormat.isFloat() && !convertedAngleFormat.isVertexTypeHalfFloat(); |
| |
| bool canExpandComponentsOnGPU = convertedFormat.actualSameGLType; |
| |
| conversion->bufferPool.releaseInFlightBuffers(contextMtl); |
| conversion->bufferPool.updateAlignment(contextMtl, convertedAngleFormat.pixelBytes); |
| |
| if (canConvertToFloatOnGPU || canExpandComponentsOnGPU) |
| { |
| ANGLE_TRY(convertVertexBufferGPU(glContext, srcBuffer, binding, attribIndex, |
| convertedFormat, stride, numVertices, |
| canExpandComponentsOnGPU, conversion)); |
| } |
| else |
| { |
| ANGLE_TRY(convertVertexBufferCPU(contextMtl, srcBuffer, binding, attribIndex, |
| convertedFormat, stride, numVertices, conversion)); |
| } |
| |
| mConvertedArrayBufferHolders[attribIndex].set(conversion->buffer.buffer()); |
| mCurrentArrayBufferOffsets[attribIndex] = |
| conversion->buffer.offset() + |
| stride * |
| ((binding.getOffset() - static_cast<VertexConversionBufferMtl *>(conversion)->offset) / |
| binding.getStride()); |
| mCurrentArrayBuffers[attribIndex] = &mConvertedArrayBufferHolders[attribIndex]; |
| mCurrentArrayBufferFormats[attribIndex] = &convertedFormat; |
| mCurrentArrayBufferStrides[attribIndex] = stride; |
| |
| ASSERT(conversion->dirty); |
| conversion->dirty = false; |
| |
| #ifndef NDEBUG |
| ANGLE_MTL_OBJC_SCOPE |
| { |
| mConvertedArrayBufferHolders[attribIndex].getCurrentBuffer()->get().label = |
| [NSString stringWithFormat:@"Converted from %p offset=%zu stride=%u", srcBuffer, |
| binding.getOffset(), binding.getStride()]; |
| } |
| #endif |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result VertexArrayMtl::convertVertexBufferCPU(ContextMtl *contextMtl, |
| BufferMtl *srcBuffer, |
| const gl::VertexBinding &binding, |
| size_t attribIndex, |
| const mtl::VertexFormat &convertedFormat, |
| GLuint targetStride, |
| size_t numVertices, |
| ConversionBufferMtl *conversion) |
| { |
| VertexConversionBufferMtl *vertexConverison = |
| static_cast<VertexConversionBufferMtl *>(conversion); |
| size_t srcOffset = MIN(binding.getOffset(), static_cast<uintptr_t>(vertexConverison->offset)); |
| angle::Span<const uint8_t> srcBytes = srcBuffer->getBufferDataReadOnly(contextMtl, srcOffset); |
| ANGLE_CHECK_GL_ALLOC(contextMtl, !srcBytes.empty()); |
| mtl::BufferSlice convertedBuffer; |
| ANGLE_TRY(StreamVertexData(contextMtl, &conversion->bufferPool, srcBytes.data(), |
| numVertices * targetStride, 0, numVertices, binding.getStride(), |
| convertedFormat.vertexLoadFunction, &convertedBuffer)); |
| conversion->buffer = std::move(convertedBuffer); |
| return angle::Result::Continue; |
| } |
| |
| angle::Result VertexArrayMtl::convertVertexBufferGPU(const gl::Context *glContext, |
| BufferMtl *srcBuffer, |
| const gl::VertexBinding &binding, |
| size_t attribIndex, |
| const mtl::VertexFormat &convertedFormat, |
| GLuint targetStride, |
| size_t numVertices, |
| bool isExpandingComponents, |
| ConversionBufferMtl *conversion) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(glContext); |
| |
| mtl::BufferSlice newBuffer; |
| ANGLE_TRY(conversion->bufferPool.allocate(contextMtl, numVertices * targetStride, &newBuffer)); |
| |
| ANGLE_CHECK_GL_MATH(contextMtl, binding.getOffset() <= std::numeric_limits<uint32_t>::max()); |
| ANGLE_CHECK_GL_MATH(contextMtl, newBuffer.offset() <= std::numeric_limits<uint32_t>::max()); |
| ANGLE_CHECK_GL_MATH(contextMtl, numVertices <= std::numeric_limits<uint32_t>::max()); |
| |
| mtl::VertexFormatConvertParams params; |
| VertexConversionBufferMtl *vertexConversion = |
| static_cast<VertexConversionBufferMtl *>(conversion); |
| params.srcBuffer = srcBuffer->getCurrentBuffer(); |
| params.srcBufferStartOffset = static_cast<uint32_t>( |
| MIN(static_cast<uintptr_t>(vertexConversion->offset), binding.getOffset())); |
| params.srcStride = binding.getStride(); |
| params.srcDefaultAlphaData = convertedFormat.defaultAlpha; |
| |
| params.dstBuffer = newBuffer.buffer(); |
| params.dstBufferStartOffset = static_cast<uint32_t>(newBuffer.offset()); |
| params.dstStride = targetStride; |
| params.dstComponents = convertedFormat.actualAngleFormat().channelCount; |
| |
| params.vertexCount = static_cast<uint32_t>(numVertices); |
| |
| mtl::RenderUtils &utils = contextMtl->getDisplay()->getUtils(); |
| |
| // Compute based buffer conversion. |
| if (!isExpandingComponents) |
| { |
| ANGLE_TRY(utils.convertVertexFormatToFloatCS( |
| contextMtl, convertedFormat.intendedAngleFormat(), params)); |
| } |
| else |
| { |
| ANGLE_TRY(utils.expandVertexFormatComponentsCS( |
| contextMtl, convertedFormat.intendedAngleFormat(), params)); |
| } |
| |
| ANGLE_TRY(conversion->bufferPool.commit(contextMtl)); |
| |
| conversion->buffer = std::move(newBuffer); |
| |
| return angle::Result::Continue; |
| } |
| } // namespace rx |