Bladeren bron

released-vbuffer-cache

David Rose 18 jaren geleden
bovenliggende
commit
be8b867498

+ 18 - 0
panda/src/gobj/config_gobj.cxx

@@ -250,6 +250,24 @@ ConfigVariableInt geom_cache_min_frames
           "object will remain in the geom cache, even if geom-cache-size "
           "object will remain in the geom cache, even if geom-cache-size "
           "is exceeded."));
           "is exceeded."));
 
 
+ConfigVariableInt released_vbuffer_cache_size
+("released-vbuffer-cache-size", 1048576,
+ PRC_DESC("Specifies the size in bytes of the cache of vertex "
+          "buffers that have recently been released.  If a new vertex "
+          "buffer is prepared while a recently-released one of the same "
+          "size is still in the cache, that same buffer is recycled.  This "
+          "cuts down on the overhead of creating and destroying vertex "
+          "buffers on the graphics card."));
+
+ConfigVariableInt released_ibuffer_cache_size
+("released-ibuffer-cache-size", 102400,
+ PRC_DESC("Specifies the size in bytes of the cache of index "
+          "buffers that have recently been released.  If a new index "
+          "buffer is prepared while a recently-released one of the same "
+          "size is still in the cache, that same buffer is recycled.  This "
+          "cuts down on the overhead of creating and destroying index "
+          "buffers on the graphics card."));
+
 ConfigVariableDouble default_near
 ConfigVariableDouble default_near
 ("default-near", 1.0,
 ("default-near", 1.0,
  PRC_DESC("The default near clipping distance for all cameras."));
  PRC_DESC("The default near clipping distance for all cameras."));

+ 2 - 0
panda/src/gobj/config_gobj.h

@@ -63,6 +63,8 @@ extern EXPCL_PANDA_GOBJ ConfigVariableBool textures_header_only;
 
 
 extern EXPCL_PANDA_GOBJ ConfigVariableInt geom_cache_size;
 extern EXPCL_PANDA_GOBJ ConfigVariableInt geom_cache_size;
 extern EXPCL_PANDA_GOBJ ConfigVariableInt geom_cache_min_frames;
 extern EXPCL_PANDA_GOBJ ConfigVariableInt geom_cache_min_frames;
+extern EXPCL_PANDA_GOBJ ConfigVariableInt released_vbuffer_cache_size;
+extern EXPCL_PANDA_GOBJ ConfigVariableInt released_ibuffer_cache_size;
 
 
 extern EXPCL_PANDA_GOBJ ConfigVariableDouble default_near;
 extern EXPCL_PANDA_GOBJ ConfigVariableDouble default_near;
 extern EXPCL_PANDA_GOBJ ConfigVariableDouble default_far;
 extern EXPCL_PANDA_GOBJ ConfigVariableDouble default_far;

+ 64 - 0
panda/src/gobj/geomEnums.cxx

@@ -17,8 +17,68 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 #include "geomEnums.h"
 #include "geomEnums.h"
