blob: 7dcc534136f36f5edf281084caf13171deefd051 [file] [edit]
//
// 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