| // |
| // 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 "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" |
| |
| 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, |
| SimpleWeakBufferHolderMtl *bufferHolder, |
| size_t *bufferOffsetOut) |
| { |
| ANGLE_CHECK(contextMtl, vertexLoadFunction, "Unsupported format conversion", GL_INVALID_ENUM); |
| uint8_t *dst = nullptr; |
| mtl::BufferRef newBuffer; |
| ANGLE_TRY(dynamicBuffer->allocate(contextMtl, bytesToAllocate, &dst, &newBuffer, |
| bufferOffsetOut, nullptr)); |
| bufferHolder->set(newBuffer); |
| dst += destOffset; |
| vertexLoadFunction(sourceData, stride, vertexCount, dst); |
| |
| ANGLE_TRY(dynamicBuffer->commit(contextMtl)); |
| return angle::Result::Continue; |
| } |
| |
| size_t GetIndexConvertedBufferSize(gl::DrawElementsType indexType, size_t indexCount) |
| { |
| size_t elementSize = gl::GetDrawElementsTypeSize(indexType); |
| if (indexType == gl::DrawElementsType::UnsignedByte) |
| { |
| // 8-bit indices are not supported by Metal, so they are promoted to |
| // 16-bit indices below |
| elementSize = sizeof(GLushort); |
| } |
| |
| const size_t amount = elementSize * indexCount; |
| |
| return amount; |
| } |
| |
| angle::Result StreamIndexData(ContextMtl *contextMtl, |
| mtl::BufferPool *dynamicBuffer, |
| const uint8_t *sourcePointer, |
| gl::DrawElementsType indexType, |
| size_t indexCount, |
| mtl::BufferRef *bufferOut, |
| size_t *bufferOffsetOut) |
| { |
| dynamicBuffer->releaseInFlightBuffers(contextMtl); |
| |
| const size_t amount = GetIndexConvertedBufferSize(indexType, indexCount); |
| GLubyte *dst = nullptr; |
| |
| ANGLE_TRY( |
| dynamicBuffer->allocate(contextMtl, amount, &dst, bufferOut, bufferOffsetOut, nullptr)); |
| |
| if (indexType == gl::DrawElementsType::UnsignedByte) |
| { |
| // Unsigned bytes don't have direct support in Metal so we have to expand the |
| // memory to a GLushort. |
| const GLubyte *in = static_cast<const GLubyte *>(sourcePointer); |
| GLushort *expandedDst = reinterpret_cast<GLushort *>(dst); |
| |
| // NOTE(hqle): May need to handle primitive restart index in future when ES 3.0 |
| // is supported. |
| // Fast path for common case. |
| for (size_t index = 0; index < indexCount; index++) |
| { |
| expandedDst[index] = static_cast<GLushort>(in[index]); |
| } |
| } |
| else |
| { |
| memcpy(dst, sourcePointer, amount); |
| } |
| ANGLE_TRY(dynamicBuffer->commit(contextMtl)); |
| |
| return angle::Result::Continue; |
| } |
| |
| 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; |
| } |
| |
| inline size_t GetIndexCount(BufferMtl *srcBuffer, size_t offset, gl::DrawElementsType indexType) |
| { |
| size_t elementSize = gl::GetDrawElementsTypeSize(indexType); |
| return (srcBuffer->size() - offset) / elementSize; |
| } |
| |
| inline void SetDefaultVertexBufferLayout(mtl::VertexBufferLayoutDesc *layout) |
| { |
| layout->stepFunction = MTLVertexStepFunctionConstant; |
| layout->stepRate = 0; |
| layout->stride = 0; |
| } |
| |
| } // namespace |
| |
| // VertexArrayMtl implementation |
| VertexArrayMtl::VertexArrayMtl(const gl::VertexArrayState &state, ContextMtl *context) |
| : VertexArrayImpl(state), |
| // Due to Metal's strict requirement for offset and stride, we need to always allocate new |
| // buffer for every conversion. |
| mDynamicVertexData(true) |
| { |
| for (BufferHolderMtl *&buffer : mCurrentArrayBuffers) |
| { |
| buffer = nullptr; |
| } |
| for (size_t &offset : mCurrentArrayBufferOffsets) |
| { |
| offset = 0; |
| } |
| for (GLuint &stride : mCurrentArrayBufferStrides) |
| { |
| stride = 0; |
| } |
| for (MTLVertexFormat &format : mCurrentArrayBufferFormats) |
| { |
| format = MTLVertexFormatFloat4; |
| } |
| |
| mDynamicVertexData.initialize(context, 0, mtl::kVertexAttribBufferStrideAlignment, |
| mtl::kMaxVertexAttribs); |
| |
| mDynamicIndexData.initialize(context, kDynamicIndexDataSize, mtl::kIndexBufferOffsetAlignment); |
| } |
| VertexArrayMtl::~VertexArrayMtl() {} |
| |
| void VertexArrayMtl::destroy(const gl::Context *context) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| |
| for (BufferHolderMtl *&buffer : mCurrentArrayBuffers) |
| { |
| buffer = nullptr; |
| } |
| for (size_t &offset : mCurrentArrayBufferOffsets) |
| { |
| offset = 0; |
| } |
| for (GLuint &stride : mCurrentArrayBufferStrides) |
| { |
| stride = 0; |
| } |
| for (MTLVertexFormat &format : mCurrentArrayBufferFormats) |
| { |
| format = MTLVertexFormatInvalid; |
| } |
| |
| mVertexArrayDirty = true; |
| |
| mDynamicVertexData.destroy(contextMtl); |
| mDynamicIndexData.destroy(contextMtl); |
| } |
| |
| 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 (size_t dirtyBit : dirtyBits) |
| { |
| switch (dirtyBit) |
| { |
| case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER: |
| case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA: |
| { |
| 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)); \ |
| mVertexArrayDirty = 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. 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) |
| { |
| bool dirty = mVertexArrayDirty || *vertexDescChanged; |
| |
| if (dirty) |
| { |
| mVertexArrayDirty = false; |
| |
| 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 (uint32_t b = 0; b < mtl::kMaxVertexAttribs; ++b) |
| { |
| SetDefaultVertexBufferLayout(&desc.layouts[b]); |
| } |
| |
| for (uint32_t v = 0; v < mtl::kMaxVertexAttribs; ++v) |
| { |
| const auto &attrib = attribs[v]; |
| const gl::VertexBinding &binding = bindings[attrib.bindingIndex]; |
| |
| desc.attributes[v].offset = mCurrentArrayBufferOffsets[v]; |
| desc.attributes[v].format = mCurrentArrayBufferFormats[v]; |
| |
| bool attribEnabled = attrib.enabled; |
| if (attribEnabled && !mCurrentArrayBuffers[v]) |
| { |
| // Disable it to avoid crash. |
| attribEnabled = false; |
| } |
| |
| if (attribEnabled) |
| { |
| uint32_t bufferIdx = mtl::kVboBindingIndexStart + v; |
| desc.attributes[v].bufferIndex = bufferIdx; |
| |
| ASSERT(bufferIdx < mtl::kMaxVertexAttribs); |
| if (binding.getDivisor() == 0) |
| { |
| desc.layouts[bufferIdx].stepFunction = MTLVertexStepFunctionPerVertex; |
| desc.layouts[bufferIdx].stepRate = 1; |
| } |
| else |
| { |
| desc.layouts[bufferIdx].stepFunction = MTLVertexStepFunctionPerInstance; |
| desc.layouts[bufferIdx].stepRate = binding.getDivisor(); |
| } |
| desc.layouts[bufferIdx].stride = mCurrentArrayBufferStrides[v]; |
| |
| cmdEncoder->setVertexBuffer(mCurrentArrayBuffers[v]->getCurrentBuffer(glContext), 0, |
| bufferIdx); |
| } |
| else |
| { |
| desc.attributes[v].bufferIndex = mtl::kDefaultAttribsBindingIndex; |
| desc.attributes[v].offset = v * mtl::kDefaultAttributeSize; |
| } |
| } |
| } |
| |
| *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->getStateCache().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 && binding.getBuffer().get() == nullptr); |
| |
| const mtl::VertexFormat &vertexFormat = |
| contextMtl->getVertexFormat(attrib.format->id, true); |
| GLuint stride = vertexFormat.actualAngleFormat().pixelBytes; |
| |
| 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()); |
| } |
| // 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) * stride; |
| src += startElement * binding.getStride(); |
| size_t destOffset = startElement * stride; |
| |
| ANGLE_TRY(StreamVertexData( |
| contextMtl, &mDynamicVertexData, src, bytesToAllocate, destOffset, elementCount, |
| binding.getStride(), vertexFormat.vertexLoadFunction, |
| &mConvertedArrayBufferHolders[attribIndex], &mCurrentArrayBufferOffsets[attribIndex])); |
| |
| mCurrentArrayBuffers[attribIndex] = &mConvertedArrayBufferHolders[attribIndex]; |
| mCurrentArrayBufferFormats[attribIndex] = vertexFormat.metalFormat; |
| mCurrentArrayBufferStrides[attribIndex] = stride; |
| } |
| |
| 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); |
| |
| if (attrib.enabled) |
| { |
| gl::Buffer *bufferGL = binding.getBuffer().get(); |
| const mtl::VertexFormat &format = contextMtl->getVertexFormat(attrib.format->id, false); |
| |
| if (bufferGL) |
| { |
| BufferMtl *bufferMtl = mtl::GetImpl(bufferGL); |
| bool needConversion = |
| format.actualFormatId != format.intendedFormatId || |
| (binding.getOffset() % mtl::kVertexAttribBufferOffsetAlignment) != 0 || |
| (binding.getStride() % mtl::kVertexAttribBufferStrideAlignment) != 0 || |
| // This is Metal requirement: |
| (format.actualAngleFormat().pixelBytes + binding.getOffset() > binding.getStride()); |
| |
| 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.metalFormat; |
| } |
| } |
| else |
| { |
| // ContextMtl must feed the client data using updateClientAttribs() |
| } |
| } |
| else |
| { |
| // Tell ContextMtl to update default attribute value |
| contextMtl->invalidateDefaultAttribute(attribIndex); |
| |
| mCurrentArrayBuffers[attribIndex] = nullptr; |
| mCurrentArrayBufferOffsets[attribIndex] = 0; |
| mCurrentArrayBufferStrides[attribIndex] = 0; |
| // NOTE(hqle): We only support ES 2.0 atm. So default attribute type should always |
| // be float. |
| mCurrentArrayBufferFormats[attribIndex] = MTLVertexFormatFloat4; |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result VertexArrayMtl::getIndexBuffer(const gl::Context *context, |
| gl::DrawElementsType type, |
| size_t count, |
| const void *indices, |
| mtl::BufferRef *idxBufferOut, |
| size_t *idxBufferOffsetOut, |
| gl::DrawElementsType *indexTypeOut) |
| { |
| const gl::Buffer *glElementArrayBuffer = getState().getElementArrayBuffer(); |
| |
| size_t convertedOffset = reinterpret_cast<size_t>(indices); |
| if (!glElementArrayBuffer) |
| { |
| ANGLE_TRY(streamIndexBufferFromClient(context, type, count, indices, idxBufferOut, |
| idxBufferOffsetOut)); |
| } |
| else |
| { |
| bool needConversion = type == gl::DrawElementsType::UnsignedByte || |
| (convertedOffset % mtl::kIndexBufferOffsetAlignment) != 0; |
| if (needConversion) |
| { |
| ANGLE_TRY(convertIndexBuffer(context, type, convertedOffset, idxBufferOut, |
| idxBufferOffsetOut)); |
| } |
| else |
| { |
| // No conversion needed: |
| BufferMtl *bufferMtl = mtl::GetImpl(glElementArrayBuffer); |
| *idxBufferOut = bufferMtl->getCurrentBuffer(context); |
| *idxBufferOffsetOut = convertedOffset; |
| } |
| } |
| |
| *indexTypeOut = type; |
| if (type == gl::DrawElementsType::UnsignedByte) |
| { |
| // This buffer is already converted to ushort indices above |
| *indexTypeOut = gl::DrawElementsType::UnsignedShort; |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result VertexArrayMtl::convertIndexBuffer(const gl::Context *glContext, |
| gl::DrawElementsType indexType, |
| size_t offset, |
| mtl::BufferRef *idxBufferOut, |
| size_t *idxBufferOffsetOut) |
| { |
| ASSERT((offset % mtl::kIndexBufferOffsetAlignment) != 0 || |
| indexType == gl::DrawElementsType::UnsignedByte); |
| |
| BufferMtl *idxBuffer = mtl::GetImpl(getState().getElementArrayBuffer()); |
| |
| IndexConversionBufferMtl *conversion = |
| idxBuffer->getIndexConversionBuffer(glContext, indexType, offset); |
| |
| // Has the content of the buffer has changed since last conversion? |
| if (!conversion->dirty) |
| { |
| // reuse the converted buffer |
| *idxBufferOut = conversion->convertedBuffer; |
| *idxBufferOffsetOut = conversion->convertedOffset; |
| return angle::Result::Continue; |
| } |
| |
| size_t indexCount = GetIndexCount(idxBuffer, offset, indexType); |
| |
| ANGLE_TRY( |
| convertIndexBufferGPU(glContext, indexType, idxBuffer, offset, indexCount, conversion)); |
| |
| *idxBufferOut = conversion->convertedBuffer; |
| *idxBufferOffsetOut = conversion->convertedOffset; |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result VertexArrayMtl::convertIndexBufferGPU(const gl::Context *glContext, |
| gl::DrawElementsType indexType, |
| BufferMtl *idxBuffer, |
| size_t offset, |
| size_t indexCount, |
| IndexConversionBufferMtl *conversion) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(glContext); |
| DisplayMtl *display = contextMtl->getDisplay(); |
| |
| const size_t amount = GetIndexConvertedBufferSize(indexType, indexCount); |
| |
| // Allocate new buffer, save it in conversion struct so that we can reuse it when the content |
| // of the original buffer is not dirty. |
| conversion->data.releaseInFlightBuffers(contextMtl); |
| ANGLE_TRY(conversion->data.allocate(contextMtl, amount, nullptr, &conversion->convertedBuffer, |
| &conversion->convertedOffset)); |
| |
| // Do the conversion on GPU. |
| ANGLE_TRY(display->getUtils().convertIndexBuffer( |
| glContext, indexType, static_cast<uint32_t>(indexCount), |
| idxBuffer->getCurrentBuffer(glContext), static_cast<uint32_t>(offset), |
| conversion->convertedBuffer, static_cast<uint32_t>(conversion->convertedOffset))); |
| |
| ANGLE_TRY(conversion->data.commit(contextMtl)); |
| |
| ASSERT(conversion->dirty); |
| conversion->dirty = false; |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result VertexArrayMtl::streamIndexBufferFromClient(const gl::Context *context, |
| gl::DrawElementsType indexType, |
| size_t indexCount, |
| const void *sourcePointer, |
| mtl::BufferRef *idxBufferOut, |
| size_t *idxBufferOffsetOut) |
| { |
| ASSERT(getState().getElementArrayBuffer() == nullptr); |
| ContextMtl *contextMtl = mtl::GetImpl(context); |
| |
| auto srcData = static_cast<const uint8_t *>(sourcePointer); |
| ANGLE_TRY(StreamIndexData(contextMtl, &mDynamicIndexData, srcData, indexType, indexCount, |
| idxBufferOut, idxBufferOffsetOut)); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result VertexArrayMtl::convertVertexBuffer(const gl::Context *glContext, |
| BufferMtl *srcBuffer, |
| const gl::VertexBinding &binding, |
| size_t attribIndex, |
| const mtl::VertexFormat &vertexFormat) |
| { |
| const angle::Format &intendedAngleFormat = vertexFormat.intendedAngleFormat(); |
| |
| ConversionBufferMtl *conversion = srcBuffer->getVertexConversionBuffer( |
| glContext, intendedAngleFormat.id, binding.getStride(), binding.getOffset()); |
| |
| // Has the content of the buffer has changed since last conversion? |
| if (!conversion->dirty) |
| { |
| return angle::Result::Continue; |
| } |
| |
| // NOTE(hqle): Do the conversion on GPU. |
| return convertVertexBufferCPU(glContext, srcBuffer, binding, attribIndex, vertexFormat, |
| conversion); |
| } |
| |
| angle::Result VertexArrayMtl::convertVertexBufferCPU(const gl::Context *glContext, |
| BufferMtl *srcBuffer, |
| const gl::VertexBinding &binding, |
| size_t attribIndex, |
| const mtl::VertexFormat &srcVertexFormat, |
| ConversionBufferMtl *conversion) |
| { |
| ContextMtl *contextMtl = mtl::GetImpl(glContext); |
| |
| // Convert to tightly packed format |
| const mtl::VertexFormat &vertexFormat = |
| contextMtl->getVertexFormat(srcVertexFormat.intendedFormatId, true); |
| unsigned srcFormatSize = vertexFormat.intendedAngleFormat().pixelBytes; |
| unsigned dstFormatSize = vertexFormat.actualAngleFormat().pixelBytes; |
| |
| conversion->data.releaseInFlightBuffers(contextMtl); |
| |
| size_t numVertices = GetVertexCount(srcBuffer, binding, srcFormatSize); |
| if (numVertices == 0) |
| { |
| return angle::Result::Continue; |
| } |
| |
| const uint8_t *srcBytes = srcBuffer->getClientShadowCopyData(glContext); |
| ANGLE_CHECK_GL_ALLOC(contextMtl, srcBytes); |
| |
| srcBytes += binding.getOffset(); |
| |
| ANGLE_TRY(StreamVertexData(contextMtl, &conversion->data, srcBytes, numVertices * dstFormatSize, |
| 0, numVertices, binding.getStride(), vertexFormat.vertexLoadFunction, |
| &mConvertedArrayBufferHolders[attribIndex], |
| &mCurrentArrayBufferOffsets[attribIndex])); |
| |
| mCurrentArrayBuffers[attribIndex] = &mConvertedArrayBufferHolders[attribIndex]; |
| mCurrentArrayBufferFormats[attribIndex] = vertexFormat.metalFormat; |
| mCurrentArrayBufferStrides[attribIndex] = dstFormatSize; |
| |
| ASSERT(conversion->dirty); |
| conversion->dirty = false; |
| |
| return angle::Result::Continue; |
| } |
| } |