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()) ) )
// add element to queue (back) garbage_image_ = new FrameBuffer( origin_->frame()->resolution(), origin_->frame()->use_alpha() );
images_.push( to_delete_ ); // no image available
elapsed_.push( now ); if (garbage_image_ != nullptr) {
timestamps_.push( origin_->playtime() ); // add element to queue (back)
// to_delete_ FBO is now used, should not be deleted images_.push( garbage_image_ );
to_delete_ = nullptr; elapsed_.push( now );
timestamps_.push( origin_->playtime() );
// garbage_image_ FBO is now used, it should not be deleted
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
} }
@@ -313,102 +313,129 @@ void FrameBuffer::checkFramebufferStatus()
{ {
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
switch (status){ switch (status){
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT is returned if any of the framebuffer attachment points are framebuffer incomplete."); Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT is returned if any of the framebuffer attachment points are framebuffer incomplete.");
break; break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT is returned if the framebuffer does not have at least one image attached to it."); Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT is returned if the framebuffer does not have at least one image attached to it.");
break; break;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER is returned if the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for any color " Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER is returned if the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for any color "
"attachment point(s) named by GL_DRAWBUFFERi."); "attachment point(s) named by GL_DRAWBUFFERi.");
break; break;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER is returned if GL_READ_BUFFER is not GL_NONE and the value of " Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER is returned if GL_READ_BUFFER is not GL_NONE and the value of "
"GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for the color attachment point named by GL_READ_BUFFER."); "GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for the color attachment point named by GL_READ_BUFFER.");
break; break;
case GL_FRAMEBUFFER_UNSUPPORTED: case GL_FRAMEBUFFER_UNSUPPORTED:
Log::Warning("GL_FRAMEBUFFER_UNSUPPORTED is returned if the combination of internal formats of the attached images violates an " Log::Warning("GL_FRAMEBUFFER_UNSUPPORTED is returned if the combination of internal formats of the attached images violates an "
"implementation-dependent set of restrictions."); "implementation-dependent set of restrictions.");
break; break;
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is returned if the value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; " Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is returned if the value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; "
"if the value of GL_TEXTURE_SAMPLES is the not same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of " "if the value of GL_TEXTURE_SAMPLES is the not same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of "
"GL_RENDERBUFFER_SAMPLES does not match the value of GL_TEXTURE_SAMPLES.\nGL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is also returned if the value of " "GL_RENDERBUFFER_SAMPLES does not match the value of GL_TEXTURE_SAMPLES.\nGL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is also returned if the value of "
"GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not the same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, " "GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not the same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, "
"the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not GL_TRUE for all attached textures."); "the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not GL_TRUE for all attached textures.");
break; break;
case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS is returned if any framebuffer attachment is layered, and any populated attachment is not layered," Log::Warning("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS is returned if any framebuffer attachment is layered, and any populated attachment is not layered,"
" or if all populated color attachments are not from textures of the same target."); " or if all populated color attachments are not from textures of the same target.");
break; break;
case GL_FRAMEBUFFER_UNDEFINED: case GL_FRAMEBUFFER_UNDEFINED:
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:
{ {
static int meminfomode = -1;
// first time check which way to get memory info
if (meminfomode<0) {
meminfomode = 0;
GLint numExtensions = 0;
glGetIntegerv( GL_NUM_EXTENSIONS, &numExtensions );
for (int i = 0; i < numExtensions; ++i){
const GLubyte *ccc = glGetStringi(GL_EXTENSIONS, i); // approximation of RAM needed for this FBO
if ( strcmp( (const char*)ccc, "GL_NVX_gpu_memory_info") == 0 ){ GLint framebufferMemoryInKB = ( width() * height() * (use_alpha_?4:3) * (use_multi_sampling_?2:1) ) / 1024;
meminfomode = 1;
break;
}
else if ( strcmp( (const char*)ccc, "GL_ATI_meminfo") == 0 ){
meminfomode = 2;
break;
}
}
} // test available memory if created buffer is big (more than 8MB)
if ( framebufferMemoryInKB > 8000 ) {
GLint framebufferMemoryInKB = ( width() * height() * 5 ) / 1024; // Obtain RAM usage in GPU (if possible)
glm::ivec2 RAM = getGPUMemoryInformation();
// NVIDIA // bad case: not enough RAM, we should warn the user
if (meminfomode == 1) { if ( RAM.x < framebufferMemoryInKB * 3 ) {
static GLint nTotalMemoryInKB = -1; Log::Warning("Critical allocation of frame buffer: only %d kB RAM remaining in graphics card.", RAM.x );
if (nTotalMemoryInKB<0) if (RAM.y < INT_MAX)
glGetIntegerv( GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX, &nTotalMemoryInKB ); Log::Warning("Only %.1f %% of %d kB available.", 100.f*float(RAM.x)/float(RAM.y), RAM.y);
GLint nCurAvailMemoryInKB = 0;
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
else if (meminfomode == 2) {
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 #ifdef FRAMEBUFFER_DEBUG
else else {
g_printerr("Framebuffer created %d x %d, %d kB allocated\n", width(), height(), framebufferMemoryInKB); // 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 #endif
}
} }
break; 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;
if (meminfomode<0) {
// initialized
meminfomode = 0;
GLint numExtensions = 0;
glGetIntegerv( GL_NUM_EXTENSIONS, &numExtensions );
for (int i = 0; i < numExtensions; ++i){
const GLubyte *ccc = glGetStringi(GL_EXTENSIONS, i);
// NVIDIA extension available
if ( strcmp( (const char*)ccc, "GL_NVX_gpu_memory_info") == 0 ){
meminfomode = 1;
break;
}
// ATI extension available
else if ( strcmp( (const char*)ccc, "GL_ATI_meminfo") == 0 ){
meminfomode = 2;
break;
}
}
}
// NVIDIA
if (meminfomode == 1) {
static GLint nTotalMemoryInKB = -1;
if (nTotalMemoryInKB<0)
glGetIntegerv( GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX, &nTotalMemoryInKB );
ret.y = nTotalMemoryInKB;
glGetIntegerv( GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX, &ret.x );
}
// ATI
else if (meminfomode == 2) {
glGetIntegerv( GL_TEXTURE_FREE_MEMORY_ATI, &ret.x );
}
return ret;
}
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_;
}; };