#include #include #include #include #include #include namespace mbgl { namespace mtl { namespace { // The `setVertexBytes:length:atIndex:` method is the best option for binding a very small // amount (less than 4 KB) of dynamic buffer data to a vertex function, as shown in Listing // 5-1. This method avoids the overhead of creating an intermediary MTLBuffer object. // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/BufferBindings.html constexpr auto bufferThreshold = 4096; } // namespace BufferResource::BufferResource(Context& context_, const void* data, std::size_t size_, MTL::ResourceOptions usage_, bool isIndexBuffer_, bool persistent_) : context(context_), size(static_cast(size_)), usage(usage_), isIndexBuffer(isIndexBuffer_), persistent(persistent_) { // If the buffer is small, not for indexes, and not explicitly persistent, skip the overhead of an `NSBuffer` if (size_ < bufferThreshold && !isIndexBuffer_ && !persistent) { if (data) { raw.assign(static_cast(data), static_cast(data) + size); } else { raw.resize(size); } assert(raw.size() == size); } else { auto& device = context.getBackend().getDevice(); buffer = NS::TransferPtr((data && size) ? device->newBuffer(data, size, usage) : device->newBuffer(size, usage)); } if (isValid()) { auto& stats = context.renderingStats(); stats.numBuffers++; stats.memBuffers += size; stats.totalBuffers++; if (buffer) { stats.totalBufferObjs++; } } } BufferResource::BufferResource(BufferResource&& other) noexcept : context(other.context), buffer(std::move(other.buffer)), raw(std::move(other.raw)), size(other.size), usage(other.usage), isIndexBuffer(other.isIndexBuffer), persistent(other.persistent) {} BufferResource::~BufferResource() noexcept { if (isValid()) { context.renderingStats().numBuffers--; context.renderingStats().memBuffers -= size; } } BufferResource BufferResource::clone() const { return {context, contents(), size, usage, isIndexBuffer, persistent}; } BufferResource& BufferResource::operator=(BufferResource&& other) noexcept { assert(&context == &other.context); if (isValid()) { context.renderingStats().numBuffers--; context.renderingStats().memBuffers -= size; } buffer = std::move(other.buffer); raw = std::move(other.raw); size = other.size; usage = other.usage; isIndexBuffer = other.isIndexBuffer; persistent = other.persistent; return *this; } void BufferResource::update(const void* newData, std::size_t updateSize, std::size_t offset) noexcept { assert(size >= 0 && updateSize + offset <= size); updateSize = std::min(updateSize, size - offset); if (updateSize <= 0) { return; } auto& stats = context.renderingStats(); if (buffer) { // Until we can be sure that the buffer is not still in use to render the // previous frame, replace it with a new buffer instead of updating it. auto& device = context.getBackend().getDevice(); const uint8_t* newBufferSource = nullptr; std::unique_ptr tempBuffer; // `[MTLBuffer contents]` may involve memory mapping and/or synchronization. If the entire // buffer is being replaced, avoid accessing the old one by creating the new buffer directly // from the given data. If it's just being updated, apply the update to a local buffer to // avoid needing to access the new buffer. const bool updateIsEntireBuffer = (offset == 0 && updateSize == size); if (updateIsEntireBuffer) { newBufferSource = static_cast(newData); } else { if (auto* const oldContent = static_cast(buffer->contents())) { tempBuffer.reset(new (std::nothrow) uint8_t[size]); assert(tempBuffer); if (tempBuffer) { memcpy(tempBuffer.get(), oldContent, size); memcpy(tempBuffer.get() + offset, newData, updateSize); newBufferSource = tempBuffer.get(); } } } if (newBufferSource) { auto newBuffer = NS::TransferPtr(device->newBuffer(newBufferSource, size, usage)); assert(newBuffer); if (newBuffer) { buffer = std::move(newBuffer); stats.totalBuffers++; stats.totalBufferObjs++; stats.bufferObjUpdates++; stats.bufferUpdateBytes += updateSize; } } } else { std::memcpy(raw.data() + offset, newData, updateSize); stats.bufferUpdateBytes += updateSize; } stats.bufferUpdates++; version++; } bool BufferResource::needReBind(VersionType version_) const noexcept { // If we're using a raw buffer, an update means we have to re-bind. // For a MTLBuffer, the binding can be left alone. return (version != version_); } void BufferResource::bindVertex(const MTLRenderCommandEncoderPtr& encoder, const std::size_t offset, const std::size_t index, std::size_t size_) const noexcept { assert(offset + size_ <= size); if (const auto* mtlBuf = buffer.get()) { encoder->setVertexBuffer(mtlBuf, static_cast(offset), static_cast(index)); } else if (!raw.empty()) { size_ = size_ ? std::min(size_, size - offset) : size - offset; encoder->setVertexBytes(raw.data() + offset, size_, index); } } void BufferResource::bindFragment(const MTLRenderCommandEncoderPtr& encoder, const std::size_t offset, const std::size_t index, std::size_t size_) const noexcept { assert(offset + size_ <= size); if (const auto* mtlBuf = buffer.get()) { encoder->setFragmentBuffer(mtlBuf, static_cast(offset), static_cast(index)); } else if (!raw.empty()) { size_ = size_ ? std::min(size_, size - offset) : size - offset; encoder->setFragmentBytes(raw.data() + offset, size_, index); } } void BufferResource::updateVertexBindOffset(const MTLRenderCommandEncoderPtr& encoder, std::size_t offset, std::size_t index, std::size_t size_) const noexcept { // If we're using a MTLBuffer, just update the offset. // The documentation for `setVertexBufferOffset` indicates that it should work for buffers // assigned using `setVertexBytes` but, in practice, it produces a validation failure: // `Set Vertex Buffer Offset Validation index(1) must have an existing buffer.` if (buffer.get()) { encoder->setVertexBufferOffset(offset, index); } else { bindVertex(encoder, offset, index, size_); } } void BufferResource::updateFragmentBindOffset(const MTLRenderCommandEncoderPtr& encoder, std::size_t offset, std::size_t index, std::size_t size_) const noexcept { if (buffer.get()) { encoder->setFragmentBufferOffset(offset, index); } else { bindFragment(encoder, offset, index, size_); } } } // namespace mtl } // namespace mbgl