// // Copyright 2022 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. // // RingBufferAllocator.cpp: // Implements classes used for ring buffer allocators. // #include "common/RingBufferAllocator.h" namespace angle { // RingBufferAllocator implementation. RingBufferAllocator::RingBufferAllocator(RingBufferAllocator &&other) { *this = std::move(other); } RingBufferAllocator &RingBufferAllocator::operator=(RingBufferAllocator &&other) { mOldBuffers = std::move(other.mOldBuffers); mBuffer = std::move(other.mBuffer); mDataBegin = other.mDataBegin; mDataEnd = other.mDataEnd; mFragmentEnd = other.mFragmentEnd; mFragmentEndR = other.mFragmentEndR; mFragmentReserve = other.mFragmentReserve; mMinCapacity = other.mMinCapacity; mCurrentCapacity = other.mCurrentCapacity; mAllocationMargin = other.mAllocationMargin; mDecaySpeedFactor = other.mDecaySpeedFactor; ASSERT(other.mOldBuffers.size() == 0); ASSERT(other.mBuffer.isEmpty()); other.mBuffer.resetId(); other.mDataBegin = nullptr; other.mDataEnd = nullptr; other.mFragmentEnd = nullptr; other.mFragmentEndR = nullptr; other.mFragmentReserve = 0; other.mMinCapacity = 0; other.mCurrentCapacity = 0; other.mAllocationMargin = 0; other.mDecaySpeedFactor = 0; return *this; } void RingBufferAllocator::reset() { mListener = nullptr; mFragmentReserve = 0; mDecaySpeedFactor = kDefaultDecaySpeedFactor; mMinCapacity = kMinRingBufferAllocationCapacity; resize(mMinCapacity); mOldBuffers.clear(); } void RingBufferAllocator::setListener(RingBufferAllocateListener *listener) { ASSERT(!mListener || !listener); mListener = listener; } void RingBufferAllocator::setDecaySpeedFactor(uint32_t decaySpeedFactor) { ASSERT(valid()); mDecaySpeedFactor = std::max(decaySpeedFactor, 1u); } RingBufferAllocatorCheckPoint RingBufferAllocator::getReleaseCheckPoint() const { ASSERT(valid()); RingBufferAllocatorCheckPoint result; result.mBufferId = mBuffer.getId(); result.mReleasePtr = mDataEnd; return result; } void RingBufferAllocator::release(const RingBufferAllocatorCheckPoint &checkPoint) { ASSERT(valid()); ASSERT(checkPoint.valid()); if (mOldBuffers.size() > 0) { // mOldBuffers are sorted by id int removeCount = 0; for (uint32_t i = 0; (i < mOldBuffers.size()) && (mOldBuffers[i].getId() < checkPoint.mBufferId); ++i) { ++removeCount; } mOldBuffers.erase(mOldBuffers.begin(), mOldBuffers.begin() + removeCount); } if (checkPoint.mBufferId == mBuffer.getId()) { const uint32_t allocatedBefore = getNumAllocatedInBuffer(); release(checkPoint.mReleasePtr); if (allocatedBefore >= mAllocationMargin) { if ((mCurrentCapacity > mMinCapacity) && (allocatedBefore * 6 <= mCurrentCapacity)) { resize(std::max(allocatedBefore * 3, mMinCapacity)); } else { mAllocationMargin = mCurrentCapacity; } } else { const uint64_t numReleased = (allocatedBefore - getNumAllocatedInBuffer()); const uint64_t distanceToMargin = (mAllocationMargin - allocatedBefore); mAllocationMargin -= std::max(static_cast(numReleased * distanceToMargin / mAllocationMargin / mDecaySpeedFactor), 1u); } } } void RingBufferAllocator::setFragmentReserve(uint32_t reserve) { ASSERT(valid()); mFragmentReserve = reserve; mFragmentEndR = mBuffer.decClamped(mFragmentEnd, mFragmentReserve); } uint8_t *RingBufferAllocator::allocateInNewFragment(uint32_t size) { if (mListener) { mListener->onRingBufferFragmentEnd(); } uint8_t *newFragmentBegin = nullptr; if (mFragmentEnd != mDataBegin) { newFragmentBegin = mBuffer.data(); uint8_t *const newFragmentEnd = mDataBegin; uint8_t *const newFragmentEndR = mBuffer.decClamped(newFragmentEnd, mFragmentReserve); // It should wrap around only if it can allocate. if (newFragmentEndR - newFragmentBegin >= static_cast(size)) { mDataEnd = newFragmentBegin; mFragmentEnd = newFragmentEnd; mFragmentEndR = newFragmentEndR; if (mListener) { mListener->onRingBufferNewFragment(); } mDataEnd = newFragmentBegin + size; return newFragmentBegin; } } resize(std::max(mCurrentCapacity + mCurrentCapacity / 2, size + mFragmentReserve)); if (mListener) { mListener->onRingBufferNewFragment(); } ASSERT(mFragmentEndR - mDataEnd >= static_cast(size)); newFragmentBegin = mDataEnd; mDataEnd = newFragmentBegin + size; return newFragmentBegin; } void RingBufferAllocator::resize(uint32_t newCapacity) { ASSERT(newCapacity >= mMinCapacity); if (mBuffer.getId() != 0) { mOldBuffers.emplace_back(std::move(mBuffer)); } mCurrentCapacity = newCapacity; mBuffer.incrementId(); mBuffer.resize(mCurrentCapacity); resetPointers(); mAllocationMargin = mCurrentCapacity; } void RingBufferAllocator::release(uint8_t *releasePtr) { if (releasePtr == mDataEnd) { // Ensures "mDataEnd == mBuffer.data()" with 0 allocations. resetPointers(); return; } if (mDataBegin == mFragmentEnd) { ASSERT((releasePtr >= mBuffer.data() && releasePtr < mDataEnd) || (releasePtr >= mDataBegin && releasePtr <= mBuffer.data() + mCurrentCapacity)); if (releasePtr < mDataBegin) { mFragmentEnd = mBuffer.data() + mCurrentCapacity; } else { mFragmentEnd = releasePtr; } mFragmentEndR = mBuffer.decClamped(mFragmentEnd, mFragmentReserve); } else { ASSERT(releasePtr >= mDataBegin && releasePtr < mDataEnd); } mDataBegin = releasePtr; } uint32_t RingBufferAllocator::getNumAllocatedInBuffer() const { // 2 free fragments: [mBuffer.begin, mDataBegin) [mDataEnd, mBuffer.end); // 1 used fragment: [DataBegin, DataEnd) if (mFragmentEnd != mDataBegin) { ASSERT(mDataEnd >= mDataBegin); return static_cast(mDataEnd - mDataBegin); } // 1 free fragment: [mDataEnd, mDataBegin) // 2 used fragments: [mBuffer.begin, mDataEnd) [mDataBegin, mBuffer.end) ASSERT(mDataBegin >= mDataEnd); return (mCurrentCapacity - static_cast(mDataBegin - mDataEnd)); } void RingBufferAllocator::resetPointers() { mDataBegin = mBuffer.data(); mDataEnd = mDataBegin; mFragmentEnd = mDataEnd + mCurrentCapacity; mFragmentEndR = mBuffer.decClamped(mFragmentEnd, mFragmentReserve); } // SharedRingBufferAllocator implementation. SharedRingBufferAllocator::SharedRingBufferAllocator() { mAllocator.reset(); } SharedRingBufferAllocator::~SharedRingBufferAllocator() { #if defined(ANGLE_ENABLE_ASSERTS) ASSERT(!mSharedCP || mSharedCP->mRefCount == 1); #endif SafeDelete(mSharedCP); } SharedRingBufferAllocatorCheckPoint *SharedRingBufferAllocator::acquireSharedCP() { if (!mSharedCP) { mSharedCP = new SharedRingBufferAllocatorCheckPoint(); } #if defined(ANGLE_ENABLE_ASSERTS) ASSERT(++mSharedCP->mRefCount > 1); // Must always be 1 ref before #endif return mSharedCP; } void SharedRingBufferAllocator::releaseToSharedCP() { ASSERT(mSharedCP); const RingBufferAllocatorCheckPoint releaseCP = mSharedCP->pop(); if (releaseCP.valid()) { mAllocator.release(releaseCP); } } void SharedRingBufferAllocatorCheckPoint::releaseAndUpdate(RingBufferAllocatorCheckPoint *newValue) { ASSERT(newValue && newValue->valid()); #if defined(ANGLE_ENABLE_ASSERTS) ASSERT(--mRefCount >= 1); // Must always remain 1 ref #endif { std::lock_guard lock(mMutex); mValue = *newValue; } newValue->reset(); } RingBufferAllocatorCheckPoint SharedRingBufferAllocatorCheckPoint::pop() { std::lock_guard lock(mMutex); RingBufferAllocatorCheckPoint value = mValue; mValue.reset(); return value; } } // namespace angle