Graphics Card Memory check before allocating FBO

Improved warning when allocating FrameBuffer. Avoid allocating FrameBuffer when buffering delay in Clone Source if we risk to consume all RAM in graphics card.
This commit is contained in:
Bruno Herbelin
2022-04-16 12:42:18 +02:00
parent c043026764
commit 48b1bfaebd
4 changed files with 153 additions and 114 deletions

View File

@@ -35,7 +35,7 @@ const char* CloneSource::cloning_provenance_label[2] = { "Original texture", "Po
CloneSource::CloneSource(Source *origin, uint64_t id) : Source(id), origin_(origin), cloningsurface_(nullptr), CloneSource::CloneSource(Source *origin, uint64_t id) : Source(id), origin_(origin), cloningsurface_(nullptr),
to_delete_(nullptr), delay_(0.0), paused_(false), provenance_(CLONE_TEXTURE) garbage_image_(nullptr), delay_(0.0), paused_(false), provenance_(CLONE_TEXTURE)
{ {
// initial name copies the origin name: diplucates are namanged in session // initial name copies the origin name: diplucates are namanged in session
name_ = origin->name(); name_ = origin->name();
@@ -152,18 +152,19 @@ void CloneSource::update(float dt)
if (!paused_ && cloningsurface_ != nullptr) { if (!paused_ && cloningsurface_ != nullptr) {
// if temporary FBO was pending to be deleted, delete it now // if temporary FBO was pending to be deleted, delete it now
if (to_delete_ != nullptr) { if (garbage_image_ != nullptr) {
delete to_delete_; delete garbage_image_;
to_delete_ = nullptr; garbage_image_ = nullptr;
} }
// What time is it? // What time is it?
double now = g_timer_elapsed (timer_, NULL); double now = g_timer_elapsed (timer_, NULL);
// is the total buffer of images longer than delay ? // is the total buffer of images longer than delay ?
if ( !images_.empty() && now - elapsed_.front() > delay_ ) { if ( !images_.empty() && now - elapsed_.front() > delay_ )
{
// remember FBO to be reused if needed (see below) or deleted later // remember FBO to be reused if needed (see below) or deleted later
to_delete_ = images_.front(); garbage_image_ = images_.front();
// remove element from queue (front) // remove element from queue (front)
images_.pop(); images_.pop();
elapsed_.pop(); elapsed_.pop();
@@ -171,16 +172,24 @@ void CloneSource::update(float dt)
} }
// add image to queue to accumulate buffer images until delay reached // add image to queue to accumulate buffer images until delay reached
if ( images_.empty() || now - elapsed_.front() < delay_ + (dt * 0.001) ) { if ( images_.empty() || now - elapsed_.front() < delay_ + (dt * 0.001) )
// create a FBO if none can be reused (from above) {
if (to_delete_ == nullptr) // create a FBO if none can be reused (from above) and test for RAM in GPU
to_delete_ = new FrameBuffer( origin_->frame()->resolution(), origin_->frame()->use_alpha() ); if (garbage_image_ == nullptr && ( images_.empty() || FrameBuffer::shouldHaveEnoughMemory(origin_->frame()->resolution(), origin_->frame()->use_alpha()) ) )
garbage_image_ = new FrameBuffer( origin_->frame()->resolution(), origin_->frame()->use_alpha() );
// no image available
if (garbage_image_ != nullptr) {
// add element to queue (back) // add element to queue (back)
images_.push( to_delete_ ); images_.push( garbage_image_ );
elapsed_.push( now ); elapsed_.push( now );
timestamps_.push( origin_->playtime() ); timestamps_.push( origin_->playtime() );
// to_delete_ FBO is now used, should not be deleted // garbage_image_ FBO is now used, it should not be deleted
to_delete_ = nullptr; garbage_image_ = nullptr;
}
else {
delay_ = now - elapsed_.front() - (dt * 0.001);
Log::Warning("Cannot satisfy delay for Clone %s: not enough RAM in graphics card.", name_.c_str());
}
} }
// CLONE_RENDER : blit rendered framebuffer in the newest image (back) // CLONE_RENDER : blit rendered framebuffer in the newest image (back)
@@ -232,16 +241,17 @@ bool CloneSource::playable () const
void CloneSource::replay() void CloneSource::replay()
{ {
// clear to_delete_ FBO if pending // clear to_delete_ FBO if pending
if (to_delete_ != nullptr) { if (garbage_image_ != nullptr) {
delete to_delete_; g_printerr(" REPLAY delete garbage %d \n", garbage_image_->opengl_id());
to_delete_ = nullptr; delete garbage_image_;
garbage_image_ = nullptr;
} }
// remove all images except the one in the back (newest) // remove all images except the one in the back (newest)
while (images_.size() > 1) { while (images_.size() > 1) {
// do not delete immediately the (oldest) front image (the FBO is currently displayed) // do not delete immediately the (oldest) front image (the FBO is currently displayed)
if (to_delete_ == nullptr) if (garbage_image_ == nullptr)
to_delete_ = images_.front(); garbage_image_ = images_.front();
// delete other FBO (unused) // delete other FBO (unused)
else if (images_.front() != nullptr) else if (images_.front() != nullptr)
delete images_.front(); delete images_.front();

View File

@@ -55,7 +55,7 @@ protected:
// cloning & queue of past frames // cloning & queue of past frames
std::queue<FrameBuffer *> images_; std::queue<FrameBuffer *> images_;
Surface *cloningsurface_; Surface *cloningsurface_;
FrameBuffer *to_delete_; FrameBuffer *garbage_image_;
// time management // time management
GTimer *timer_; GTimer *timer_;

View File

@@ -32,7 +32,7 @@
#include <stb_image_write.h> #include <stb_image_write.h>
#ifndef NDEBUG #ifndef NDEBUG
//#define FRAMEBUFFER_DEBUG #define FRAMEBUFFER_DEBUG
#endif #endif
#define GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX 0x9048 #define GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX 0x9048
@@ -140,7 +140,7 @@ void FrameBuffer::init()
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0);
#ifdef FRAMEBUFFER_DEBUG #ifdef FRAMEBUFFER_DEBUG
g_printerr("New FBO %d Multi Sampling ", framebufferid_); g_printerr("New FBO %d Multi Sampling\n", framebufferid_);
#endif #endif
} }
else { else {
@@ -149,7 +149,7 @@ void FrameBuffer::init()
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0);
#ifdef FRAMEBUFFER_DEBUG #ifdef FRAMEBUFFER_DEBUG
g_printerr("New FBO %d Single Sampling ", framebufferid_); g_printerr("New FBO %d Single Sampling\n", framebufferid_);
#endif #endif
} }
@@ -171,8 +171,8 @@ FrameBuffer::~FrameBuffer()
glDeleteTextures(1, &intermediate_textureid_); glDeleteTextures(1, &intermediate_textureid_);
#ifdef FRAMEBUFFER_DEBUG #ifdef FRAMEBUFFER_DEBUG
GLint framebufferMemoryInKB = ( width() * height() * 5 ) / 1024; GLint framebufferMemoryInKB = ( width() * height() * (use_alpha_?4:3) ) / 1024;
g_printerr("Framebuffer deleted %d x %d, %d kB freed\n", width(), height(), framebufferMemoryInKB); g_printerr("Framebuffer deleted %d x %d, ~%d kB freed\n", width(), height(), framebufferMemoryInKB);
#endif #endif
} }
@@ -346,21 +346,62 @@ void FrameBuffer::checkFramebufferStatus()
Log::Warning(" GL_FRAMEBUFFER_UNDEFINED is returned if target is the default framebuffer, but the default framebuffer does not exist."); Log::Warning(" GL_FRAMEBUFFER_UNDEFINED is returned if target is the default framebuffer, but the default framebuffer does not exist.");
break; break;
case GL_FRAMEBUFFER_COMPLETE: case GL_FRAMEBUFFER_COMPLETE:
{ {
// approximation of RAM needed for this FBO
GLint framebufferMemoryInKB = ( width() * height() * (use_alpha_?4:3) * (use_multi_sampling_?2:1) ) / 1024;
// test available memory if created buffer is big (more than 8MB)
if ( framebufferMemoryInKB > 8000 ) {
// Obtain RAM usage in GPU (if possible)
glm::ivec2 RAM = getGPUMemoryInformation();
// bad case: not enough RAM, we should warn the user
if ( RAM.x < framebufferMemoryInKB * 3 ) {
Log::Warning("Critical allocation of frame buffer: only %d kB RAM remaining in graphics card.", RAM.x );
if (RAM.y < INT_MAX)
Log::Warning("Only %.1f %% of %d kB available.", 100.f*float(RAM.x)/float(RAM.y), RAM.y);
}
#ifdef FRAMEBUFFER_DEBUG
else {
// normal case: enough RAM (or unknown) for this FBO
g_printerr("Framebuffer allocated %d x %d, ~%d kB", width(), height(), framebufferMemoryInKB);
if (RAM.x < INT_MAX)
g_printerr(" (%d kB remaining)", RAM.x);
g_printerr("\n");
}
#endif
}
}
break;
}
}
// RAM usage in GPU
// returns { CurAvailMemoryInKB, TotalMemoryInKB }
// MAX values means the info in not available
glm::ivec2 FrameBuffer::getGPUMemoryInformation()
{
glm::ivec2 ret(INT_MAX, INT_MAX);
// Detect method to get info
static int meminfomode = -1; static int meminfomode = -1;
// first time check which way to get memory info
if (meminfomode<0) { if (meminfomode<0) {
// initialized
meminfomode = 0; meminfomode = 0;
GLint numExtensions = 0; GLint numExtensions = 0;
glGetIntegerv( GL_NUM_EXTENSIONS, &numExtensions ); glGetIntegerv( GL_NUM_EXTENSIONS, &numExtensions );
for (int i = 0; i < numExtensions; ++i){ for (int i = 0; i < numExtensions; ++i){
const GLubyte *ccc = glGetStringi(GL_EXTENSIONS, i); const GLubyte *ccc = glGetStringi(GL_EXTENSIONS, i);
// NVIDIA extension available
if ( strcmp( (const char*)ccc, "GL_NVX_gpu_memory_info") == 0 ){ if ( strcmp( (const char*)ccc, "GL_NVX_gpu_memory_info") == 0 ){
meminfomode = 1; meminfomode = 1;
break; break;
} }
// ATI extension available
else if ( strcmp( (const char*)ccc, "GL_ATI_meminfo") == 0 ){ else if ( strcmp( (const char*)ccc, "GL_ATI_meminfo") == 0 ){
meminfomode = 2; meminfomode = 2;
break; break;
@@ -369,46 +410,32 @@ void FrameBuffer::checkFramebufferStatus()
} }
GLint framebufferMemoryInKB = ( width() * height() * 5 ) / 1024;
// NVIDIA // NVIDIA
if (meminfomode == 1) { if (meminfomode == 1) {
static GLint nTotalMemoryInKB = -1; static GLint nTotalMemoryInKB = -1;
if (nTotalMemoryInKB<0) if (nTotalMemoryInKB<0)
glGetIntegerv( GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX, &nTotalMemoryInKB ); glGetIntegerv( GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX, &nTotalMemoryInKB );
ret.y = nTotalMemoryInKB;
GLint nCurAvailMemoryInKB = 0; glGetIntegerv( GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX, &ret.x );
glGetIntegerv( GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX, &nCurAvailMemoryInKB );
if ( nCurAvailMemoryInKB < framebufferMemoryInKB )
Log::Warning("Cannot allocate frame buffer: only %d kB GPU RAM remaining on the %d kB available (%d %%).",
nCurAvailMemoryInKB, nTotalMemoryInKB, int(float(nCurAvailMemoryInKB) / float(nTotalMemoryInKB) * 100.f ) );
#ifdef FRAMEBUFFER_DEBUG
else
g_printerr("Framebuffer created %d x %d, %d kB allocated (%d kB remaining on %d kB)\n", width(), height(), framebufferMemoryInKB, nCurAvailMemoryInKB, nTotalMemoryInKB);
#endif
} }
// ATI // ATI
else if (meminfomode == 2) { else if (meminfomode == 2) {
glGetIntegerv( GL_TEXTURE_FREE_MEMORY_ATI, &ret.x );
GLint nCurAvailMemoryInKB = 0;
glGetIntegerv( GL_TEXTURE_FREE_MEMORY_ATI, &nCurAvailMemoryInKB );
if ( nCurAvailMemoryInKB < framebufferMemoryInKB )
Log::Warning("Cannot allocate frame buffer: only %d kB GPU RAM remaining.", nCurAvailMemoryInKB );
#ifdef FRAMEBUFFER_DEBUG
else
g_printerr("Framebuffer created %d x %d, %d kB allocated (%d kB remaining)\n", width(), height(), framebufferMemoryInKB, nCurAvailMemoryInKB);
#endif
} }
#ifdef FRAMEBUFFER_DEBUG
else
g_printerr("Framebuffer created %d x %d, %d kB allocated\n", width(), height(), framebufferMemoryInKB);
#endif
} return ret;
break; }
}
bool FrameBuffer::shouldHaveEnoughMemory(glm::vec3 resolution, bool useAlpha, bool multiSampling)
{
glm::ivec2 RAM = getGPUMemoryInformation();
// approximation of RAM needed for such FBO
GLint framebufferMemoryInKB = ( resolution.x * resolution.x * (useAlpha?4:3) * (multiSampling?2:1) ) / 1024;
return ( RAM.x > framebufferMemoryInKB * 3 );
} }

View File

@@ -44,8 +44,9 @@ public:
static float resolution_height[5]; static float resolution_height[5];
static glm::vec3 getResolutionFromParameters(int ar, int h); static glm::vec3 getResolutionFromParameters(int ar, int h);
static glm::ivec2 getParametersFromResolution(glm::vec3 res); static glm::ivec2 getParametersFromResolution(glm::vec3 res);
// unbind any framebuffer object // memory management
static void release(); static glm::ivec2 getGPUMemoryInformation();
static bool shouldHaveEnoughMemory(glm::vec3 resolution, bool useAlpha = false, bool multiSampling = false);
FrameBuffer(glm::vec3 resolution, bool useAlpha = false, bool multiSampling = false); FrameBuffer(glm::vec3 resolution, bool useAlpha = false, bool multiSampling = false);
FrameBuffer(uint width, uint height, bool useAlpha = false, bool multiSampling = false); FrameBuffer(uint width, uint height, bool useAlpha = false, bool multiSampling = false);
@@ -56,6 +57,8 @@ public:
void begin(bool clear = true); void begin(bool clear = true);
// pop attrib and unbind to end draw // pop attrib and unbind to end draw
void end(); void end();
// unbind (any) framebuffer object
static void release();
// blit copy to another, returns true on success // blit copy to another, returns true on success
bool blit(FrameBuffer *destination); bool blit(FrameBuffer *destination);
// bind the FrameBuffer in READ and perform glReadPixels // bind the FrameBuffer in READ and perform glReadPixels
@@ -101,7 +104,6 @@ private:
uint textureid_, intermediate_textureid_; uint textureid_, intermediate_textureid_;
uint framebufferid_, intermediate_framebufferid_; uint framebufferid_, intermediate_framebufferid_;
bool use_alpha_, use_multi_sampling_; bool use_alpha_, use_multi_sampling_;
}; };