blob: ba0c505618dab13fe989bb8524a20dc5c54d6369 [file] [log] [blame]
//
// 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/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/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,
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;
}
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;
}
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,
bool primitiveRestartEnabled,
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);
if (primitiveRestartEnabled)
{
for (size_t index = 0; index < indexCount; index++)
{
if (in[index] == 0xFF)
{
expandedDst[index] = 0xFFFF;
}
else
{
expandedDst[index] = static_cast<GLushort>(in[index]);
}
}
} // if (primitiveRestartEnabled)
else
{
for (size_t index = 0; index < indexCount; index++)
{
expandedDst[index] = static_cast<GLushort>(in[index]);
}
} // if (primitiveRestartEnabled)
}
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;
}
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<GLintptr>(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 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 = mtl::kVertexStepFunctionInvalid;
layout->stepRate = 0;
layout->stride = 0;
}
} // namespace
// VertexArrayMtl implementation
VertexArrayMtl::VertexArrayMtl(const gl::VertexArrayState &state, ContextMtl *context)
: VertexArrayImpl(state),
mDefaultFloatVertexFormat(
context->getVertexFormat(angle::FormatID::R32G32B32A32_FLOAT, false)),
mDefaultIntVertexFormat(context->getVertexFormat(angle::FormatID::R32G32B32A32_SINT, false)),
mDefaultUIntVertexFormat(context->getVertexFormat(angle::FormatID::R32G32B32A32_UINT, 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 (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;
}
ANGLE_INLINE void VertexArrayMtl::getVertexAttribFormatAndArraySize(const sh::ShaderVariable &var,
MTLVertexFormat *formatOut,
uint32_t *arraySizeOut)
{
uint32_t arraySize = var.getArraySizeProduct();
MTLVertexFormat format;
switch (var.type)
{
case GL_INT:
case GL_INT_VEC2:
case GL_INT_VEC3:
case GL_INT_VEC4:
format = mDefaultIntVertexFormat.metalFormat;
break;
case GL_UNSIGNED_INT:
case GL_UNSIGNED_INT_VEC2:
case GL_UNSIGNED_INT_VEC3:
case GL_UNSIGNED_INT_VEC4:
format = mDefaultUIntVertexFormat.metalFormat;
break;
case GL_FLOAT_MAT2:
case GL_FLOAT_MAT2x3:
case GL_FLOAT_MAT2x4:
arraySize *= 2;
format = mDefaultFloatVertexFormat.metalFormat;
break;
case GL_FLOAT_MAT3:
case GL_FLOAT_MAT3x2:
case GL_FLOAT_MAT3x4:
arraySize *= 3;
format = mDefaultFloatVertexFormat.metalFormat;
break;
case GL_FLOAT_MAT4:
case GL_FLOAT_MAT4x2:
case GL_FLOAT_MAT4x3:
arraySize *= 4;
format = mDefaultFloatVertexFormat.metalFormat;
break;
default:
format = mDefaultFloatVertexFormat.metalFormat;
}
*arraySizeOut = arraySize;
*formatOut = format;
}
// 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 gl::ProgramState &programState = glContext->getState().getProgram()->getState();
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)
{
if (!programActiveAttribsMask.test(v))
{
desc.attributes[v].format = MTLVertexFormatInvalid;
desc.attributes[v].bufferIndex = 0;
desc.attributes[v].offset = 0;
continue;
}
const auto &attrib = attribs[v];
const gl::VertexBinding &binding = bindings[attrib.bindingIndex];
bool attribEnabled = attrib.enabled;
if (attribEnabled && !mCurrentArrayBuffers[v] && !mCurrentArrayInlineDataPointers[v])
{
// Disable it to avoid crash.
attribEnabled = false;
}
if (!attribEnabled)
{
// Use default attribute
// Need to find the attribute having the exact binding location = v in the program
// inputs list to retrieve its coresponding data type:
const std::vector<sh::ShaderVariable> &programInputs =
programState.getProgramInputs();
std::vector<sh::ShaderVariable>::const_iterator attribInfoIte = std::find_if(
begin(programInputs), end(programInputs), [v](const sh::ShaderVariable &sv) {
return static_cast<uint32_t>(sv.location) == v;
});
if (attribInfoIte == end(programInputs))
{
// Most likely this is array element with index > 0.
// Already handled when encounter first element.
continue;
}
uint32_t arraySize;
MTLVertexFormat format;
getVertexAttribFormatAndArraySize(*attribInfoIte, &format, &arraySize);
for (uint32_t vaIdx = v; vaIdx < v + arraySize; ++vaIdx)
{
desc.attributes[vaIdx].bufferIndex = mtl::kDefaultAttribsBindingIndex;
desc.attributes[vaIdx].offset = vaIdx * mtl::kDefaultAttributeSize;
desc.attributes[vaIdx].format = format;
}
}
else
{
uint32_t bufferIdx = mtl::kVboBindingIndexStart + v;
uint32_t bufferOffset = static_cast<uint32_t>(mCurrentArrayBufferOffsets[v]);
const angle::Format &angleFormat =
mCurrentArrayBufferFormats[v]->actualAngleFormat();
desc.attributes[v].format = mCurrentArrayBufferFormats[v]->metalFormat;
desc.attributes[v].bufferIndex = bufferIdx;
desc.attributes[v].offset = 0;
ASSERT((bufferOffset % angleFormat.pixelBytes) == 0);
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];
if (mCurrentArrayBuffers[v])
{
cmdEncoder->setVertexBuffer(mCurrentArrayBuffers[v]->getCurrentBuffer(),
bufferOffset, bufferIdx);
}
else
{
// No buffer specified, use the client memory directly as inline constant data
ASSERT(mCurrentArrayInlineDataSizes[v] <= mInlineDataMaxSize);
cmdEncoder->setVertexBytes(mCurrentArrayInlineDataPointers[v],
mCurrentArrayInlineDataSizes[v], bufferIdx);
}
}
} // for (v)
}
*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);
// 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;
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,
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);
ANGLE_TRY(StreamVertexData(contextMtl, &mDynamicVertexData, src, bytesToAllocate,
destOffset, elementCount, binding.getStride(),
streamFormat.vertexLoadFunction,
&mConvertedArrayBufferHolders[attribIndex],
&mCurrentArrayBufferOffsets[attribIndex]));
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);
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() % format.actualAngleFormat().pixelBytes) != 0 ||
(binding.getOffset() % mtl::kVertexAttribBufferStrideAlignment) != 0 ||
(binding.getStride() < format.actualAngleFormat().pixelBytes) ||
(binding.getStride() % mtl::kVertexAttribBufferStrideAlignment) != 0;
if (needConversion)
{
mContentsObservers->enableForBuffer(bufferGL, static_cast<uint32_t>(attribIndex));
ANGLE_TRY(convertVertexBuffer(glContext, bufferMtl, binding, attribIndex, format));
}
else
{
mContentsObservers->disableForBuffer(bufferGL, static_cast<uint32_t>(attribIndex));
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;
}
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;
if (needConversion)
{
ANGLE_TRY(convertIndexBuffer(context, type, convertedOffset, idxBufferOut,
idxBufferOffsetOut));
}
else
{
// No conversion needed:
BufferMtl *bufferMtl = mtl::GetImpl(glElementArrayBuffer);
*idxBufferOut = bufferMtl->getCurrentBuffer();
*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;
}
std::vector<DrawCommandRange> VertexArrayMtl::getDrawIndices(const gl::Context *glContext,
gl::DrawElementsType originalIndexType,
gl::DrawElementsType indexType,
gl::PrimitiveMode primitiveMode,
mtl::BufferRef clientBuffer,
uint32_t indexCount,
size_t offset)
{
ContextMtl *contextMtl = mtl::GetImpl(glContext);
std::vector<DrawCommandRange> drawCommands;
// The indexed draw needs to be split to separate draw commands in case primitive restart is
// enabled and the drawn primitive supports primitive restart. Otherwise the whole indexed draw
// can be sent as one draw command.
bool isSimpleType = primitiveMode == gl::PrimitiveMode::Points ||
primitiveMode == gl::PrimitiveMode::Lines ||
primitiveMode == gl::PrimitiveMode::Triangles;
if (!isSimpleType || !glContext->getState().isPrimitiveRestartEnabled())
{
drawCommands.push_back({indexCount, offset});
return drawCommands;
}
const std::vector<IndexRange> *restartIndices;
std::vector<IndexRange> clientIndexRange;
const gl::Buffer *glElementArrayBuffer = getState().getElementArrayBuffer();
if (glElementArrayBuffer)
{
BufferMtl *idxBuffer = mtl::GetImpl(glElementArrayBuffer);
restartIndices = &idxBuffer->getRestartIndices(contextMtl, originalIndexType);
}
else
{
clientIndexRange =
BufferMtl::getRestartIndicesFromClientData(contextMtl, indexType, clientBuffer);
restartIndices = &clientIndexRange;
}
// Reminder, offset is in bytes, not elements.
// Slice draw commands based off of indices.
uint32_t nIndicesPerPrimitive;
switch (primitiveMode)
{
case gl::PrimitiveMode::Points:
nIndicesPerPrimitive = 1;
break;
case gl::PrimitiveMode::Lines:
nIndicesPerPrimitive = 2;
break;
case gl::PrimitiveMode::Triangles:
nIndicesPerPrimitive = 3;
break;
default:
UNREACHABLE();
return drawCommands;
}
const GLuint indexTypeBytes = gl::GetDrawElementsTypeSize(indexType);
uint32_t indicesLeft = indexCount;
size_t currentIndexOffset = offset / indexTypeBytes;
for (auto &range : *restartIndices)
{
if (range.restartBegin > currentIndexOffset)
{
int64_t nIndicesInSlice =
MIN(((int64_t)range.restartBegin - currentIndexOffset) -
((int64_t)range.restartBegin - currentIndexOffset) % nIndicesPerPrimitive,
indicesLeft);
size_t restartSize = (range.restartEnd - range.restartBegin) + 1;
if (nIndicesInSlice >= nIndicesPerPrimitive)
{
drawCommands.push_back(
{(uint32_t)nIndicesInSlice, currentIndexOffset * indexTypeBytes});
}
// Account for dropped indices due to incomplete primitives.
size_t indicesUsed = ((range.restartBegin + restartSize) - currentIndexOffset);
if (indicesLeft <= indicesUsed)
{
indicesLeft = 0;
}
else
{
indicesLeft -= indicesUsed;
}
currentIndexOffset = (size_t)(range.restartBegin + restartSize);
}
// If the initial offset into the index buffer is within a restart zone, move to the end of
// the restart zone.
else if (range.restartEnd >= currentIndexOffset)
{
size_t restartSize = (range.restartEnd - currentIndexOffset) + 1;
if (indicesLeft <= restartSize)
{
indicesLeft = 0;
}
else
{
indicesLeft -= restartSize;
}
currentIndexOffset = (size_t)(currentIndexOffset + restartSize);
}
}
if (indicesLeft >= nIndicesPerPrimitive)
drawCommands.push_back({indicesLeft, currentIndexOffset * indexTypeBytes});
return drawCommands;
}
angle::Result VertexArrayMtl::convertIndexBuffer(const gl::Context *glContext,
gl::DrawElementsType indexType,
size_t offset,
mtl::BufferRef *idxBufferOut,
size_t *idxBufferOffsetOut)
{
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(getState().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
*idxBufferOut = conversion->convertedBuffer;
*idxBufferOffsetOut = conversion->convertedOffset + alignedOffset;
return angle::Result::Continue;
}
size_t indexCount = GetIndexCount(idxBuffer, offsetModulo, indexType);
if ((!contextMtl->getDisplay()->getFeatures().hasCheapRenderPass.enabled &&
contextMtl->getRenderCommandEncoder()))
{
// We shouldn't use GPU to convert when we are in a middle of a render pass.
ANGLE_TRY(StreamIndexData(contextMtl, &conversion->data,
idxBuffer->getClientShadowCopyData(contextMtl) + offsetModulo,
indexType, indexCount, glState.isPrimitiveRestartEnabled(),
&conversion->convertedBuffer, &conversion->convertedOffset));
}
else
{
ANGLE_TRY(convertIndexBufferGPU(glContext, indexType, idxBuffer, offsetModulo, indexCount,
conversion));
}
// Calculate ranges for prim restart simple types.
*idxBufferOut = conversion->convertedBuffer;
*idxBufferOffsetOut = conversion->convertedOffset + alignedOffset;
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().convertIndexBufferGPU(
contextMtl, {indexType, static_cast<uint32_t>(indexCount), idxBuffer->getCurrentBuffer(),
static_cast<uint32_t>(offset), conversion->convertedBuffer,
static_cast<uint32_t>(conversion->convertedOffset),
glContext->getState().isPrimitiveRestartEnabled()}));
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,
context->getState().isPrimitiveRestartEnabled(), 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 &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->convertedBuffer);
mCurrentArrayBufferOffsets[attribIndex] =
conversion->convertedOffset +
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;
if (contextMtl->getRenderCommandEncoder() &&
!contextMtl->getDisplay()->getFeatures().hasCheapRenderPass.enabled &&
!contextMtl->getDisplay()->getFeatures().hasExplicitMemBarrier.enabled)
{
// Cannot use GPU to convert when we are in a middle of a render pass.
canConvertToFloatOnGPU = canExpandComponentsOnGPU = false;
}
conversion->data.releaseInFlightBuffers(contextMtl);
conversion->data.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->convertedBuffer);
mCurrentArrayBufferOffsets[attribIndex] =
conversion->convertedOffset +
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)
{
const uint8_t *srcBytes = srcBuffer->getClientShadowCopyData(contextMtl);
ANGLE_CHECK_GL_ALLOC(contextMtl, srcBytes);
VertexConversionBufferMtl *vertexConverison =
static_cast<VertexConversionBufferMtl *>(conversion);
srcBytes += MIN(binding.getOffset(), static_cast<GLintptr>(vertexConverison->offset));
SimpleWeakBufferHolderMtl conversionBufferHolder;
ANGLE_TRY(StreamVertexData(contextMtl, &conversion->data, srcBytes, numVertices * targetStride,
0, numVertices, binding.getStride(),
convertedFormat.vertexLoadFunction, &conversionBufferHolder,
&conversion->convertedOffset));
conversion->convertedBuffer = conversionBufferHolder.getCurrentBuffer();
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::BufferRef newBuffer;
size_t newBufferOffset;
ANGLE_TRY(conversion->data.allocate(contextMtl, numVertices * targetStride, nullptr, &newBuffer,
&newBufferOffset));
ANGLE_CHECK_GL_MATH(contextMtl, binding.getOffset() <= std::numeric_limits<uint32_t>::max());
ANGLE_CHECK_GL_MATH(contextMtl, newBufferOffset <= 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<GLintptr>(vertexConversion->offset), binding.getOffset()));
params.srcStride = binding.getStride();
params.srcDefaultAlphaData = convertedFormat.defaultAlpha;
params.dstBuffer = newBuffer;
params.dstBufferStartOffset = static_cast<uint32_t>(newBufferOffset);
params.dstStride = targetStride;
params.dstComponents = convertedFormat.actualAngleFormat().channelCount;
params.vertexCount = static_cast<uint32_t>(numVertices);
mtl::RenderUtils &utils = contextMtl->getDisplay()->getUtils();
mtl::RenderCommandEncoder *renderEncoder = contextMtl->getRenderCommandEncoder();
if (renderEncoder && contextMtl->getDisplay()->getFeatures().hasExplicitMemBarrier.enabled)
{
// If we are in the middle of a render pass, use vertex shader based buffer conversion to
// avoid breaking the render pass.
if (!isExpandingComponents)
{
ANGLE_TRY(utils.convertVertexFormatToFloatVS(
glContext, renderEncoder, convertedFormat.intendedAngleFormat(), params));
}
else
{
ANGLE_TRY(utils.expandVertexFormatComponentsVS(
glContext, renderEncoder, convertedFormat.intendedAngleFormat(), params));
}
}
else
{
// 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->data.commit(contextMtl));
conversion->convertedBuffer = newBuffer;
conversion->convertedOffset = newBufferOffset;
return angle::Result::Continue;
}
}