blob: da16886593a206ebc85e6a5b4e6527dd00cf7cfc [file] [log] [blame]
//
// 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<uint32_t>(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<ptrdiff_t>(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<ptrdiff_t>(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<uint32_t>(mDataEnd - mDataBegin);
}
// 1 free fragment: [mDataEnd, mDataBegin)
// 2 used fragments: [mBuffer.begin, mDataEnd) [mDataBegin, mBuffer.end)
ASSERT(mDataBegin >= mDataEnd);
return (mCurrentCapacity - static_cast<uint32_t>(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<std::mutex> lock(mMutex);
mValue = *newValue;
}
newValue->reset();
}
RingBufferAllocatorCheckPoint SharedRingBufferAllocatorCheckPoint::pop()
{
std::lock_guard<std::mutex> lock(mMutex);
RingBufferAllocatorCheckPoint value = mValue;
mValue.reset();
return value;
}
} // namespace angle