// Filename: preparedGraphicsObjects.cxx
// Created by:  drose (19Feb04)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "preparedGraphicsObjects.h"
#include "textureContext.h"
#include "vertexBufferContext.h"
#include "indexBufferContext.h"
#include "texture.h"
#include "geom.h"
#include "geomVertexArrayData.h"
#include "geomPrimitive.h"
#include "shader.h"
#include "reMutexHolder.h"
#include "geomContext.h"
#include "shaderContext.h"
#include "config_gobj.h"
#include "throw_event.h"

int PreparedGraphicsObjects::_name_index = 0;

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::Constructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
PreparedGraphicsObjects::
PreparedGraphicsObjects() : 
  _lock("PreparedGraphicsObjects::_lock"),
  _name(init_name()),
  _vertex_buffer_cache_size(0),
  _index_buffer_cache_size(0),
  _texture_residency(_name, "texture"),
  _vbuffer_residency(_name, "vbuffer"),
  _ibuffer_residency(_name, "ibuffer"),
  _graphics_memory_lru("graphics_memory_lru", graphics_memory_limit)
{
  // GLGSG will turn this flag on.  This is a temporary hack to
  // disable this feature for DX8/DX9 for now, until we work out the
  // fine points of updating the fvf properly.
  _support_released_buffer_cache = false;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::Destructor
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
PreparedGraphicsObjects::
~PreparedGraphicsObjects() {
  // There may be objects that are still prepared when we destruct.
  // If this is so, then all of the GSG's that own them have already
  // destructed, so we can assume their resources were internally
  // cleaned up.  Quietly erase these remaining objects.
  ReMutexHolder holder(_lock);

  release_all_textures();
  Textures::iterator tci;
  for (tci = _released_textures.begin();
       tci != _released_textures.end();
       ++tci) {
    TextureContext *tc = (*tci);
    tc->set_owning_chain(NULL);
  }
  // Is this a leak?  Should we delete these TextureContexts?
  _released_textures.clear();

  release_all_geoms();
  _released_geoms.clear();

  release_all_shaders();
  _released_shaders.clear();

  release_all_vertex_buffers();
  Buffers::iterator vbci;
  for (vbci = _released_vertex_buffers.begin();
       vbci != _released_vertex_buffers.end();
       ++vbci) {
    VertexBufferContext *vbc = (VertexBufferContext *)(*vbci);
    vbc->set_owning_chain(NULL);
  }
  _released_vertex_buffers.clear();

  release_all_index_buffers();
  Buffers::iterator ibci;
  for (ibci = _released_index_buffers.begin();
       ibci != _released_index_buffers.end();
       ++ibci) {
    IndexBufferContext *ibc = (IndexBufferContext *)(*ibci);
    ibc->set_owning_chain(NULL);
  }
  _released_index_buffers.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::set_graphics_memory_limit
//       Access: Public
//  Description: Sets an artificial cap on graphics memory that
//               will be imposed on this GSG.
//
//               This limits the total amount of graphics memory,
//               including texture memory and vertex buffer memory,
//               that will be consumed by the GSG, regardless of
//               whether the hardware claims to provide more graphics
//               memory than this. It is useful to put a ceiling on
//               graphics memory consumed, since some drivers seem to
//               allow the application to consume more memory than the
//               hardware can realistically support.
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
set_graphics_memory_limit(size_t limit) {
  if (limit != _graphics_memory_lru.get_max_size()) {
    _graphics_memory_lru.set_max_size(limit);
  
    // We throw an event here so global objects (particularly the
    // TexMemWatcher) can automatically respond to this change.
    throw_event("graphics_memory_limit_changed");
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::show_graphics_memory_lru
//       Access: Public
//  Description: Writes to the indicated ostream a report of how the
//               various textures and vertex buffers are allocated in
//               the LRU.
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
show_graphics_memory_lru(ostream &out) const {
  _graphics_memory_lru.write(out, 0);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::show_residency_trackers
//       Access: Public
//  Description: Writes to the indicated ostream a report of how the
//               various textures and vertex buffers are allocated in
//               the LRU.
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
show_residency_trackers(ostream &out) const {
  out << "Textures:\n";
  _texture_residency.write(out, 2);

  out << "\nVertex buffers:\n";
  _vbuffer_residency.write(out, 2);

  out << "\nIndex buffers:\n";
  _ibuffer_residency.write(out, 2);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::enqueue_texture
//       Access: Public
//  Description: Indicates that a texture would like to be put on the
//               list to be prepared when the GSG is next ready to
//               do this (presumably at the next frame).
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
enqueue_texture(Texture *tex) {
  ReMutexHolder holder(_lock);

  _enqueued_textures.insert(tex);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::is_texture_queued
//       Access: Public
//  Description: Returns true if the texture has been queued on this
//               GSG, false otherwise.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
is_texture_queued(const Texture *tex) const {
  ReMutexHolder holder(_lock);

  EnqueuedTextures::const_iterator qi = _enqueued_textures.find((Texture *)tex);
  return (qi != _enqueued_textures.end());
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::dequeue_texture
//       Access: Public
//  Description: Removes a texture from the queued list of textures to
//               be prepared.  Normally it is not necessary to call
//               this, unless you change your mind about preparing it
//               at the last minute, since the texture will
//               automatically be dequeued and prepared at the next
//               frame.
//
//               The return value is true if the texture is
//               successfully dequeued, false if it had not been
//               queued.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
dequeue_texture(Texture *tex) {
  ReMutexHolder holder(_lock);

  EnqueuedTextures::iterator qi = _enqueued_textures.find(tex);
  if (qi != _enqueued_textures.end()) {
    _enqueued_textures.erase(qi);
    return true;
  }
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::is_texture_prepared
//       Access: Public
//  Description: Returns true if the texture has been prepared on
//               this GSG, false otherwise.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
is_texture_prepared(const Texture *tex) const {
  return tex->is_prepared((PreparedGraphicsObjects *)this);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::release_texture
//       Access: Public
//  Description: Indicates that a texture context, created by a
//               previous call to prepare_texture(), is no longer
//               needed.  The driver resources will not be freed until
//               some GSG calls update(), indicating it is at a
//               stage where it is ready to release textures--this
//               prevents conflicts from threading or multiple GSG's
//               sharing textures (we have no way of knowing which
//               graphics context is currently active, or what state
//               it's in, at the time release_texture is called).
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
release_texture(TextureContext *tc) {
  ReMutexHolder holder(_lock);

  tc->_texture->clear_prepared(this);

  // We have to set the Texture pointer to NULL at this point, since
  // the Texture itself might destruct at any time after it has been
  // released.
  tc->_texture = (Texture *)NULL;

  bool removed = (_prepared_textures.erase(tc) != 0);
  nassertv(removed);

  _released_textures.insert(tc);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::release_texture
//       Access: Public
//  Description: Releases a texture if it has already been prepared,
//               or removes it from the preparation queue.
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
release_texture(Texture *tex) {
  tex->release(this);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::release_all_textures
//       Access: Public
//  Description: Releases all textures at once.  This will force them
//               to be reloaded into texture memory for all GSG's that
//               share this object.  Returns the number of textures
//               released.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
release_all_textures() {
  ReMutexHolder holder(_lock);

  int num_textures = (int)_prepared_textures.size() + (int)_enqueued_textures.size();

  Textures::iterator tci;
  for (tci = _prepared_textures.begin();
       tci != _prepared_textures.end();
       ++tci) {
    TextureContext *tc = (*tci);
    tc->_texture->clear_prepared(this);
    tc->_texture = (Texture *)NULL;

    _released_textures.insert(tc);
  }

  _prepared_textures.clear();
  _enqueued_textures.clear();

  return num_textures;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::get_num_queued_textures
//       Access: Public
//  Description: Returns the number of textures that have been
//               enqueued to be prepared on this GSG.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
get_num_queued_textures() const {
  return _enqueued_textures.size();
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::get_num_prepared_textures
//       Access: Public
//  Description: Returns the number of textures that have already been
//               prepared on this GSG.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
get_num_prepared_textures() const {
  return _prepared_textures.size();
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::prepare_texture_now
//       Access: Public
//  Description: Immediately creates a new TextureContext for the
//               indicated texture and returns it.  This assumes that
//               the GraphicsStateGuardian is the currently active
//               rendering context and that it is ready to accept new
//               textures.  If this is not necessarily the case, you
//               should use enqueue_texture() instead.
//
//               Normally, this function is not called directly.  Call
//               Texture::prepare_now() instead.
//
//               The TextureContext contains all of the pertinent
//               information needed by the GSG to keep track of this
//               one particular texture, and will exist as long as the
//               texture is ready to be rendered.
//
//               When either the Texture or the
//               PreparedGraphicsObjects object destructs, the
//               TextureContext will be deleted.
////////////////////////////////////////////////////////////////////
TextureContext *PreparedGraphicsObjects::
prepare_texture_now(Texture *tex, GraphicsStateGuardianBase *gsg) {
  ReMutexHolder holder(_lock);

  // Ask the GSG to create a brand new TextureContext.  There might
  // be several GSG's sharing the same set of textures; if so, it
  // doesn't matter which of them creates the context (since they're
  // all shared anyway).
  TextureContext *tc = gsg->prepare_texture(tex);

  if (tc != (TextureContext *)NULL) {
    bool prepared = _prepared_textures.insert(tc).second;
    nassertr(prepared, tc);
  }

  return tc;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::enqueue_geom
//       Access: Public
//  Description: Indicates that a geom would like to be put on the
//               list to be prepared when the GSG is next ready to
//               do this (presumably at the next frame).
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
enqueue_geom(Geom *geom) {
  ReMutexHolder holder(_lock);

  _enqueued_geoms.insert(geom);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::is_geom_queued
//       Access: Public
//  Description: Returns true if the geom has been queued on this
//               GSG, false otherwise.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
is_geom_queued(const Geom *geom) const {
  ReMutexHolder holder(_lock);

  EnqueuedGeoms::const_iterator qi = _enqueued_geoms.find((Geom *)geom);
  return (qi != _enqueued_geoms.end());
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::dequeue_geom
//       Access: Public
//  Description: Removes a geom from the queued list of geoms to
//               be prepared.  Normally it is not necessary to call
//               this, unless you change your mind about preparing it
//               at the last minute, since the geom will
//               automatically be dequeued and prepared at the next
//               frame.
//
//               The return value is true if the geom is
//               successfully dequeued, false if it had not been
//               queued.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
dequeue_geom(Geom *geom) {
  ReMutexHolder holder(_lock);

  EnqueuedGeoms::iterator qi = _enqueued_geoms.find(geom);
  if (qi != _enqueued_geoms.end()) {
    _enqueued_geoms.erase(qi);
    return true;
  }
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::is_geom_prepared
//       Access: Public
//  Description: Returns true if the vertex buffer has been prepared on
//               this GSG, false otherwise.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
is_geom_prepared(const Geom *geom) const {
  return geom->is_prepared((PreparedGraphicsObjects *)this);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::release_geom
//       Access: Public
//  Description: Indicates that a geom context, created by a
//               previous call to prepare_geom(), is no longer
//               needed.  The driver resources will not be freed until
//               some GSG calls update(), indicating it is at a
//               stage where it is ready to release geoms--this
//               prevents conflicts from threading or multiple GSG's
//               sharing geoms (we have no way of knowing which
//               graphics context is currently active, or what state
//               it's in, at the time release_geom is called).
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
release_geom(GeomContext *gc) {
  ReMutexHolder holder(_lock);

  gc->_geom->clear_prepared(this);

  // We have to set the Geom pointer to NULL at this point, since
  // the Geom itself might destruct at any time after it has been
  // released.
  gc->_geom = (Geom *)NULL;

  bool removed = (_prepared_geoms.erase(gc) != 0);
  nassertv(removed);

  _released_geoms.insert(gc);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::release_all_geoms
//       Access: Public
//  Description: Releases all geoms at once.  This will force them
//               to be reloaded into geom memory for all GSG's that
//               share this object.  Returns the number of geoms
//               released.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
release_all_geoms() {
  ReMutexHolder holder(_lock);

  int num_geoms = (int)_prepared_geoms.size() + (int)_enqueued_geoms.size();

  Geoms::iterator gci;
  for (gci = _prepared_geoms.begin();
       gci != _prepared_geoms.end();
       ++gci) {
    GeomContext *gc = (*gci);
    gc->_geom->clear_prepared(this);
    gc->_geom = (Geom *)NULL;

    _released_geoms.insert(gc);
  }

  _prepared_geoms.clear();
  _enqueued_geoms.clear();

  return num_geoms;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::get_num_queued_geoms
//       Access: Public
//  Description: Returns the number of geoms that have been
//               enqueued to be prepared on this GSG.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
get_num_queued_geoms() const {
  return _enqueued_geoms.size();
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::get_num_prepared_geoms
//       Access: Public
//  Description: Returns the number of geoms that have already been
//               prepared on this GSG.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
get_num_prepared_geoms() const {
  return _prepared_geoms.size();
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::prepare_geom_now
//       Access: Public
//  Description: Immediately creates a new GeomContext for the
//               indicated geom and returns it.  This assumes that
//               the GraphicsStateGuardian is the currently active
//               rendering context and that it is ready to accept new
//               geoms.  If this is not necessarily the case, you
//               should use enqueue_geom() instead.
//
//               Normally, this function is not called directly.  Call
//               Geom::prepare_now() instead.
//
//               The GeomContext contains all of the pertinent
//               information needed by the GSG to keep track of this
//               one particular geom, and will exist as long as the
//               geom is ready to be rendered.
//
//               When either the Geom or the
//               PreparedGraphicsObjects object destructs, the
//               GeomContext will be deleted.
////////////////////////////////////////////////////////////////////
GeomContext *PreparedGraphicsObjects::
prepare_geom_now(Geom *geom, GraphicsStateGuardianBase *gsg) {
  ReMutexHolder holder(_lock);

  // Ask the GSG to create a brand new GeomContext.  There might
  // be several GSG's sharing the same set of geoms; if so, it
  // doesn't matter which of them creates the context (since they're
  // all shared anyway).
  GeomContext *gc = gsg->prepare_geom(geom);

  if (gc != (GeomContext *)NULL) {
    bool prepared = _prepared_geoms.insert(gc).second;
    nassertr(prepared, gc);
  }

  return gc;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::enqueue_shader
//       Access: Public
//  Description: Indicates that a shader would like to be put on the
//               list to be prepared when the GSG is next ready to
//               do this (presumably at the next frame).
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
enqueue_shader(Shader *se) {
  ReMutexHolder holder(_lock);

  _enqueued_shaders.insert(se);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::is_shader_queued
//       Access: Public
//  Description: Returns true if the shader has been queued on this
//               GSG, false otherwise.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
is_shader_queued(const Shader *shader) const {
  ReMutexHolder holder(_lock);

  EnqueuedShaders::const_iterator qi = _enqueued_shaders.find((Shader *)shader);
  return (qi != _enqueued_shaders.end());
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::dequeue_shader
//       Access: Public
//  Description: Removes a shader from the queued list of shaders to
//               be prepared.  Normally it is not necessary to call
//               this, unless you change your mind about preparing it
//               at the last minute, since the shader will
//               automatically be dequeued and prepared at the next
//               frame.
//
//               The return value is true if the shader is
//               successfully dequeued, false if it had not been
//               queued.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
dequeue_shader(Shader *se) {
  ReMutexHolder holder(_lock);

  EnqueuedShaders::iterator qi = _enqueued_shaders.find(se);
  if (qi != _enqueued_shaders.end()) {
    _enqueued_shaders.erase(qi);
    return true;
  }
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::is_shader_prepared
//       Access: Public
//  Description: Returns true if the shader has been prepared on
//               this GSG, false otherwise.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
is_shader_prepared(const Shader *shader) const {
  return shader->is_prepared((PreparedGraphicsObjects *)this);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::release_shader
//       Access: Public
//  Description: Indicates that a shader context, created by a
//               previous call to prepare_shader(), is no longer
//               needed.  The driver resources will not be freed until
//               some GSG calls update(), indicating it is at a
//               stage where it is ready to release shaders--this
//               prevents conflicts from threading or multiple GSG's
//               sharing shaders (we have no way of knowing which
//               graphics context is currently active, or what state
//               it's in, at the time release_shader is called).
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
release_shader(ShaderContext *sc) {
  ReMutexHolder holder(_lock);

  sc->_shader->clear_prepared(this);

  // We have to set the Shader pointer to NULL at this point, since
  // the Shader itself might destruct at any time after it has been
  // released.
  sc->_shader = (Shader *)NULL;

  bool removed = (_prepared_shaders.erase(sc) != 0);
  nassertv(removed);

  _released_shaders.insert(sc);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::release_all_shaders
//       Access: Public
//  Description: Releases all shaders at once.  This will force them
//               to be reloaded into shader memory for all GSG's that
//               share this object.  Returns the number of shaders
//               released.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
release_all_shaders() {
  ReMutexHolder holder(_lock);

  int num_shaders = (int)_prepared_shaders.size() + (int)_enqueued_shaders.size();

  Shaders::iterator sci;
  for (sci = _prepared_shaders.begin();
       sci != _prepared_shaders.end();
       ++sci) {
    ShaderContext *sc = (*sci);
    sc->_shader->clear_prepared(this);
    sc->_shader = (Shader *)NULL;

    _released_shaders.insert(sc);
  }

  _prepared_shaders.clear();
  _enqueued_shaders.clear();

  return num_shaders;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::get_num_queued_shaders
//       Access: Public
//  Description: Returns the number of shaders that have been
//               enqueued to be prepared on this GSG.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
get_num_queued_shaders() const {
  return _enqueued_shaders.size();
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::get_num_prepared_shaders
//       Access: Public
//  Description: Returns the number of shaders that have already been
//               prepared on this GSG.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
get_num_prepared_shaders() const {
  return _prepared_shaders.size();
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::prepare_shader_now
//       Access: Public
//  Description: Immediately creates a new ShaderContext for the
//               indicated shader and returns it.  This assumes that
//               the GraphicsStateGuardian is the currently active
//               rendering context and that it is ready to accept new
//               shaders.  If this is not necessarily the case, you
//               should use enqueue_shader() instead.
//
//               Normally, this function is not called directly.  Call
//               Shader::prepare_now() instead.
//
//               The ShaderContext contains all of the pertinent
//               information needed by the GSG to keep track of this
//               one particular shader, and will exist as long as the
//               shader is ready to be rendered.
//
//               When either the Shader or the
//               PreparedGraphicsObjects object destructs, the
//               ShaderContext will be deleted.
////////////////////////////////////////////////////////////////////
ShaderContext *PreparedGraphicsObjects::
prepare_shader_now(Shader *se, GraphicsStateGuardianBase *gsg) {
  ReMutexHolder holder(_lock);

  // Ask the GSG to create a brand new ShaderContext.  There might
  // be several GSG's sharing the same set of shaders; if so, it
  // doesn't matter which of them creates the context (since they're
  // all shared anyway).
  ShaderContext *sc = gsg->prepare_shader(se);

  if (sc != (ShaderContext *)NULL) {
    bool prepared = _prepared_shaders.insert(sc).second;
    nassertr(prepared, sc);
  }

  return sc;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::enqueue_vertex_buffer
//       Access: Public
//  Description: Indicates that a buffer would like to be put on the
//               list to be prepared when the GSG is next ready to
//               do this (presumably at the next frame).
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
enqueue_vertex_buffer(GeomVertexArrayData *data) {
  ReMutexHolder holder(_lock);

  _enqueued_vertex_buffers.insert(data);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::is_vertex_buffer_queued
//       Access: Public
//  Description: Returns true if the vertex buffer has been queued on
//               this GSG, false otherwise.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
is_vertex_buffer_queued(const GeomVertexArrayData *data) const {
  ReMutexHolder holder(_lock);

  EnqueuedVertexBuffers::const_iterator qi = _enqueued_vertex_buffers.find((GeomVertexArrayData *)data);
  return (qi != _enqueued_vertex_buffers.end());
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::dequeue_vertex_buffer
//       Access: Public
//  Description: Removes a buffer from the queued list of data
//               arrays to be prepared.  Normally it is not necessary
//               to call this, unless you change your mind about
//               preparing it at the last minute, since the data will
//               automatically be dequeued and prepared at the next
//               frame.
//
//               The return value is true if the buffer is
//               successfully dequeued, false if it had not been
//               queued.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
dequeue_vertex_buffer(GeomVertexArrayData *data) {
  ReMutexHolder holder(_lock);

  EnqueuedVertexBuffers::iterator qi = _enqueued_vertex_buffers.find(data);
  if (qi != _enqueued_vertex_buffers.end()) {
    _enqueued_vertex_buffers.erase(qi);
    return true;
  }
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::is_vertex_buffer_prepared
//       Access: Public
//  Description: Returns true if the vertex buffer has been prepared on
//               this GSG, false otherwise.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
is_vertex_buffer_prepared(const GeomVertexArrayData *data) const {
  return data->is_prepared((PreparedGraphicsObjects *)this);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::release_vertex_buffer
//       Access: Public
//  Description: Indicates that a data context, created by a
//               previous call to prepare_vertex_buffer(), is no longer
//               needed.  The driver resources will not be freed until
//               some GSG calls update(), indicating it is at a
//               stage where it is ready to release datas--this
//               prevents conflicts from threading or multiple GSG's
//               sharing datas (we have no way of knowing which
//               graphics context is currently active, or what state
//               it's in, at the time release_vertex_buffer is called).
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
release_vertex_buffer(VertexBufferContext *vbc) {
  ReMutexHolder holder(_lock);

  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
  // the Data itself might destruct at any time after it has been
  // released.
  vbc->_data = (GeomVertexArrayData *)NULL;

  bool removed = (_prepared_vertex_buffers.erase(vbc) != 0);
  nassertv(removed);

  if (_support_released_buffer_cache) {
    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);
  } else {
    _released_vertex_buffers.insert(vbc);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::release_all_vertex_buffers
//       Access: Public
//  Description: Releases all datas at once.  This will force them
//               to be reloaded into data memory for all GSG's that
//               share this object.  Returns the number of datas
//               released.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
release_all_vertex_buffers() {
  ReMutexHolder holder(_lock);

  int num_vertex_buffers = (int)_prepared_vertex_buffers.size() + (int)_enqueued_vertex_buffers.size();

  Buffers::iterator vbci;
  for (vbci = _prepared_vertex_buffers.begin();
       vbci != _prepared_vertex_buffers.end();
       ++vbci) {
    VertexBufferContext *vbc = (VertexBufferContext *)(*vbci);
    vbc->_data->clear_prepared(this);
    vbc->_data = (GeomVertexArrayData *)NULL;

    _released_vertex_buffers.insert(vbc);
  }

  _prepared_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;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::get_num_queued_vertex_buffers
//       Access: Public
//  Description: Returns the number of vertex buffers that have been
//               enqueued to be prepared on this GSG.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
get_num_queued_vertex_buffers() const {
  return _enqueued_vertex_buffers.size();
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::get_num_prepared_vertex_buffers
//       Access: Public
//  Description: Returns the number of vertex buffers that have
//               already been prepared on this GSG.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
get_num_prepared_vertex_buffers() const {
  return _prepared_vertex_buffers.size();
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::prepare_vertex_buffer_now
//       Access: Public
//  Description: Immediately creates a new VertexBufferContext for the
//               indicated data and returns it.  This assumes that
//               the GraphicsStateGuardian is the currently active
//               rendering context and that it is ready to accept new
//               datas.  If this is not necessarily the case, you
//               should use enqueue_vertex_buffer() instead.
//
//               Normally, this function is not called directly.  Call
//               Data::prepare_now() instead.
//
//               The VertexBufferContext contains all of the pertinent
//               information needed by the GSG to keep track of this
//               one particular data, and will exist as long as the
//               data is ready to be rendered.
//
//               When either the Data or the
//               PreparedGraphicsObjects object destructs, the
//               VertexBufferContext will be deleted.
////////////////////////////////////////////////////////////////////
VertexBufferContext *PreparedGraphicsObjects::
prepare_vertex_buffer_now(GeomVertexArrayData *data, GraphicsStateGuardianBase *gsg) {
  ReMutexHolder holder(_lock);

  // 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) {
    bool prepared = _prepared_vertex_buffers.insert(vbc).second;
    nassertr(prepared, vbc);
  }

  return vbc;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::enqueue_index_buffer
//       Access: Public
//  Description: Indicates that a buffer would like to be put on the
//               list to be prepared when the GSG is next ready to
//               do this (presumably at the next frame).
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
enqueue_index_buffer(GeomPrimitive *data) {
  ReMutexHolder holder(_lock);

  _enqueued_index_buffers.insert(data);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::is_index_buffer_queued
//       Access: Public
//  Description: Returns true if the index buffer has been queued on
//               this GSG, false otherwise.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
is_index_buffer_queued(const GeomPrimitive *data) const {
  ReMutexHolder holder(_lock);

  EnqueuedIndexBuffers::const_iterator qi = _enqueued_index_buffers.find((GeomPrimitive *)data);
  return (qi != _enqueued_index_buffers.end());
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::dequeue_index_buffer
//       Access: Public
//  Description: Removes a buffer from the queued list of data
//               arrays to be prepared.  Normally it is not necessary
//               to call this, unless you change your mind about
//               preparing it at the last minute, since the data will
//               automatically be dequeued and prepared at the next
//               frame.
//
//               The return value is true if the buffer is
//               successfully dequeued, false if it had not been
//               queued.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
dequeue_index_buffer(GeomPrimitive *data) {
  ReMutexHolder holder(_lock);

  EnqueuedIndexBuffers::iterator qi = _enqueued_index_buffers.find(data);
  if (qi != _enqueued_index_buffers.end()) {
    _enqueued_index_buffers.erase(qi);
    return true;
  }
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::is_index_buffer_prepared
//       Access: Public
//  Description: Returns true if the index buffer has been prepared on
//               this GSG, false otherwise.
////////////////////////////////////////////////////////////////////
bool PreparedGraphicsObjects::
is_index_buffer_prepared(const GeomPrimitive *data) const {
  return data->is_prepared((PreparedGraphicsObjects *)this);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::release_index_buffer
//       Access: Public
//  Description: Indicates that a data context, created by a
//               previous call to prepare_index_buffer(), is no longer
//               needed.  The driver resources will not be freed until
//               some GSG calls update(), indicating it is at a
//               stage where it is ready to release datas--this
//               prevents conflicts from threading or multiple GSG's
//               sharing datas (we have no way of knowing which
//               graphics context is currently active, or what state
//               it's in, at the time release_index_buffer is called).
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
release_index_buffer(IndexBufferContext *ibc) {
  ReMutexHolder holder(_lock);

  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
  // the Data itself might destruct at any time after it has been
  // released.
  ibc->_data = (GeomPrimitive *)NULL;

  bool removed = (_prepared_index_buffers.erase(ibc) != 0);
  nassertv(removed);

  if (_support_released_buffer_cache) {
    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);
  } else {
    _released_index_buffers.insert(ibc);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::release_all_index_buffers
//       Access: Public
//  Description: Releases all datas at once.  This will force them
//               to be reloaded into data memory for all GSG's that
//               share this object.  Returns the number of datas
//               released.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
release_all_index_buffers() {
  ReMutexHolder holder(_lock);

  int num_index_buffers = (int)_prepared_index_buffers.size() + (int)_enqueued_index_buffers.size();

  Buffers::iterator ibci;
  for (ibci = _prepared_index_buffers.begin();
       ibci != _prepared_index_buffers.end();
       ++ibci) {
    IndexBufferContext *ibc = (IndexBufferContext *)(*ibci);
    ibc->_data->clear_prepared(this);
    ibc->_data = (GeomPrimitive *)NULL;

    _released_index_buffers.insert(ibc);
  }

  _prepared_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;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::get_num_queued_index_buffers
//       Access: Public
//  Description: Returns the number of index buffers that have been
//               enqueued to be prepared on this GSG.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
get_num_queued_index_buffers() const {
  return _enqueued_index_buffers.size();
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::get_num_prepared_index_buffers
//       Access: Public
//  Description: Returns the number of index buffers that have
//               already been prepared on this GSG.
////////////////////////////////////////////////////////////////////
int PreparedGraphicsObjects::
get_num_prepared_index_buffers() const {
  return _prepared_index_buffers.size();
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::prepare_index_buffer_now
//       Access: Public
//  Description: Immediately creates a new IndexBufferContext for the
//               indicated data and returns it.  This assumes that
//               the GraphicsStateGuardian is the currently active
//               rendering context and that it is ready to accept new
//               datas.  If this is not necessarily the case, you
//               should use enqueue_index_buffer() instead.
//
//               Normally, this function is not called directly.  Call
//               Data::prepare_now() instead.
//
//               The IndexBufferContext contains all of the pertinent
//               information needed by the GSG to keep track of this
//               one particular data, and will exist as long as the
//               data is ready to be rendered.
//
//               When either the Data or the
//               PreparedGraphicsObjects object destructs, the
//               IndexBufferContext will be deleted.
////////////////////////////////////////////////////////////////////
IndexBufferContext *PreparedGraphicsObjects::
prepare_index_buffer_now(GeomPrimitive *data, GraphicsStateGuardianBase *gsg) {
  ReMutexHolder holder(_lock);

  // 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) {
    bool prepared = _prepared_index_buffers.insert(ibc).second;
    nassertr(prepared, ibc);
  }

  return ibc;
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::begin_frame
//       Access: Public
//  Description: This is called by the GraphicsStateGuardian to
//               indicate that it is about to begin processing of the
//               frame.
//
//               Any texture contexts that were previously passed to
//               release_texture() are actually passed to the GSG to
//               be freed at this point; textures that were previously
//               passed to prepare_texture are actually loaded.
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
  ReMutexHolder holder(_lock, current_thread);

  // First, release all the textures, geoms, and buffers awaiting
  // release.
  if (!_released_textures.empty()) {
    Textures::iterator tci;
    for (tci = _released_textures.begin();
         tci != _released_textures.end();
         ++tci) {
      TextureContext *tc = (*tci);
      gsg->release_texture(tc);
    }
  }

  _released_textures.clear();

  Geoms::iterator gci;
  for (gci = _released_geoms.begin();
       gci != _released_geoms.end();
       ++gci) {
    GeomContext *gc = (*gci);
    gsg->release_geom(gc);
  }

  _released_geoms.clear();

  Shaders::iterator sci;
  for (sci = _released_shaders.begin();
       sci != _released_shaders.end();
       ++sci) {
    ShaderContext *sc = (*sci);
    gsg->release_shader(sc);
  }

  _released_shaders.clear();

  Buffers::iterator vbci;
  for (vbci = _released_vertex_buffers.begin();
       vbci != _released_vertex_buffers.end();
       ++vbci) {
    VertexBufferContext *vbc = (VertexBufferContext *)(*vbci);
    gsg->release_vertex_buffer(vbc);
  }

  _released_vertex_buffers.clear();

  Buffers::iterator ibci;
  for (ibci = _released_index_buffers.begin();
       ibci != _released_index_buffers.end();
       ++ibci) {
    IndexBufferContext *ibc = (IndexBufferContext *)(*ibci);
    gsg->release_index_buffer(ibc);
  }

  _released_index_buffers.clear();

  // Reset the residency trackers.
  _texture_residency.begin_frame(current_thread);
  _vbuffer_residency.begin_frame(current_thread);
  _ibuffer_residency.begin_frame(current_thread);

  // Now prepare all the textures, geoms, and buffers awaiting
  // preparation.
  EnqueuedTextures::iterator qti;
  for (qti = _enqueued_textures.begin();
       qti != _enqueued_textures.end();
       ++qti) {
    Texture *tex = (*qti);
    TextureContext *tc = tex->prepare_now(this, gsg);
    if (tc != (TextureContext *)NULL) {
      gsg->update_texture(tc, true);
    }
  }

  _enqueued_textures.clear();

  EnqueuedGeoms::iterator qgi;
  for (qgi = _enqueued_geoms.begin();
       qgi != _enqueued_geoms.end();
       ++qgi) {
    Geom *geom = (*qgi);
    geom->prepare_now(this, gsg);
  }

  _enqueued_geoms.clear();

  EnqueuedShaders::iterator qsi;
  for (qsi = _enqueued_shaders.begin();
       qsi != _enqueued_shaders.end();
       ++qsi) {
    Shader *shader = (*qsi);
    shader->prepare_now(this, gsg);
  }

  _enqueued_shaders.clear();

  EnqueuedVertexBuffers::iterator qvbi;
  for (qvbi = _enqueued_vertex_buffers.begin();
       qvbi != _enqueued_vertex_buffers.end();
       ++qvbi) {
    GeomVertexArrayData *data = (*qvbi);
    data->prepare_now(this, gsg);
  }

  _enqueued_vertex_buffers.clear();

  EnqueuedIndexBuffers::iterator qibi;
  for (qibi = _enqueued_index_buffers.begin();
       qibi != _enqueued_index_buffers.end();
       ++qibi) {
    GeomPrimitive *data = (*qibi);
    data->prepare_now(this, gsg);
  }

  _enqueued_index_buffers.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::end_frame
//       Access: Public
//  Description: This is called by the GraphicsStateGuardian to
//               indicate that it has finished processing of the
//               frame.
////////////////////////////////////////////////////////////////////
void PreparedGraphicsObjects::
end_frame(Thread *current_thread) {
  ReMutexHolder holder(_lock, current_thread);

  _texture_residency.end_frame(current_thread);
  _vbuffer_residency.end_frame(current_thread);
  _ibuffer_residency.end_frame(current_thread);
}

////////////////////////////////////////////////////////////////////
//     Function: PreparedGraphicsObjects::init_name
//       Access: Private, Static
//  Description: Returns a new, unique name for a newly-constructed
//               object.
////////////////////////////////////////////////////////////////////
string PreparedGraphicsObjects::
init_name() {
  ++_name_index;
  ostringstream strm;
  strm << "context" << _name_index;
  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;
}
