blob: 9ab5639491f1f506149b2e3a01638e4539a973e3 [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.
//
// BufferMtl.mm:
// Implements the class methods for BufferMtl.
//
#include "libANGLE/renderer/metal/BufferMtl.h"
#include "common/debug.h"
#include "common/utilities.h"
#include "libANGLE/renderer/metal/ContextMtl.h"
namespace rx
{
namespace
{
// Start with a fairly small buffer size. We can increase this dynamically as we convert more data.
constexpr size_t kConvertedElementArrayBufferInitialSize = 1024 * 8;
template <typename IndexType>
angle::Result GetFirstLastIndices(const IndexType *indices,
size_t count,
std::pair<uint32_t, uint32_t> *outIndices)
{
IndexType first, last;
// Use memcpy to avoid unaligned memory access crash:
memcpy(&first, &indices[0], sizeof(first));
memcpy(&last, &indices[count - 1], sizeof(last));
outIndices->first = first;
outIndices->second = last;
return angle::Result::Continue;
}
} // namespace
// ConversionBufferMtl implementation.
ConversionBufferMtl::ConversionBufferMtl(const gl::Context *context,
size_t initialSize,
size_t alignment)
: dirty(true)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
data.initialize(contextMtl, initialSize, alignment);
}
ConversionBufferMtl::~ConversionBufferMtl() = default;
// IndexConversionBufferMtl implementation.
IndexConversionBufferMtl::IndexConversionBufferMtl(const gl::Context *context,
gl::DrawElementsType typeIn,
size_t offsetIn)
: ConversionBufferMtl(context,
kConvertedElementArrayBufferInitialSize,
mtl::kBufferSettingOffsetAlignment),
type(typeIn),
offset(offsetIn),
convertedBuffer(nullptr),
convertedOffset(0)
{}
// BufferMtl::VertexConversionBuffer implementation.
BufferMtl::VertexConversionBuffer::VertexConversionBuffer(const gl::Context *context,
angle::FormatID formatIDIn,
GLuint strideIn,
size_t offsetIn)
: ConversionBufferMtl(context, 0, mtl::kVertexAttribBufferStrideAlignment),
formatID(formatIDIn),
stride(strideIn),
offset(offsetIn)
{
// Due to Metal's strict requirement for offset and stride, we need to always allocate new
// buffer for every conversion.
data.setAlwaysAllocateNewBuffer(true);
}
// BufferMtl implementation
BufferMtl::BufferMtl(const gl::BufferState &state)
: BufferImpl(state), mBufferPool(/** alwaysAllocNewBuffer */ true)
{}
BufferMtl::~BufferMtl() {}
void BufferMtl::destroy(const gl::Context *context)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
mShadowCopy.resize(0);
mBufferPool.destroy(contextMtl);
mBuffer = nullptr;
}
angle::Result BufferMtl::setData(const gl::Context *context,
gl::BufferBinding target,
const void *data,
size_t size,
gl::BufferUsage usage)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
if (!mShadowCopy.size() || size > static_cast<size_t>(mState.getSize()) ||
usage != mState.getUsage())
{
if (size == 0)
{
size = 1;
}
// Re-create the buffer
markConversionBuffersDirty();
ANGLE_MTL_CHECK(contextMtl, mShadowCopy.resize(size), GL_OUT_OF_MEMORY);
if (data)
{
auto ptr = static_cast<const uint8_t *>(data);
std::copy(ptr, ptr + size, mShadowCopy.data());
}
size_t maxBuffers;
switch (usage)
{
case gl::BufferUsage::StaticCopy:
case gl::BufferUsage::StaticDraw:
case gl::BufferUsage::StaticRead:
maxBuffers = 1; // static buffer doesn't need high speed data update
break;
default:
// dynamic buffer, allow up to 2 update per frame/encoding without
// waiting for GPU.
maxBuffers = 2;
break;
}
mBufferPool.initialize(contextMtl, size, 1, maxBuffers);
return commitShadowCopy(context);
}
else
{
// update data only
return setSubData(context, target, data, size, 0);
}
}
angle::Result BufferMtl::setSubData(const gl::Context *context,
gl::BufferBinding target,
const void *data,
size_t size,
size_t offset)
{
return setSubDataImpl(context, data, size, offset);
}
angle::Result BufferMtl::copySubData(const gl::Context *context,
BufferImpl *source,
GLintptr sourceOffset,
GLintptr destOffset,
GLsizeiptr size)
{
if (!source)
{
return angle::Result::Continue;
}
ASSERT(mShadowCopy.size());
auto srcMtl = GetAs<BufferMtl>(source);
// NOTE(hqle): use blit command.
return setSubDataImpl(context, srcMtl->getClientShadowCopyData(context) + sourceOffset, size,
destOffset);
}
angle::Result BufferMtl::map(const gl::Context *context, GLenum access, void **mapPtr)
{
ASSERT(mShadowCopy.size());
return mapRange(context, 0, mState.getSize(), 0, mapPtr);
}
angle::Result BufferMtl::mapRange(const gl::Context *context,
size_t offset,
size_t length,
GLbitfield access,
void **mapPtr)
{
ASSERT(mShadowCopy.size());
// NOTE(hqle): use access flags
if (mapPtr)
{
*mapPtr = mShadowCopy.data() + offset;
}
return angle::Result::Continue;
}
angle::Result BufferMtl::unmap(const gl::Context *context, GLboolean *result)
{
ASSERT(mShadowCopy.size());
markConversionBuffersDirty();
ANGLE_TRY(commitShadowCopy(context));
return angle::Result::Continue;
}
angle::Result BufferMtl::getIndexRange(const gl::Context *context,
gl::DrawElementsType type,
size_t offset,
size_t count,
bool primitiveRestartEnabled,
gl::IndexRange *outRange)
{
ASSERT(mShadowCopy.size());
const uint8_t *indices = mShadowCopy.data() + offset;
*outRange = gl::ComputeIndexRange(type, indices, count, primitiveRestartEnabled);
return angle::Result::Continue;
}
angle::Result BufferMtl::getFirstLastIndices(const gl::Context *context,
gl::DrawElementsType type,
size_t offset,
size_t count,
std::pair<uint32_t, uint32_t> *outIndices) const
{
ASSERT(mShadowCopy.size());
const uint8_t *indices = mShadowCopy.data() + offset;
switch (type)
{
case gl::DrawElementsType::UnsignedByte:
return GetFirstLastIndices(static_cast<const GLubyte *>(indices), count, outIndices);
case gl::DrawElementsType::UnsignedShort:
return GetFirstLastIndices(reinterpret_cast<const GLushort *>(indices), count,
outIndices);
case gl::DrawElementsType::UnsignedInt:
return GetFirstLastIndices(reinterpret_cast<const GLuint *>(indices), count,
outIndices);
default:
UNREACHABLE();
return angle::Result::Stop;
}
return angle::Result::Continue;
}
const uint8_t *BufferMtl::getClientShadowCopyData(const gl::Context *context)
{
// NOTE(hqle): Support buffer update from GPU.
// Which mean we have to stall the GPU by calling finish and copy
// data back to shadow copy.
return mShadowCopy.data();
}
ConversionBufferMtl *BufferMtl::getVertexConversionBuffer(const gl::Context *context,
angle::FormatID formatID,
GLuint stride,
size_t offset)
{
for (VertexConversionBuffer &buffer : mVertexConversionBuffers)
{
if (buffer.formatID == formatID && buffer.stride == stride && buffer.offset == offset)
{
return &buffer;
}
}
mVertexConversionBuffers.emplace_back(context, formatID, stride, offset);
return &mVertexConversionBuffers.back();
}
IndexConversionBufferMtl *BufferMtl::getIndexConversionBuffer(const gl::Context *context,
gl::DrawElementsType type,
size_t offset)
{
for (auto &buffer : mIndexConversionBuffers)
{
if (buffer.type == type && buffer.offset == offset)
{
return &buffer;
}
}
mIndexConversionBuffers.emplace_back(context, type, offset);
return &mIndexConversionBuffers.back();
}
void BufferMtl::markConversionBuffersDirty()
{
for (VertexConversionBuffer &buffer : mVertexConversionBuffers)
{
buffer.dirty = true;
}
for (auto &buffer : mIndexConversionBuffers)
{
buffer.dirty = true;
buffer.convertedBuffer = nullptr;
buffer.convertedOffset = 0;
}
}
angle::Result BufferMtl::setSubDataImpl(const gl::Context *context,
const void *data,
size_t size,
size_t offset)
{
if (!data)
{
return angle::Result::Continue;
}
ContextMtl *contextMtl = mtl::GetImpl(context);
ASSERT(mShadowCopy.size());
ANGLE_MTL_TRY(contextMtl, offset <= this->size());
auto srcPtr = static_cast<const uint8_t *>(data);
auto sizeToCopy = std::min<size_t>(size, this->size() - offset);
std::copy(srcPtr, srcPtr + sizeToCopy, mShadowCopy.data() + offset);
markConversionBuffersDirty();
ANGLE_TRY(commitShadowCopy(context));
return angle::Result::Continue;
}
angle::Result BufferMtl::commitShadowCopy(const gl::Context *context)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
uint8_t *ptr = nullptr;
ANGLE_TRY(
mBufferPool.allocate(contextMtl, mShadowCopy.size(), &ptr, &mBuffer, nullptr, nullptr));
std::copy(mShadowCopy.data(), mShadowCopy.data() + mShadowCopy.size(), ptr);
ANGLE_TRY(mBufferPool.commit(contextMtl));
return angle::Result::Continue;
}
// SimpleWeakBufferHolderMtl implementation
SimpleWeakBufferHolderMtl::SimpleWeakBufferHolderMtl()
{
mIsWeak = true;
}
} // namespace rx