+#include "string_utils.h"
+#include "config_gobj.h"
 
 
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomEnums::UsageHint output operator
+//  Description: 
+////////////////////////////////////////////////////////////////////
+ostream &
+operator << (ostream &out, GeomEnums::UsageHint usage_hint) {
+  switch (usage_hint) {
+  case GeomEnums::UH_client:
+    return out << "client";
+
+  case GeomEnums::UH_stream:
+    return out << "stream";
+
+  case GeomEnums::UH_dynamic:
+    return out << "dynamic";
+
+  case GeomEnums::UH_static:
+    return out << "static";
+
+  case GeomEnums::UH_unspecified:
+    return out << "unspecified";
+  }
+
+  return out << "**invalid usage hint (" << (int)usage_hint << ")**";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeomEnums::UsageHint input operator
+//  Description: 
+////////////////////////////////////////////////////////////////////
+istream &
+operator >> (istream &in, GeomEnums::UsageHint &usage_hint) {
+  string word;
+  in >> word;
+
+  if (cmp_nocase(word, "client") == 0) {
+    usage_hint = GeomEnums::UH_client;
+  } else if (cmp_nocase(word, "stream") == 0) {
+    usage_hint = GeomEnums::UH_stream;
+  } else if (cmp_nocase(word, "dynamic") == 0) {
+    usage_hint = GeomEnums::UH_dynamic;
+  } else if (cmp_nocase(word, "static") == 0) {
+    usage_hint = GeomEnums::UH_static;
+  } else if (cmp_nocase(word, "unspecified") == 0) {
+    usage_hint = GeomEnums::UH_unspecified;
+
+  } else {
+    gobj_cat->error() << "Invalid usage hint value: " << word << "\n";
+    usage_hint = GeomEnums::UH_unspecified;
+  }
+
+  return in;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeomEnums::NumericType output operator
+//  Description: 
+////////////////////////////////////////////////////////////////////
 ostream &
 ostream &
 operator << (ostream &out, GeomEnums::NumericType numeric_type) {
 operator << (ostream &out, GeomEnums::NumericType numeric_type) {
   switch (numeric_type) {
   switch (numeric_type) {
@@ -44,6 +104,10 @@ operator << (ostream &out, GeomEnums::NumericType numeric_type) {
   return out << "**invalid numeric type (" << (int)numeric_type << ")**";
   return out << "**invalid numeric type (" << (int)numeric_type << ")**";
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomEnums::Contents output operator
+//  Description: 
+////////////////////////////////////////////////////////////////////
 ostream &
 ostream &
 operator << (ostream &out, GeomEnums::Contents contents) {
 operator << (ostream &out, GeomEnums::Contents contents) {
   switch (contents) {
   switch (contents) {

+ 3 - 1
panda/src/gobj/geomEnums.h

@@ -67,7 +67,7 @@ PUBLISHED:
     UH_static,
     UH_static,
 
 
     // UH_unspecified: the usage is unspecified.  This is intended as
     // UH_unspecified: the usage is unspecified.  This is intended as
-    // a "don't care" option for abstract objects, it should not be
+    // a "don't care" option for abstract objects; it should not be
     // applied to any actual geometry to be rendered.  You take your
     // applied to any actual geometry to be rendered.  You take your
     // chances if a geom actually gets into the scene graph with this
     // chances if a geom actually gets into the scene graph with this
     // set.
     // set.
@@ -211,6 +211,8 @@ PUBLISHED:
   };
   };
 };
 };
 
 
+EXPCL_PANDA_GOBJ ostream &operator << (ostream &out, GeomEnums::UsageHint usage_hint);
+EXPCL_PANDA_GOBJ istream &operator >> (istream &in, GeomEnums::UsageHint &usage_hint);
 EXPCL_PANDA_GOBJ ostream &operator << (ostream &out, GeomEnums::NumericType numeric_type);
 EXPCL_PANDA_GOBJ ostream &operator << (ostream &out, GeomEnums::NumericType numeric_type);
 EXPCL_PANDA_GOBJ ostream &operator << (ostream &out, GeomEnums::Contents contents);
 EXPCL_PANDA_GOBJ ostream &operator << (ostream &out, GeomEnums::Contents contents);
 
 

+ 34 - 0
panda/src/gobj/preparedGraphicsObjects.I

@@ -72,3 +72,37 @@ get_num_prepared() const {
           get_num_prepared_vertex_buffers() +
           get_num_prepared_vertex_buffers() +
           get_num_prepared_index_buffers());
           get_num_prepared_index_buffers());
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PreparedGraphicsObjects::BufferCacheKey::operator <
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE bool PreparedGraphicsObjects::BufferCacheKey::
+operator < (const PreparedGraphicsObjects::BufferCacheKey &other) const {
+  if (_data_size_bytes != other._data_size_bytes) {
+    return _data_size_bytes < other._data_size_bytes;
+  }
+  return (int)_usage_hint < (int)other._usage_hint;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PreparedGraphicsObjects::BufferCacheKey::operator ==
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE bool PreparedGraphicsObjects::BufferCacheKey::
+operator == (const PreparedGraphicsObjects::BufferCacheKey &other) const {
+  return (_data_size_bytes == other._data_size_bytes &&
+          _usage_hint == other._usage_hint);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PreparedGraphicsObjects::BufferCacheKey::operator !=
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE bool PreparedGraphicsObjects::BufferCacheKey::
+operator != (const PreparedGraphicsObjects::BufferCacheKey &other) const {
+  return !operator == (other);
+}

+ 205 - 63
panda/src/gobj/preparedGraphicsObjects.cxx

@@ -42,7 +42,9 @@ PreparedGraphicsObjects() :
   _name(init_name()),
   _name(init_name()),
   _texture_residency(_name, "texture"),
   _texture_residency(_name, "texture"),
   _vbuffer_residency(_name, "vbuffer"),
   _vbuffer_residency(_name, "vbuffer"),
-  _ibuffer_residency(_name, "ibuffer")
+  _ibuffer_residency(_name, "ibuffer"),
+  _vertex_buffer_cache_size(0),
+  _index_buffer_cache_size(0)
 {
 {
 }
 }
 
 
@@ -59,68 +61,41 @@ PreparedGraphicsObjects::
   // cleaned up.  Quietly erase these remaining objects.
   // cleaned up.  Quietly erase these remaining objects.
   ReMutexHolder holder(_lock);
   ReMutexHolder holder(_lock);
 
 
+  release_all_textures();
   Textures::iterator tci;
   Textures::iterator tci;
-  for (tci = _prepared_textures.begin();
-       tci != _prepared_textures.end();
+  for (tci = _released_textures.begin();
+       tci != _released_textures.end();
        ++tci) {
        ++tci) {
     TextureContext *tc = (*tci);
     TextureContext *tc = (*tci);
-    tc->get_texture()->clear_prepared(this);
     tc->set_owning_chain(NULL);
     tc->set_owning_chain(NULL);
   }
   }
-
-  _prepared_textures.clear();
   _released_textures.clear();
   _released_textures.clear();
-  _enqueued_textures.clear();
-
-  Geoms::iterator gci;
-  for (gci = _prepared_geoms.begin();
-       gci != _prepared_geoms.end();
-       ++gci) {
-    GeomContext *gc = (*gci);
-    gc->_geom->clear_prepared(this);
-  }
 
 
-  _prepared_geoms.clear();
+  release_all_geoms();
   _released_geoms.clear();
   _released_geoms.clear();
-  _enqueued_geoms.clear();
 
 
-  Shaders::iterator sci;
-  for (sci = _prepared_shaders.begin();
-       sci != _prepared_shaders.end();
-       ++sci) {
-    ShaderContext *sc = (*sci);
-    sc->_expansion->clear_prepared(this);
-  }
-
-  _prepared_shaders.clear();
+  release_all_shaders();
   _released_shaders.clear();
   _released_shaders.clear();
-  _enqueued_shaders.clear();
 
 
-  VertexBuffers::iterator vbci;
-  for (vbci = _prepared_vertex_buffers.begin();
-       vbci != _prepared_vertex_buffers.end();
+  release_all_vertex_buffers();
+  Buffers::iterator vbci;
+  for (vbci = _released_vertex_buffers.begin();
+       vbci != _released_vertex_buffers.end();
        ++vbci) {
        ++vbci) {
-    VertexBufferContext *vbc = (*vbci);
-    vbc->_data->clear_prepared(this);
+    VertexBufferContext *vbc = (VertexBufferContext *)(*vbci);
     vbc->set_owning_chain(NULL);
     vbc->set_owning_chain(NULL);
   }
   }
-
-  _prepared_vertex_buffers.clear();
   _released_vertex_buffers.clear();
   _released_vertex_buffers.clear();
-  _enqueued_vertex_buffers.clear();
 
 
-  IndexBuffers::iterator ibci;
-  for (ibci = _prepared_index_buffers.begin();
-       ibci != _prepared_index_buffers.end();
+  release_all_index_buffers();
+  Buffers::iterator ibci;
+  for (ibci = _released_index_buffers.begin();
+       ibci != _released_index_buffers.end();
        ++ibci) {
        ++ibci) {
-    IndexBufferContext *ibc = (*ibci);
-    ibc->_data->clear_prepared(this);
+    IndexBufferContext *ibc = (IndexBufferContext *)(*ibci);
     ibc->set_owning_chain(NULL);
     ibc->set_owning_chain(NULL);
   }
   }
-
-  _prepared_index_buffers.clear();
   _released_index_buffers.clear();
   _released_index_buffers.clear();
-  _enqueued_index_buffers.clear();
 }
 }
 
 
 
 
@@ -772,6 +747,9 @@ release_vertex_buffer(VertexBufferContext *vbc) {
 
 
   vbc->_data->clear_prepared(this);
   vbc->_data->clear_prepared(this);
 
 
+  size_t data_size_bytes = vbc->_data->get_data_size_bytes();
+  GeomEnums::UsageHint usage_hint = vbc->_data->get_usage_hint();
+
   // We have to set the Data pointer to NULL at this point, since
   // We have to set the Data pointer to NULL at this point, since
   // the Data itself might destruct at any time after it has been
   // the Data itself might destruct at any time after it has been
   // released.
   // released.
@@ -780,7 +758,11 @@ release_vertex_buffer(VertexBufferContext *vbc) {
   bool removed = (_prepared_vertex_buffers.erase(vbc) != 0);
   bool removed = (_prepared_vertex_buffers.erase(vbc) != 0);
   nassertv(removed);
   nassertv(removed);
 
 
-  _released_vertex_buffers.insert(vbc);
+  cache_unprepared_buffer(vbc, data_size_bytes, usage_hint,
+                          _vertex_buffer_cache,
+                          _vertex_buffer_cache_lru, _vertex_buffer_cache_size,
+                          released_vbuffer_cache_size,
+                          _released_vertex_buffers);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -797,11 +779,11 @@ release_all_vertex_buffers() {
 
 
   int num_vertex_buffers = (int)_prepared_vertex_buffers.size() + (int)_enqueued_vertex_buffers.size();
   int num_vertex_buffers = (int)_prepared_vertex_buffers.size() + (int)_enqueued_vertex_buffers.size();
 
 
-  VertexBuffers::iterator vbci;
+  Buffers::iterator vbci;
   for (vbci = _prepared_vertex_buffers.begin();
   for (vbci = _prepared_vertex_buffers.begin();
        vbci != _prepared_vertex_buffers.end();
        vbci != _prepared_vertex_buffers.end();
        ++vbci) {
        ++vbci) {
-    VertexBufferContext *vbc = (*vbci);
+    VertexBufferContext *vbc = (VertexBufferContext *)(*vbci);
     vbc->_data->clear_prepared(this);
     vbc->_data->clear_prepared(this);
     vbc->_data = (GeomVertexArrayData *)NULL;
     vbc->_data = (GeomVertexArrayData *)NULL;
 
 
@@ -811,6 +793,23 @@ release_all_vertex_buffers() {
   _prepared_vertex_buffers.clear();
   _prepared_vertex_buffers.clear();
   _enqueued_vertex_buffers.clear();
   _enqueued_vertex_buffers.clear();
 
 
+  // Also clear the cache of recently-unprepared vertex buffers.
+  BufferCache::iterator bci;
+  for (bci = _vertex_buffer_cache.begin(); 
+       bci != _vertex_buffer_cache.end(); 
+       ++bci) {
+    BufferList &buffer_list = (*bci).second;
+    nassertr(!buffer_list.empty(), num_vertex_buffers);
+    BufferList::iterator li;
+    for (li = buffer_list.begin(); li != buffer_list.end(); ++li) {
+      VertexBufferContext *vbc = (VertexBufferContext *)(*li);
+      _released_vertex_buffers.insert(vbc);
+    }
+  }
+  _vertex_buffer_cache.clear();
+  _vertex_buffer_cache_lru.clear();
+  _vertex_buffer_cache_size = 0;
+  
   return num_vertex_buffers;
   return num_vertex_buffers;
 }
 }
 
 
@@ -862,11 +861,24 @@ VertexBufferContext *PreparedGraphicsObjects::
 prepare_vertex_buffer_now(GeomVertexArrayData *data, GraphicsStateGuardianBase *gsg) {
 prepare_vertex_buffer_now(GeomVertexArrayData *data, GraphicsStateGuardianBase *gsg) {
   ReMutexHolder holder(_lock);
   ReMutexHolder holder(_lock);
 
 
-  // Ask the GSG to create a brand new VertexBufferContext.  There might
-  // be several GSG's sharing the same set of datas; if so, it
-  // doesn't matter which of them creates the context (since they're
-  // all shared anyway).
-  VertexBufferContext *vbc = gsg->prepare_vertex_buffer(data);
+  // First, see if there might be a cached context of the appropriate
+  // size.
+  size_t data_size_bytes = data->get_data_size_bytes();
+  GeomEnums::UsageHint usage_hint = data->get_usage_hint();
+  VertexBufferContext *vbc = (VertexBufferContext *)
+    get_cached_buffer(data_size_bytes, usage_hint,
+                      _vertex_buffer_cache, _vertex_buffer_cache_lru,
+                      _vertex_buffer_cache_size);
+  if (vbc != (VertexBufferContext *)NULL) {
+    vbc->_data = data;
+
+  } else {
+    // Ask the GSG to create a brand new VertexBufferContext.  There
+    // might be several GSG's sharing the same set of datas; if so, it
+    // doesn't matter which of them creates the context (since they're
+    // all shared anyway).
+    vbc = gsg->prepare_vertex_buffer(data);
+  }
 
 
   if (vbc != (VertexBufferContext *)NULL) {
   if (vbc != (VertexBufferContext *)NULL) {
     bool prepared = _prepared_vertex_buffers.insert(vbc).second;
     bool prepared = _prepared_vertex_buffers.insert(vbc).second;
@@ -960,6 +972,9 @@ release_index_buffer(IndexBufferContext *ibc) {
 
 
   ibc->_data->clear_prepared(this);
   ibc->_data->clear_prepared(this);
 
 
+  size_t data_size_bytes = ibc->_data->get_data_size_bytes();
+  GeomEnums::UsageHint usage_hint = ibc->_data->get_usage_hint();
+
   // We have to set the Data pointer to NULL at this point, since
   // We have to set the Data pointer to NULL at this point, since
   // the Data itself might destruct at any time after it has been
   // the Data itself might destruct at any time after it has been
   // released.
   // released.
@@ -968,7 +983,11 @@ release_index_buffer(IndexBufferContext *ibc) {
   bool removed = (_prepared_index_buffers.erase(ibc) != 0);
   bool removed = (_prepared_index_buffers.erase(ibc) != 0);
   nassertv(removed);
   nassertv(removed);
 
 
-  _released_index_buffers.insert(ibc);
+  cache_unprepared_buffer(ibc, data_size_bytes, usage_hint,
+                          _index_buffer_cache,
+                          _index_buffer_cache_lru, _index_buffer_cache_size,
+                          released_ibuffer_cache_size,
+                          _released_index_buffers);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -985,11 +1004,11 @@ release_all_index_buffers() {
 
 
   int num_index_buffers = (int)_prepared_index_buffers.size() + (int)_enqueued_index_buffers.size();
   int num_index_buffers = (int)_prepared_index_buffers.size() + (int)_enqueued_index_buffers.size();
 
 
-  IndexBuffers::iterator ibci;
+  Buffers::iterator ibci;
   for (ibci = _prepared_index_buffers.begin();
   for (ibci = _prepared_index_buffers.begin();
        ibci != _prepared_index_buffers.end();
        ibci != _prepared_index_buffers.end();
        ++ibci) {
        ++ibci) {
-    IndexBufferContext *ibc = (*ibci);
+    IndexBufferContext *ibc = (IndexBufferContext *)(*ibci);
     ibc->_data->clear_prepared(this);
     ibc->_data->clear_prepared(this);
     ibc->_data = (GeomPrimitive *)NULL;
     ibc->_data = (GeomPrimitive *)NULL;
 
 
@@ -999,6 +1018,23 @@ release_all_index_buffers() {
   _prepared_index_buffers.clear();
   _prepared_index_buffers.clear();
   _enqueued_index_buffers.clear();
   _enqueued_index_buffers.clear();
 
 
+  // Also clear the cache of recently-unprepared index buffers.
+  BufferCache::iterator bci;
+  for (bci = _index_buffer_cache.begin(); 
+       bci != _index_buffer_cache.end(); 
+       ++bci) {
+    BufferList &buffer_list = (*bci).second;
+    nassertr(!buffer_list.empty(), num_index_buffers);
+    BufferList::iterator li;
+    for (li = buffer_list.begin(); li != buffer_list.end(); ++li) {
+      IndexBufferContext *vbc = (IndexBufferContext *)(*li);
+      _released_index_buffers.insert(vbc);
+    }
+  }
+  _index_buffer_cache.clear();
+  _index_buffer_cache_lru.clear();
+  _index_buffer_cache_size = 0;
+
   return num_index_buffers;
   return num_index_buffers;
 }
 }
 
 
@@ -1050,11 +1086,24 @@ IndexBufferContext *PreparedGraphicsObjects::
 prepare_index_buffer_now(GeomPrimitive *data, GraphicsStateGuardianBase *gsg) {
 prepare_index_buffer_now(GeomPrimitive *data, GraphicsStateGuardianBase *gsg) {
   ReMutexHolder holder(_lock);
   ReMutexHolder holder(_lock);
 
 
-  // Ask the GSG to create a brand new IndexBufferContext.  There might
-  // be several GSG's sharing the same set of datas; if so, it
-  // doesn't matter which of them creates the context (since they're
-  // all shared anyway).
-  IndexBufferContext *ibc = gsg->prepare_index_buffer(data);
+  // First, see if there might be a cached context of the appropriate
+  // size.
+  size_t data_size_bytes = data->get_data_size_bytes();
+  GeomEnums::UsageHint usage_hint = data->get_usage_hint();
+  IndexBufferContext *ibc = (IndexBufferContext *)
+    get_cached_buffer(data_size_bytes, usage_hint,
+                      _index_buffer_cache, _index_buffer_cache_lru,
+                      _index_buffer_cache_size);
+  if (ibc != (IndexBufferContext *)NULL) {
+    ibc->_data = data;
+
+  } else {
+    // Ask the GSG to create a brand new IndexBufferContext.  There
+    // might be several GSG's sharing the same set of datas; if so, it
+    // doesn't matter which of them creates the context (since they're
+    // all shared anyway).
+    ibc = gsg->prepare_index_buffer(data);
+  }
 
 
   if (ibc != (IndexBufferContext *)NULL) {
   if (ibc != (IndexBufferContext *)NULL) {
     bool prepared = _prepared_index_buffers.insert(ibc).second;
     bool prepared = _prepared_index_buffers.insert(ibc).second;
@@ -1114,21 +1163,21 @@ begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
 
 
   _released_shaders.clear();
   _released_shaders.clear();
 
 
-  VertexBuffers::iterator vbci;
+  Buffers::iterator vbci;
   for (vbci = _released_vertex_buffers.begin();
   for (vbci = _released_vertex_buffers.begin();
        vbci != _released_vertex_buffers.end();
        vbci != _released_vertex_buffers.end();
        ++vbci) {
        ++vbci) {
-    VertexBufferContext *vbc = (*vbci);
+    VertexBufferContext *vbc = (VertexBufferContext *)(*vbci);
     gsg->release_vertex_buffer(vbc);
     gsg->release_vertex_buffer(vbc);
   }
   }
 
 
   _released_vertex_buffers.clear();
   _released_vertex_buffers.clear();
 
 
-  IndexBuffers::iterator ibci;
+  Buffers::iterator ibci;
   for (ibci = _released_index_buffers.begin();
   for (ibci = _released_index_buffers.begin();
        ibci != _released_index_buffers.end();
        ibci != _released_index_buffers.end();
        ++ibci) {
        ++ibci) {
-    IndexBufferContext *ibc = (*ibci);
+    IndexBufferContext *ibc = (IndexBufferContext *)(*ibci);
     gsg->release_index_buffer(ibc);
     gsg->release_index_buffer(ibc);
   }
   }
 
 
@@ -1221,3 +1270,96 @@ init_name() {
   strm << "context" << _name_index;
   strm << "context" << _name_index;
   return strm.str();
   return strm.str();
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PreparedGraphicsObjects::cache_unprepared_buffer
+//       Access: Private
+//  Description: Called when a vertex or index buffer is no longer
+//               officially "prepared".  However, we still have the
+//               context on the graphics card, and we might be able to
+//               reuse that context if we're about to re-prepare a
+//               different buffer, especially one exactly the same
+//               size.  So instead of immediately enqueuing the vertex
+//               buffer for release, we cache it.
+////////////////////////////////////////////////////////////////////
+void PreparedGraphicsObjects::
+cache_unprepared_buffer(BufferContext *buffer, size_t data_size_bytes,
+                        GeomEnums::UsageHint usage_hint,
+                        PreparedGraphicsObjects::BufferCache &buffer_cache,
+                        PreparedGraphicsObjects::BufferCacheLRU &buffer_cache_lru,
+                        size_t &buffer_cache_size, 
+                        int released_buffer_cache_size,
+                        PreparedGraphicsObjects::Buffers &released_buffers) {
+  BufferCacheKey key;
+  key._data_size_bytes = data_size_bytes;
+  key._usage_hint = usage_hint;
+
+  buffer_cache[key].push_back(buffer);
+  buffer_cache_size += data_size_bytes;
+
+  // Move the key to the head of the LRU.
+  BufferCacheLRU::iterator li =
+    find(buffer_cache_lru.begin(), buffer_cache_lru.end(), key);
+  if (li != buffer_cache_lru.end()) {
+    buffer_cache_lru.erase(li);
+  }
+  buffer_cache_lru.insert(buffer_cache_lru.begin(), key);
+
+  // Now release not-recently-used buffers until we fit within the
+  // constrained size.
+  while ((int)buffer_cache_size > released_buffer_cache_size) {
+    nassertv(!buffer_cache_lru.empty());
+    const BufferCacheKey &release_key = *buffer_cache_lru.rbegin();
+    BufferList &buffer_list = buffer_cache[release_key];
+    while (!buffer_list.empty() && 
+           (int)buffer_cache_size > released_buffer_cache_size) {
+      BufferContext *released_buffer = buffer_list.back();
+      buffer_list.pop_back();
+      released_buffers.insert(released_buffer);
+      buffer_cache_size -= release_key._data_size_bytes;
+    }
+
+    if (buffer_list.empty()) {
+      buffer_cache.erase(release_key);
+      buffer_cache_lru.pop_back();
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PreparedGraphicsObjects::get_cached_buffer
+//       Access: Private
+//  Description: Returns a previously-cached buffer from the cache, or
+//               NULL if there is no such buffer.
+////////////////////////////////////////////////////////////////////
+BufferContext *PreparedGraphicsObjects::
+get_cached_buffer(size_t data_size_bytes, GeomEnums::UsageHint usage_hint,
+                  PreparedGraphicsObjects::BufferCache &buffer_cache,
+                  PreparedGraphicsObjects::BufferCacheLRU &buffer_cache_lru,
+                  size_t &buffer_cache_size) {
+  BufferCacheKey key;
+  key._data_size_bytes = data_size_bytes;
+  key._usage_hint = usage_hint;
+
+  BufferCache::iterator bci = buffer_cache.find(key);
+  if (bci == buffer_cache.end()) {
+    return NULL;
+  }
+
+  BufferList &buffer_list = (*bci).second;
+  nassertr(!buffer_list.empty(), NULL);
+
+  BufferContext *buffer = buffer_list.back();
+  buffer_list.pop_back();
+  if (buffer_list.empty()) {
+    buffer_cache.erase(bci);
+    BufferCacheLRU::iterator li =
+      find(buffer_cache_lru.begin(), buffer_cache_lru.end(), key);
+    if (li != buffer_cache_lru.end()) {
+      buffer_cache_lru.erase(li);
+    }
+  }
+
+  buffer_cache_size -= data_size_bytes;
+  return buffer;
+}

+ 36 - 4
panda/src/gobj/preparedGraphicsObjects.h

@@ -144,11 +144,35 @@ private:
   typedef phash_set< PT(Geom) > EnqueuedGeoms;
   typedef phash_set< PT(Geom) > EnqueuedGeoms;
   typedef phash_set<ShaderContext *, pointer_hash> Shaders;
   typedef phash_set<ShaderContext *, pointer_hash> Shaders;
   typedef phash_set< PT(ShaderExpansion) > EnqueuedShaders;
   typedef phash_set< PT(ShaderExpansion) > EnqueuedShaders;
-  typedef phash_set<VertexBufferContext *, pointer_hash> VertexBuffers;
+  typedef phash_set<BufferContext *, pointer_hash> Buffers;
   typedef phash_set< PT(GeomVertexArrayData) > EnqueuedVertexBuffers;
   typedef phash_set< PT(GeomVertexArrayData) > EnqueuedVertexBuffers;
-  typedef phash_set<IndexBufferContext *, pointer_hash> IndexBuffers;
   typedef phash_set< PT(GeomPrimitive) > EnqueuedIndexBuffers;
   typedef phash_set< PT(GeomPrimitive) > EnqueuedIndexBuffers;
 
 
+  class BufferCacheKey {
+  public:
+    INLINE bool operator < (const BufferCacheKey &other) const;
+    INLINE bool operator == (const BufferCacheKey &other) const;
+    INLINE bool operator != (const BufferCacheKey &other) const;
+    size_t _data_size_bytes;
+    GeomEnums::UsageHint _usage_hint;
+  };
+  typedef pvector<BufferContext *> BufferList;
+  typedef pmap<BufferCacheKey, BufferList> BufferCache;
+  typedef plist<BufferCacheKey> BufferCacheLRU;
+
+  void cache_unprepared_buffer(BufferContext *buffer, size_t data_size_bytes,
+                               GeomEnums::UsageHint usage_hint,
+                               BufferCache &buffer_cache,
+                               BufferCacheLRU &buffer_cache_lru,
+                               size_t &buffer_cache_size,
+                               int released_buffer_cache_size,
+                               Buffers &released_buffers);
+  BufferContext *get_cached_buffer(size_t data_size_bytes, 
+                                   GeomEnums::UsageHint usage_hint,
+                                   BufferCache &buffer_cache,
+                                   BufferCacheLRU &buffer_cache_lru,
+                                   size_t &buffer_cache_size);
+
   ReMutex _lock;
   ReMutex _lock;
   string _name;
   string _name;
   Textures _prepared_textures, _released_textures;  
   Textures _prepared_textures, _released_textures;  
@@ -157,11 +181,19 @@ private:
   EnqueuedGeoms _enqueued_geoms;
   EnqueuedGeoms _enqueued_geoms;
   Shaders _prepared_shaders, _released_shaders;
   Shaders _prepared_shaders, _released_shaders;
   EnqueuedShaders _enqueued_shaders;
   EnqueuedShaders _enqueued_shaders;
-  VertexBuffers _prepared_vertex_buffers, _released_vertex_buffers;  
+  Buffers _prepared_vertex_buffers, _released_vertex_buffers;  
   EnqueuedVertexBuffers _enqueued_vertex_buffers;
   EnqueuedVertexBuffers _enqueued_vertex_buffers;
-  IndexBuffers _prepared_index_buffers, _released_index_buffers;  
+  Buffers _prepared_index_buffers, _released_index_buffers;  
   EnqueuedIndexBuffers _enqueued_index_buffers;
   EnqueuedIndexBuffers _enqueued_index_buffers;
 
 
+  BufferCache _vertex_buffer_cache;
+  BufferCacheLRU _vertex_buffer_cache_lru;
+  size_t _vertex_buffer_cache_size;
+
+  BufferCache _index_buffer_cache;
+  BufferCacheLRU _index_buffer_cache_lru;
+  size_t _index_buffer_cache_size;
+
 public:
 public:
   BufferResidencyTracker _texture_residency;
   BufferResidencyTracker _texture_residency;
   BufferResidencyTracker _vbuffer_residency;
   BufferResidencyTracker _vbuffer_residency;