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 // SPDX-License-Identifier: MIT
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#pragma once #ifndef _DATACHUNKER_H_
#define _DATACHUNKER_H_ #define _DATACHUNKER_H_
#ifndef _PLATFORM_H_ #ifndef _PLATFORM_H_
@ -21,8 +21,6 @@
#include <algorithm> #include <algorithm>
#include <stdint.h> #include <stdint.h>
//#include "math/mMathFn.h" // tgemit - needed here for the moment
/// Implements a chunked data allocator. /// Implements a chunked data allocator.
/// ///
/// This memory allocator allocates data in chunks of bytes, /// This memory allocator allocates data in chunks of bytes,
@ -430,3 +428,5 @@ public:
inline ClassChunker<K2>& getT2Chunker() { return mT2; } inline ClassChunker<K2>& getT2Chunker() { return mT2; }
inline ClassChunker<K3>& getT3Chunker() { return mT3; } 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 // SPDX-License-Identifier: MIT
// 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.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include "core/frameAllocator.h" #include "core/frameAllocator.h"
#include "console/engineAPI.h"
thread_local FrameAllocator::FrameAllocatorType FrameAllocator::smMainInstance; thread_local ManagedAlignedBufferAllocator<U32> FrameAllocator::smFrameAllocator;
#ifdef TORQUE_MEM_DEBUG #ifdef TORQUE_MEM_DEBUG
thread_local dsize_t FrameAllocator::smAllocatedBytes; thread_local dsize_t FrameAllocator::smMaxAllocationBytes = 0;
#endif #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 // SPDX-License-Identifier: MIT
// 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.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#ifndef _FRAMEALLOCATOR_H_ #ifndef _FRAMEALLOCATOR_H_
@ -27,6 +12,19 @@
#include "platform/platform.h" #include "platform/platform.h"
#endif #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 template<typename T> class AlignedBufferAllocator
{ {
protected: 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) inline void initWithElements(T* ptr, U32 numElements)
{ {
mBuffer = ptr; mBuffer = ptr;
@ -49,6 +48,7 @@ public:
mWaterMark = 0; mWaterMark = 0;
} }
/// Inits allocator based on a ptr to a memory block of size bytes
inline void initWithBytes(T* ptr, dsize_t bytes) inline void initWithBytes(T* ptr, dsize_t bytes)
{ {
mBuffer = ptr; mBuffer = ptr;
@ -56,6 +56,7 @@ public:
mWaterMark = 0; mWaterMark = 0;
} }
/// Allocs numBytes worth of elements
inline T* allocBytes(const size_t numBytes) inline T* allocBytes(const size_t numBytes)
{ {
T* ptr = &mBuffer[mWaterMark]; T* ptr = &mBuffer[mWaterMark];
@ -71,6 +72,7 @@ public:
return ptr; return ptr;
} }
/// Allocates numElements elements
inline T* allocElements(const U32 numElements) inline T* allocElements(const U32 numElements)
{ {
T* ptr = &mBuffer[mWaterMark]; T* ptr = &mBuffer[mWaterMark];
@ -85,6 +87,7 @@ public:
return ptr; return ptr;
} }
/// Sets current aligned watermark position
inline void setPosition(const U32 waterMark) inline void setPosition(const U32 waterMark)
{ {
AssertFatal(waterMark <= mHighWaterMark, "Error, invalid 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 /// Implements an AlignedBufferAllocator<T> which manages its own memory.
/// many small chunks of memory, then free them all in a batch. For instance,
/// say we're allocating storage for some vertex calculations:
/// ///
/// @code template<typename T> class ManagedAlignedBufferAllocator : public AlignedBufferAllocator<T>
/// // 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
{ {
public: public:
static U32 smMaxFrameAllocation; T* mMemory;
#ifdef TORQUE_MEM_DEBUG
static thread_local dsize_t smAllocatedBytes;
#endif
typedef AlignedBufferAllocator<U32> FrameAllocatorType;
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(); destroy();
AssertFatal(smMainInstance.getAlignedBuffer() != NULL, "Error, not initialized");
if (curPtr == NULL)
return;
delete[] curPtr;
smMainInstance.initWithElements(NULL, 0);
} }
inline static void* alloc(const U32 allocSize) void init(const dsize_t byteSize)
{ {
void* outPtr = smMainInstance.allocBytes(allocSize); AssertFatal(mMemory == NULL, "ManagedAlignedBufferAllocator already initialized");
U32 frameSize = calcRequiredElementSize(byteSize);
#ifdef TORQUE_MEM_DEBUG mMemory = new U32[frameSize];
smAllocatedBytes += allocSize; initWithElements(mMemory, frameSize);
if (smAllocatedBytes > smMaxFrameAllocation)
{
smMaxFrameAllocation = smAllocatedBytes;
}
#endif
return outPtr;
} }
inline static void setWaterMark(const U32 waterMark) void destroy()
{ {
#ifdef TORQUE_MEM_DEBUG //setPositition(0);
AssertFatal(waterMark % sizeof(FrameAllocatorType::ValueType) == 0, "Misaligned watermark"); delete[] mMemory;
smAllocatedBytes = waterMark; mMemory = NULL;
#endif
smMainInstance.setPosition(waterMark / sizeof(FrameAllocatorType::ValueType));
} }
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. /// Implements a thread-local global buffer for frame-based allocations.
/// /// All allocations are aligned to U32.
/// The purpose of this class is to make it simpler and more reliable to use the ///
/// FrameAllocator. Simply use it like this: /// Example usage:
/// ///
/// @code /// @code
/// FrameAllocatorMarker mem; /// U32 waterMark = FrameAllocator::getWaterMark();
/// /// void* ptr = FrameAllocator::alloc(10);
/// char *buff = (char*)mem.alloc(100); /// // Cleanup...
/// FrameAllocator::setWaterMark(waterMark);
/// @endcode /// @endcode
/// ///
/// When you leave the scope you defined the FrameAllocatorMarker in, it will /// See also: FrameAllocatorMarker, FrameTemp.
/// automatically restore the watermark on the FrameAllocator. In situations ///
/// with complex branches, this can be a significant headache remover, as you /// NOTE: worker threads which use FrameAllocator should call init and destroy. i.e.
/// don't have to remember to reset the FrameAllocator on every posssible branch. ///
class FrameAllocatorMarker /// @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: 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() FrameAllocatorMarker()
{ {
mMarker = FrameAllocator::getWaterMark(); mPosition = FrameAllocator::getWaterMark();
} }
~FrameAllocatorMarker() ~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 /// Helper class which temporarily allocates a set of elements of type T from FrameAllocator,
/// the FrameAllocator. For example: /// restoring the water mark when the scope has ended as with FrameAllocatorMarker.
///
/// Example usage:
///
/// @code /// @code
/// FrameTemp<char> tempStr(32); // NOTE! This parameter is NOT THE SIZE IN BYTES. See constructor docs. /// FrameTemp<UTF8> textMarker(64);
/// dStrcat( tempStr, SomeOtherString ); /// for (U32 i=0; i<textMarker.size(); i++)
/// tempStr[2] = 'l'; /// {
/// Con::printf( tempStr ); /// textMarker[i] = '\0';
/// Con::printf( "Foo: %s", ~tempStr ); /// }
/// @endcode /// @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> template<class T>
class FrameTemp class FrameTemp
{ {
protected: T* mData;
U32 mWaterMark; U32 mSize;
T* mMemory; U32 mPosition;
U32 mNumObjectsInMemory;
public: 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++) FrameTemp(const U32 numElements = 0)
constructInPlace<T>(&mMemory[i]); {
mPosition = FrameAllocator::getWaterMark();
mData = (T*)FrameAllocator::alloc(sizeof(T) * numElements);
mSize = numElements;
} }
/// Destructor restores the watermark
~FrameTemp() ~FrameTemp()
{ {
// Call destructor for (U32 i = 0; i < mSize; i++)
for (U32 i = 0; i < mNumObjectsInMemory; i++) destructInPlace(&mData[i]);
destructInPlace<T>(&mMemory[i]); FrameAllocator::setWaterMark(mPosition);
FrameAllocator::setWaterMark(mWaterMark);
} }
U32 getObjectCount(void) const { return mNumObjectsInMemory; } // Operators
U32 size(void) const { return mNumObjectsInMemory; }
T& operator *() { return *mMemory; }; inline TORQUE_FORCEINLINE T& operator*() { return *mData; }
const T& operator *() const { return *mMemory; } inline TORQUE_FORCEINLINE const T& operator*() const { return *mData; }
T** operator &() { return &mMemory; } inline TORQUE_FORCEINLINE T** operator&() { return &mData; }
T* const * operator &() const { return &mMemory; } inline TORQUE_FORCEINLINE T* const * operator&() const { return &mData; }
operator T* () { return mMemory; } inline TORQUE_FORCEINLINE operator T&() { return *mData; }
operator const T* () const { return mMemory; } inline TORQUE_FORCEINLINE operator const T&() const { return *mData; }
operator T& () { return *mMemory; } inline TORQUE_FORCEINLINE operator T* () { return mData; }
operator const T& () const { return *mMemory; } inline TORQUE_FORCEINLINE operator const T* () const { return mData; }
operator T() { return *mMemory; } inline TORQUE_FORCEINLINE operator T () { return *mData; }
operator const T() const { return *mMemory; } 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 // Utils
// Which seems to upset visual studio.
T& operator[](const U32 idx) { return mMemory[idx]; } inline TORQUE_FORCEINLINE T* address() const { return mData; }
const T& operator[](const U32 idx) const { return mMemory[idx]; } inline TORQUE_FORCEINLINE const U32 size() const { return mSize; }
T& operator[](const S32 idx) { return mMemory[idx]; } inline TORQUE_FORCEINLINE const U32 getObjectCount() const { return mSize; }
const T& operator[](const S32 idx) const { return mMemory[idx]; }
}; };
//-----------------------------------------------------------------------------
#endif // _H_FRAMEALLOCATOR_ #endif // _H_FRAMEALLOCATOR_

View file

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