Reimplement FrameAllocator and FrameTemp; Tidy up DataChunker header.

- Also additional work on tests to reflect watermark behavior change
This commit is contained in:
James Urquhart 2024-02-06 02:30:47 +00:00
parent 7332dd6643
commit 45898694e4
4 changed files with 154 additions and 227 deletions

View file

@ -5,7 +5,7 @@
// SPDX-License-Identifier: MIT
//-----------------------------------------------------------------------------
#pragma once
#ifndef _DATACHUNKER_H_
#define _DATACHUNKER_H_
#ifndef _PLATFORM_H_
@ -21,8 +21,6 @@
#include <algorithm>
#include <stdint.h>
//#include "math/mMathFn.h" // tgemit - needed here for the moment
/// Implements a chunked data allocator.
///
/// This memory allocator allocates data in chunks of bytes,
@ -430,3 +428,5 @@ public:
inline ClassChunker<K2>& getT2Chunker() { return mT2; }
inline ClassChunker<K3>& getT3Chunker() { return mT3; }
};
#endif

View file

@ -1,46 +1,15 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
// Copyright (C) 2024 tgemit contributors.
// See AUTHORS file and git repository for contributor information.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// SPDX-License-Identifier: MIT
//-----------------------------------------------------------------------------
#include "core/frameAllocator.h"
#include "console/engineAPI.h"
thread_local FrameAllocator::FrameAllocatorType FrameAllocator::smMainInstance;
thread_local ManagedAlignedBufferAllocator<U32> FrameAllocator::smFrameAllocator;
#ifdef TORQUE_MEM_DEBUG
thread_local dsize_t FrameAllocator::smAllocatedBytes;
thread_local dsize_t FrameAllocator::smMaxAllocationBytes = 0;
#endif
U32 FrameAllocator::smMaxFrameAllocation;
U32 FrameAllocator::getMaxFrameAllocation()
{
return (S32)FrameAllocator::smMaxFrameAllocation;
}
#if defined(TORQUE_DEBUG)
DefineEngineFunction(getMaxFrameAllocation, S32, (), , "")
{
return (S32)FrameAllocator::getMaxFrameAllocation();
}
#endif

View file

