Torque3D/Engine/source/gfx/gl/gfxGLCircularVolatileBuffer.h
AzaezelX 5ffa3b81f1 dial back nullPtr usage
while it still remains a good idea to port as many NULL compares and assignments over to nullPtr as feasable, we do still need to sort out how to better support scripted empty, false, and zero assigns for things like objectIDs.

this means we'll need to both fully convert the backend of the parser to support that kind of thing, but also alter most if not all exisiting NULLs. up to and including things like SAFE_DELETE. while that's certainly feasable, given there's aproximatel 400 nullptr assigns/checks prior to this commit, and roughly 1800 of the prior, if it terminates in a script call and not an aip one direct, we'll be dialing that back until such time as fork fully fopcused on converting and resolving any lingering mismatches is completed.
2025-12-29 17:45:09 -06:00

321 lines
7.6 KiB
C++

#ifndef GL_CIRCULAR_VOLATILE_BUFFER_H
#define GL_CIRCULAR_VOLATILE_BUFFER_H
#include "gfx/gl/gfxGLDevice.h"
#include "gfx/gl/gfxGLUtils.h"
class GLFenceRange
{
public:
GLFenceRange() : mStart(0), mEnd(0), mSync(0)
{
}
~GLFenceRange()
{
//the order of creation/destruction of static variables is indetermined... depends on detail of the build
//looks like for some reason on windows + sdl + opengl the order make invalid / wrong the process TODO: Refactor -LAR
//AssertFatal( mSync == 0, "");
}
void init(U32 start, U32 end)
{
PROFILE_SCOPE(GFXGLQueryFence_issue);
mStart = start;
mEnd = end;
mSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
bool checkOverlap(U32 start, U32 end)
{
if ((mStart < end - 1) && (start < mEnd - 1))
return true;
return false;
}
void wait()
{
PROFILE_SCOPE(GFXGLQueryFence_block);
GLbitfield waitFlags = 0;
GLuint64 waitDuration = 0;
while( 1 )
{
GLenum waitRet = glClientWaitSync( mSync, waitFlags, waitDuration );
if( waitRet == GL_ALREADY_SIGNALED || waitRet == GL_CONDITION_SATISFIED )
{
break;
}
if( waitRet == GL_WAIT_FAILED )
{
AssertFatal(0, "GLSync failed.");
break;
}
waitFlags = GL_SYNC_FLUSH_COMMANDS_BIT;
waitDuration = scOneSecondInNanoSeconds;
}
glDeleteSync(mSync);
mSync = 0;
}
void swap( GLFenceRange &r )
{
GLFenceRange temp;
temp = *this;
*this = r;
r = temp;
}
protected:
U32 mStart, mEnd;
GLsync mSync;
static const GLuint64 scOneSecondInNanoSeconds = 1000000000;
GLFenceRange( const GLFenceRange &);
GLFenceRange& operator=(const GLFenceRange &r)
{
mStart = r.mStart;
mEnd = r.mEnd;
mSync = r.mSync;
return *this;
}
};
class GLOrderedFenceRangeManager
{
public:
~GLOrderedFenceRangeManager( )
{
//the order of creation/destruction of static variables is indetermined... depends on detail of the build
//looks like for some reason on windows + sdl + opengl the order make invalid / wrong the process TODO: Refactor -LAR
//waitAllRanges( );
}
void protectOrderedRange( U32 start, U32 end )
{
mFenceRanges.increment();
GLFenceRange &range = mFenceRanges.last();
range.init( start, end );
}
void waitFirstRange( U32 start, U32 end )
{
if( !mFenceRanges.size() || !mFenceRanges[0].checkOverlap( start, end ) )
return;
mFenceRanges[0].wait();
mFenceRanges.pop_front();
}
void waitOverlapRanges( U32 start, U32 end )
{
for( U32 i = 0; i < mFenceRanges.size(); ++i )
{
if( !mFenceRanges[i].checkOverlap( start, end ) )
continue;
mFenceRanges[i].wait();
mFenceRanges.erase(i);
}
}
void waitAllRanges()
{
for( int i = 0; i < mFenceRanges.size(); ++i )
mFenceRanges[i].wait();
mFenceRanges.clear();
}
protected:
Vector<GLFenceRange> mFenceRanges;
};
class GLCircularVolatileBuffer
{
public:
GLCircularVolatileBuffer(GLuint binding)
: mBinding(binding), mBufferName(0), mBufferPtr(NULL), mBufferSize(0), mBufferFreePos(0), mCurrectUsedRangeStart(0)
{
init();
}
~GLCircularVolatileBuffer()
{
glDeleteBuffers(1, &mBufferName);
}
void init()
{
glGenBuffers(1, &mBufferName);
PRESERVE_BUFFER( mBinding );
glBindBuffer(mBinding, mBufferName);
const U32 cSizeInMB = 10;
mBufferSize = (cSizeInMB << 20);
if( GFXGL->mCapabilities.bufferStorage )
{
const GLbitfield flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;
glBufferStorage(mBinding, mBufferSize, NULL, flags);
mBufferPtr = glMapBufferRange(mBinding, 0, mBufferSize, flags);
}
else
{
glBufferData(mBinding, mBufferSize, NULL, GL_DYNAMIC_DRAW);
}
}
struct
{
U32 mOffset = 0;
U32 mSize = 0;
}_getBufferData;
void lock(const U32 size, U32 offsetAlign, U32& outOffset, void*& outPtr)
{
if (!size)
{
AssertFatal(0, "GLCircularVolatileBuffer::lock - size must be > 0");
outOffset = 0;
outPtr = NULL;
return;
}
// Align free pos first (before wraparound check)
if (offsetAlign)
{
mBufferFreePos = ((mBufferFreePos + offsetAlign - 1) / offsetAlign) * offsetAlign;
}
// If the size won't fit from current pos to end, wrap around
if (mBufferFreePos + size > mBufferSize)
{
// Protect the remaining space
if (mBufferFreePos < mBufferSize)
mUsedRanges.push_back(UsedRange(mBufferFreePos, mBufferSize - 1));
// Reset free pos
mBufferFreePos = 0;
// Realign after wrap
if (offsetAlign)
{
mBufferFreePos = ((mBufferFreePos + offsetAlign - 1) / offsetAlign) * offsetAlign;
}
// Now check for overlaps *after* wrapping
mLockManager.waitOverlapRanges(mBufferFreePos, mBufferFreePos + size - 1);
}
else
{
// Normal range wait
mLockManager.waitOverlapRanges(mBufferFreePos, mBufferFreePos + size - 1);
}
outOffset = mBufferFreePos;
if (GFXGL->mCapabilities.bufferStorage)
{
outPtr = static_cast<U8*>(mBufferPtr) + mBufferFreePos;
}
else if (GFXGL->glUseMap())
{
PRESERVE_BUFFER(mBinding);
glBindBuffer(mBinding, mBufferName);
const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT;
outPtr = glMapBufferRange(mBinding, outOffset, size, access);
}
else
{
_getBufferData.mOffset = outOffset;
_getBufferData.mSize = size;
outPtr = mFrameAllocator.lock(size);
}
mBufferFreePos += size;
//align 4bytes
mBufferFreePos = ((mBufferFreePos + 4 - 1) / 4) * 4;
}
void unlock()
{
if( GFXGL->mCapabilities.bufferStorage )
{
return;
}
else if( GFXGL->glUseMap() )
{
PRESERVE_BUFFER( mBinding );
glBindBuffer(mBinding, mBufferName);
glUnmapBuffer(mBinding);
}
else
{
PRESERVE_BUFFER( mBinding );
glBindBuffer(mBinding, mBufferName);
glBufferSubData( mBinding, _getBufferData.mOffset, _getBufferData.mSize, mFrameAllocator.getlockedPtr() );
_getBufferData.mOffset = 0;
_getBufferData.mSize = 0;
mFrameAllocator.unlock();
}
}
U32 getHandle() const { return mBufferName; }
void protectUsedRange()
{
for( int i = 0; i < mUsedRanges.size(); ++i )
{
mLockManager.protectOrderedRange( mUsedRanges[i].start, mUsedRanges[i].end );
}
mUsedRanges.clear();
if( mCurrectUsedRangeStart < mBufferFreePos )
{
mLockManager.protectOrderedRange( mCurrectUsedRangeStart, mBufferFreePos-1 );
mCurrectUsedRangeStart = mBufferFreePos;
}
}
protected:
GLuint mBinding;
GLuint mBufferName;
void *mBufferPtr;
U32 mBufferSize;
U32 mBufferFreePos;
U32 mCurrectUsedRangeStart;
GLOrderedFenceRangeManager mLockManager;
FrameAllocatorLockableHelper mFrameAllocator;
struct UsedRange
{
UsedRange(U32 _start = 0, U32 _end = 0)
: start(_start), end(_end)
{
}
U32 start, end;
};
Vector<UsedRange> mUsedRanges;
};
#endif