@ -1,23 +1,8 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
// Copyright (C) 2023-2024 tgemit contributors.
// See AUTHORS file and git repository for contributor information.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// SPDX-License-Identifier: MIT
//-----------------------------------------------------------------------------
#ifndef _FRAMEALLOCATOR_H_
@ -27,6 +12,19 @@
#include "platform/platform.h"
#endif
/// Implements an buffer which allocates data based on the alignment of type T.
///
/// Example usage:
///
/// @code
/// AlignedBufferAllocator<U32> alloc32;
/// alloc32.initWithElements(new U32[10], 10);
///
/// void* ptr = alloc32.allocBytes(2);
/// // Reset back to start
/// alloc32.setPosition(0);
/// @endcode
///
template<typename T> class AlignedBufferAllocator
{
protected:
@ -42,6 +40,7 @@ public:
{
}
/// Inits allocator based on a ptr to a memory block of size numElements * sizeof(T)
inline void initWithElements(T* ptr, U32 numElements)
{
mBuffer = ptr;
@ -49,6 +48,7 @@ public:
mWaterMark = 0;
}
/// Inits allocator based on a ptr to a memory block of size bytes
inline void initWithBytes(T* ptr, dsize_t bytes)
{
mBuffer = ptr;
@ -56,6 +56,7 @@ public:
mWaterMark = 0;
}
/// Allocs numBytes worth of elements
inline T* allocBytes(const size_t numBytes)
{
T* ptr = &mBuffer[mWaterMark];
@ -71,6 +72,7 @@ public:
return ptr;
}
/// Allocates numElements elements
inline T* allocElements(const U32 numElements)
{
T* ptr = &mBuffer[mWaterMark];
@ -85,6 +87,7 @@ public:
return ptr;
}
/// Sets current aligned watermark position
inline void setPosition(const U32 waterMark)
{
AssertFatal(waterMark <= mHighWaterMark, "Error, invalid waterMark");
@ -144,231 +147,186 @@ public:
}
};
/// Temporary memory pool for per-frame allocations.
///
/// In the course of rendering a frame, it is often necessary to allocate
/// many small chunks of memory, then free them all in a batch. For instance,
/// say we're allocating storage for some vertex calculations:
/// Implements an AlignedBufferAllocator<T> which manages its own memory.
///
/// @code
/// // Get FrameAllocator memory...
/// U32 waterMark = FrameAllocator::getWaterMark();
/// F32 * ptr = (F32*)FrameAllocator::alloc(sizeof(F32)*2*targetMesh->vertsPerFrame);
///
/// ... calculations ...
///
/// // Free frameAllocator memory
/// FrameAllocator::setWaterMark(waterMark);
/// @endcode
class FrameAllocator
template<typename T> class ManagedAlignedBufferAllocator : public AlignedBufferAllocator<T>
{
public:
static U32 smMaxFrameAllocation;
#ifdef TORQUE_MEM_DEBUG
static thread_local dsize_t smAllocatedBytes;
#endif
typedef AlignedBufferAllocator<U32> FrameAllocatorType;
T* mMemory;
inline static void init(const U32 frameSize)
ManagedAlignedBufferAllocator() : mMemory(NULL)
{
FrameAllocatorType::ValueType* curPtr = smMainInstance.getAlignedBuffer();
AssertFatal(curPtr == NULL, "Error, already initialized");
if (curPtr)
return;
#ifdef TORQUE_MEM_DEBUG
smAllocatedBytes = 0;
#endif
U32 elementSize = FrameAllocatorType::calcRequiredElementSize(frameSize);
FrameAllocatorType::ValueType* newAlignedBuffer = new FrameAllocatorType::ValueType[elementSize];
smMainInstance.initWithElements(newAlignedBuffer, elementSize);
}
inline static void destroy()
~ManagedAlignedBufferAllocator()
{
FrameAllocatorType::ValueType* curPtr = smMainInstance.getAlignedBuffer();
AssertFatal(smMainInstance.getAlignedBuffer() != NULL, "Error, not initialized");
if (curPtr == NULL)
return;
delete[] curPtr;
smMainInstance.initWithElements(NULL, 0);
destroy();
}
inline static void* alloc(const U32 allocSize)
void init(const dsize_t byteSize)
{
void* outPtr = smMainInstance.allocBytes(allocSize);
#ifdef TORQUE_MEM_DEBUG
smAllocatedBytes += allocSize;
if (smAllocatedBytes > smMaxFrameAllocation)
{
smMaxFrameAllocation = smAllocatedBytes;
}
#endif
return outPtr;
AssertFatal(mMemory == NULL, "ManagedAlignedBufferAllocator already initialized");
U32 frameSize = calcRequiredElementSize(byteSize);
mMemory = new U32[frameSize];
initWithElements(mMemory, frameSize);
}
inline static void setWaterMark(const U32 waterMark)
void destroy()
{
#ifdef TORQUE_MEM_DEBUG
AssertFatal(waterMark % sizeof(FrameAllocatorType::ValueType) == 0, "Misaligned watermark");
smAllocatedBytes = waterMark;
#endif
smMainInstance.setPosition(waterMark / sizeof(FrameAllocatorType::ValueType));
//setPositition(0);
delete[] mMemory;
mMemory = NULL;
}
inline static U32 getWaterMark()
{
return smMainInstance.getPositionBytes();
}
inline static U32 getHighWaterMark()
{
return smMainInstance.getSizeBytes();
}
static U32 getMaxFrameAllocation();
static thread_local FrameAllocatorType smMainInstance;
};
/// Helper class to deal with FrameAllocator usage.
///
/// The purpose of this class is to make it simpler and more reliable to use the
/// FrameAllocator. Simply use it like this:
/// Implements a thread-local global buffer for frame-based allocations.
/// All allocations are aligned to U32.
///
/// Example usage:
///
/// @code
/// FrameAllocatorMarker mem;
///
/// char *buff = (char*)mem.alloc(100);
/// U32 waterMark = FrameAllocator::getWaterMark();
/// void* ptr = FrameAllocator::alloc(10);
/// // Cleanup...
/// FrameAllocator::setWaterMark(waterMark);
/// @endcode
///
/// When you leave the scope you defined the FrameAllocatorMarker in, it will
/// automatically restore the watermark on the FrameAllocator. In situations
/// with complex branches, this can be a significant headache remover, as you
/// don't have to remember to reset the FrameAllocator on every posssible branch.
class FrameAllocatorMarker
/// See also: FrameAllocatorMarker, FrameTemp.
///
/// NOTE: worker threads which use FrameAllocator should call init and destroy. i.e.
///
/// @code
/// FrameAllocator::init(1024 * 1024 * 12);
/// // Do work...
/// FrameAllocator::destroy();
/// @endcode
///
class FrameAllocator
{
U32 mMarker;
protected:
static thread_local ManagedAlignedBufferAllocator<U32> smFrameAllocator;
#ifdef TORQUE_MEM_DEBUG
static thread_local dsize_t smMaxAllocationBytes;
#endif
public:
inline TORQUE_FORCEINLINE static void init(const dsize_t byteSize) { return smFrameAllocator.init(byteSize); }
inline TORQUE_FORCEINLINE static void destroy() { smFrameAllocator.destroy(); }
inline TORQUE_FORCEINLINE static void* alloc(const dsize_t numBytes)
{
#ifdef TORQUE_MEM_DEBUG
const dsize_t allocBytes = smFrameAllocator.getPositionBytes();
smMaxAllocationBytes = allocBytes > smMaxAllocationBytes ? allocBytes : smMaxAllocationBytes;
#endif
return smFrameAllocator.allocBytes(numBytes);
}
inline TORQUE_FORCEINLINE static U32 getWaterMark() { return smFrameAllocator.getPosition(); }
inline TORQUE_FORCEINLINE static dsize_t getWaterMarkBytes() { return smFrameAllocator.getPositionBytes(); }
inline TORQUE_FORCEINLINE static void setWaterMark(U32 pos) { return smFrameAllocator.setPosition(pos); }
inline TORQUE_FORCEINLINE static U32 getHighWaterMark() { return smFrameAllocator.getSizeBytes(); }
};
/// Helper class which saves and restores the previous water mark level from FrameAllocator based on scope.
///
/// Example usage:
///
/// @code
/// FrameAllocatorMarker marker;
/// void* ptr = marker.alloc(1024);
/// @endcode
///
class FrameAllocatorMarker
{
U32 mPosition;
public:
FrameAllocatorMarker()
{
mMarker = FrameAllocator::getWaterMark();
mPosition = FrameAllocator::getWaterMark();
}
~FrameAllocatorMarker()
{
FrameAllocator::setWaterMark(mMarker);
FrameAllocator::setWaterMark(mPosition);
}
void* alloc(const U32 allocSize)
/// Allocs numBytes of memory from FrameAllocator
inline TORQUE_FORCEINLINE static void* alloc(const dsize_t numBytes)
{
return FrameAllocator::alloc(allocSize);
return FrameAllocator::alloc(numBytes);
}
};
/// Class for temporary variables that you want to allocate easily using
/// the FrameAllocator. For example:
/// Helper class which temporarily allocates a set of elements of type T from FrameAllocator,
/// restoring the water mark when the scope has ended as with FrameAllocatorMarker.
///
/// Example usage:
///
/// @code
/// FrameTemp<char> tempStr(32); // NOTE! This parameter is NOT THE SIZE IN BYTES. See constructor docs.
/// dStrcat( tempStr, SomeOtherString );
/// tempStr[2] = 'l';
/// Con::printf( tempStr );
/// Con::printf( "Foo: %s", ~tempStr );
/// FrameTemp<UTF8> textMarker(64);
/// for (U32 i=0; i<textMarker.size(); i++)
/// {
/// textMarker[i] = '\0';
/// }
/// @endcode
///
/// This will automatically handle getting and restoring the watermark of the
/// FrameAllocator when it goes out of scope. You should notice the strange
/// operator infront of tempStr on the printf call. This is normally a unary
/// operator for ones-complement, but in this class it will simply return the
/// memory of the allocation. It's the same as doing (const char *)tempStr
/// in the above case. The reason why it is necessary for the second printf
/// and not the first is because the second one is taking a variable arg
/// list and so it isn't getting the cast so that it's cast operator can
/// properly return the memory instead of the FrameTemp object itself.
///
/// @note It is important to note that this object is designed to just be a
/// temporary array of a dynamic size. Some wierdness may occur if you try
/// do perform crazy pointer stuff with it using regular operators on it.
/// I implemented what I thought were the most common operators that it
/// would be used for. If strange things happen, you will need to debug
/// them yourself.
///
template<class T>
class FrameTemp
{
protected:
U32 mWaterMark;
T* mMemory;
U32 mNumObjectsInMemory;
T* mData;
U32 mSize;
U32 mPosition;
public:
/// Constructor will store the FrameAllocator watermark and allocate the memory off
/// of the FrameAllocator.
///
/// @note It is important to note that, unlike the FrameAllocatorMarker and the
/// FrameAllocator itself, the argument to allocate is NOT the size in bytes,
/// doing:
/// @code
/// FrameTemp<F64> f64s(5);
/// @endcode
/// Is the same as
/// @code
/// F64 *f64s = new F64[5];
/// @endcode
///
/// @param count The number of objects to allocate
FrameTemp(const U32 count = 1) : mNumObjectsInMemory(count)
{
AssertFatal(count > 0, "Allocating a FrameTemp with less than one instance");
mWaterMark = FrameAllocator::getWaterMark();
mMemory = reinterpret_cast<T*>(FrameAllocator::alloc(sizeof(T) * count));
for (U32 i = 0; i < mNumObjectsInMemory; i++)
constructInPlace<T>(&mMemory[i]);
FrameTemp(const U32 numElements = 0)
{
mPosition = FrameAllocator::getWaterMark();
mData = (T*)FrameAllocator::alloc(sizeof(T) * numElements);
mSize = numElements;
}
/// Destructor restores the watermark
~FrameTemp()
{
// Call destructor
for (U32 i = 0; i < mNumObjectsInMemory; i++)
destructInPlace<T>(&mMemory[i]);
FrameAllocator::setWaterMark(mWaterMark);
for (U32 i = 0; i < mSize; i++)
destructInPlace(&mData[i]);
FrameAllocator::setWaterMark(mPosition);
}
U32 getObjectCount(void) const { return mNumObjectsInMemory; }
U32 size(void) const { return mNumObjectsInMemory; }
// Operators
T& operator *() { return *mMemory; };
const T& operator *() const { return *mMemory; }
inline TORQUE_FORCEINLINE T& operator*() { return *mData; }
inline TORQUE_FORCEINLINE const T& operator*() const { return *mData; }
T** operator &() { return &mMemory; }
T* const * operator &() const { return &mMemory; }
inline TORQUE_FORCEINLINE T** operator&() { return &mData; }
inline TORQUE_FORCEINLINE T* const * operator&() const { return &mData; }
operator T* () { return mMemory; }
operator const T* () const { return mMemory; }
inline TORQUE_FORCEINLINE operator T&() { return *mData; }
inline TORQUE_FORCEINLINE operator const T&() const { return *mData; }
operator T& () { return *mMemory; }
operator const T& () const { return *mMemory; }
inline TORQUE_FORCEINLINE operator T* () { return mData; }
inline TORQUE_FORCEINLINE operator const T* () const { return mData; }
operator T() { return *mMemory; }
operator const T() const { return *mMemory; }
inline TORQUE_FORCEINLINE operator T () { return *mData; }
inline TORQUE_FORCEINLINE operator const T () const { return *mData; }
inline T* address() const { return mMemory; }
inline TORQUE_FORCEINLINE T& operator[](const dsize_t idx) { return mData[idx]; }
inline TORQUE_FORCEINLINE const T& operator[](const dsize_t idx) const { return mData[idx]; }
// This ifdef is to satisfy the ever so pedantic GCC compiler
// Which seems to upset visual studio.
T& operator[](const U32 idx) { return mMemory[idx]; }
const T& operator[](const U32 idx) const { return mMemory[idx]; }
T& operator[](const S32 idx) { return mMemory[idx]; }
const T& operator[](const S32 idx) const { return mMemory[idx]; }
// Utils
inline TORQUE_FORCEINLINE T* address() const { return mData; }
inline TORQUE_FORCEINLINE const U32 size() const { return mSize; }
inline TORQUE_FORCEINLINE const U32 getObjectCount() const { return mSize; }
};
//-----------------------------------------------------------------------------
#endif // _H_FRAMEALLOCATOR_

View file

@ -89,13 +89,13 @@ TEST(FrameAllocatorTest, FrameAllocator_Should_Function_Correctly)
EXPECT_TRUE(FrameAllocator::getWaterMark() == 104);
FrameAllocator::alloc(1);
EXPECT_TRUE(FrameAllocator::getWaterMark() == 108);
EXPECT_TRUE(FrameAllocator::getWaterMark() == 105);
FrameAllocator::alloc(5);
EXPECT_TRUE(FrameAllocator::getWaterMark() == 116);
EXPECT_TRUE(FrameAllocator::getWaterMark() == 107); // 5 bytes == 2 ints
FrameAllocator::setWaterMark(0);
FrameAllocator::alloc(1);
EXPECT_TRUE(FrameAllocator::getWaterMark() == 4);
EXPECT_TRUE(FrameAllocator::getWaterMarkBytes() == 4);
FrameAllocator::setWaterMark(0);
}
@ -127,7 +127,7 @@ TEST(FrameTempTest, FrameTempShould_Function_Correctly)
FrameAllocator::setWaterMark(0);
{
FrameTemp<TestAlignmentStruct> fooTemp(20);
EXPECT_TRUE(FrameAllocator::getWaterMark() == sizeof(TestAlignmentStruct)*20);
EXPECT_TRUE(FrameAllocator::getWaterMarkBytes() == sizeof(TestAlignmentStruct)*20);
EXPECT_TRUE(&fooTemp[0] == fooTemp.address());
EXPECT_TRUE((&fooTemp[1] - &fooTemp[0]) == 1);
EXPECT_TRUE(fooTemp.getObjectCount() == 20);
@ -171,7 +171,7 @@ TEST(FrameTempTest, FrameTempShould_Function_Correctly)
}
// Exiting scope sets watermark
EXPECT_TRUE(FrameAllocator::getWaterMark() == 0);
EXPECT_TRUE(FrameAllocator::getWaterMarkBytes() == 0);
// Test the destructor actually gets called