add ReferenceCount::local_object(), clarify GeomVertexCache

This commit is contained in:
David Rose 2005-04-17 17:38:51 +00:00
parent c83feafc70
commit 05ba41b4d6
30 changed files with 324 additions and 187 deletions

View File

@ -62,6 +62,28 @@ release_all_geoms() {
return _prepared_objects->release_all_geoms(); return _prepared_objects->release_all_geoms();
} }
////////////////////////////////////////////////////////////////////
// Function: GraphicsStateGuardian::release_all_vertex_buffers
// Access: Public
// Description: Frees the resources for all vertex buffers associated
// with this GSG.
////////////////////////////////////////////////////////////////////
INLINE int GraphicsStateGuardian::
release_all_vertex_buffers() {
return _prepared_objects->release_all_vertex_buffers();
}
////////////////////////////////////////////////////////////////////
// Function: GraphicsStateGuardian::release_all_index_buffers
// Access: Public
// Description: Frees the resources for all index buffers associated
// with this GSG.
////////////////////////////////////////////////////////////////////
INLINE int GraphicsStateGuardian::
release_all_index_buffers() {
return _prepared_objects->release_all_index_buffers();
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: GraphicsStateGuardian::set_active // Function: GraphicsStateGuardian::set_active
// Access: Published // Access: Published

View File

@ -1511,6 +1511,8 @@ close_gsg() {
if (_prepared_objects->get_ref_count() == 1) { if (_prepared_objects->get_ref_count() == 1) {
release_all_textures(); release_all_textures();
release_all_geoms(); release_all_geoms();
release_all_vertex_buffers();
release_all_index_buffers();
} }
} }

View File

@ -74,6 +74,8 @@ public:
PUBLISHED: PUBLISHED:
INLINE int release_all_textures(); INLINE int release_all_textures();
INLINE int release_all_geoms(); INLINE int release_all_geoms();
INLINE int release_all_vertex_buffers();
INLINE int release_all_index_buffers();
INLINE void set_active(bool active); INLINE void set_active(bool active);
INLINE bool is_active() const; INLINE bool is_active() const;

View File

@ -59,7 +59,7 @@ GraphicsThreadingModel(const string &model) {
size_t slash = model.find('/', start); size_t slash = model.find('/', start);
if (slash == string::npos) { if (slash == string::npos) {
_cull_name = model; _cull_name = model.substr(start);
} else { } else {
_cull_name = model.substr(start, slash - start); _cull_name = model.substr(start, slash - start);
_draw_name = model.substr(slash + 1); _draw_name = model.substr(slash + 1);

View File

@ -3197,44 +3197,51 @@ release_texture(TextureContext *tc) {
VertexBufferContext *DXGraphicsStateGuardian8:: VertexBufferContext *DXGraphicsStateGuardian8::
prepare_vertex_buffer(qpGeomVertexArrayData *data) { prepare_vertex_buffer(qpGeomVertexArrayData *data) {
DXVertexBufferContext8 *dvbc = new DXVertexBufferContext8(data); DXVertexBufferContext8 *dvbc = new DXVertexBufferContext8(data);
if (vertex_buffers && data->get_usage_hint() != qpGeom::UH_client) {
dvbc->create_vbuffer(*_pScrn);
if (dxgsg8_cat.is_debug()) {
dxgsg8_cat.debug()
<< "creating vertex buffer " << dvbc->_vbuffer << ": "
<< data->get_num_rows() << " vertices "
<< *data->get_array_format() << "\n";
}
}
return dvbc; return dvbc;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: DXGraphicsStateGuardian8::apply_vertex_buffer // Function: DXGraphicsStateGuardian8::apply_vertex_buffer
// Access: Public // Access: Public
// Description: Makes the data the currently available data for // Description: Updates the vertex buffer with the current data, and
// rendering. // makes it the current vertex buffer for rendering.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void DXGraphicsStateGuardian8:: void DXGraphicsStateGuardian8::
apply_vertex_buffer(VertexBufferContext *vbc) { apply_vertex_buffer(VertexBufferContext *vbc) {
DXVertexBufferContext8 *dvbc = DCAST(DXVertexBufferContext8, vbc); DXVertexBufferContext8 *dvbc = DCAST(DXVertexBufferContext8, vbc);
if (dvbc->_vbuffer != NULL) { if (dvbc->_vbuffer == NULL) {
// Attempt to create a new vertex buffer.
if (vertex_buffers &&
dvbc->get_data()->get_usage_hint() != qpGeom::UH_client) {
dvbc->create_vbuffer(*_pScrn);
}
if (dvbc->_vbuffer != NULL) {
dvbc->upload_data();
add_to_vertex_buffer_record(dvbc);
add_to_total_buffer_record(dvbc);
dvbc->mark_loaded();
_pD3DDevice->SetStreamSource
(0, dvbc->_vbuffer, dvbc->get_data()->get_array_format()->get_stride());
_vbuffer_active = true;
} else {
_vbuffer_active = false;
}
} else {
add_to_vertex_buffer_record(dvbc); add_to_vertex_buffer_record(dvbc);
if (dvbc->was_modified()) { if (dvbc->was_modified()) {
if (dvbc->changed_size()) { if (dvbc->changed_size()) {
// Here we have to destroy the old vertex buffer and create a // We have to destroy the old vertex buffer and create a new
// new one. // one.
dvbc->create_vbuffer(*_pScrn); dvbc->create_vbuffer(*_pScrn);
} else {
// Here we just copy the new data to the vertex buffer.
dvbc->upload_data();
} }
dvbc->upload_data();
add_to_total_buffer_record(dvbc); add_to_total_buffer_record(dvbc);
dvbc->mark_loaded(); dvbc->mark_loaded();
@ -3243,9 +3250,6 @@ apply_vertex_buffer(VertexBufferContext *vbc) {
_pD3DDevice->SetStreamSource _pD3DDevice->SetStreamSource
(0, dvbc->_vbuffer, dvbc->get_data()->get_array_format()->get_stride()); (0, dvbc->_vbuffer, dvbc->get_data()->get_array_format()->get_stride());
_vbuffer_active = true; _vbuffer_active = true;
} else {
_vbuffer_active = false;
} }
set_vertex_format(dvbc->_fvf); set_vertex_format(dvbc->_fvf);
@ -3281,43 +3285,48 @@ release_vertex_buffer(VertexBufferContext *vbc) {
IndexBufferContext *DXGraphicsStateGuardian8:: IndexBufferContext *DXGraphicsStateGuardian8::
prepare_index_buffer(qpGeomPrimitive *data) { prepare_index_buffer(qpGeomPrimitive *data) {
DXIndexBufferContext8 *dibc = new DXIndexBufferContext8(data); DXIndexBufferContext8 *dibc = new DXIndexBufferContext8(data);
dibc->create_ibuffer(*_pScrn);
if (dxgsg8_cat.is_debug()) {
dxgsg8_cat.debug()
<< "creating index buffer " << dibc->_ibuffer << ": "
<< data->get_num_vertices() << " indices ("
<< data->get_vertices()->get_array_format()->get_column(0)->get_numeric_type()
<< ")\n";
}
return dibc; return dibc;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: DXGraphicsStateGuardian8::apply_index_buffer // Function: DXGraphicsStateGuardian8::apply_index_buffer
// Access: Public // Access: Public
// Description: Makes the data the currently available data for // Description: Updates the index buffer with the current data, and
// rendering. // makes it the current index buffer for rendering.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void DXGraphicsStateGuardian8:: void DXGraphicsStateGuardian8::
apply_index_buffer(IndexBufferContext *ibc) { apply_index_buffer(IndexBufferContext *ibc) {
DXIndexBufferContext8 *dibc = DCAST(DXIndexBufferContext8, ibc); DXIndexBufferContext8 *dibc = DCAST(DXIndexBufferContext8, ibc);
if (dibc->_ibuffer != NULL) { if (dibc->_ibuffer == NULL) {
// Attempt to create a new index buffer.
dibc->create_ibuffer(*_pScrn);
if (dibc->_ibuffer != NULL) {
dibc->upload_data();
add_to_index_buffer_record(dibc);
add_to_total_buffer_record(dibc);
dibc->mark_loaded();
_pD3DDevice->SetIndices(dibc->_ibuffer, 0);
_ibuffer_active = true;
} else {
_pD3DDevice->SetIndices(NULL, 0);
_ibuffer_active = false;
}
} else {
add_to_index_buffer_record(dibc); add_to_index_buffer_record(dibc);
if (dibc->was_modified()) { if (dibc->was_modified()) {
if (dibc->changed_size()) { if (dibc->changed_size()) {
// Here we have to destroy the old index buffer and create a // We have to destroy the old index buffer and create a new
// new one. // one.
dibc->create_ibuffer(*_pScrn); dibc->create_ibuffer(*_pScrn);
} else {
// Here we just copy the new data to the index buffer.
dibc->upload_data();
} }
dibc->upload_data();
add_to_total_buffer_record(dibc); add_to_total_buffer_record(dibc);
dibc->mark_loaded(); dibc->mark_loaded();
@ -3325,10 +3334,6 @@ apply_index_buffer(IndexBufferContext *ibc) {
_pD3DDevice->SetIndices(dibc->_ibuffer, 0); _pD3DDevice->SetIndices(dibc->_ibuffer, 0);
_ibuffer_active = true; _ibuffer_active = true;
} else {
_pD3DDevice->SetIndices(NULL, 0);
_ibuffer_active = false;
} }
} }

View File

@ -58,7 +58,8 @@ DXIndexBufferContext8::
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: DXIndexBufferContext8::create_ibuffer // Function: DXIndexBufferContext8::create_ibuffer
// Access: Public // Access: Public
// Description: Creates a new index buffer and uploads data to it. // Description: Creates a new index buffer (but does not upload data
// to it).
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void DXIndexBufferContext8:: void DXIndexBufferContext8::
create_ibuffer(DXScreenData &scrn) { create_ibuffer(DXScreenData &scrn) {
@ -77,9 +78,14 @@ create_ibuffer(DXScreenData &scrn) {
dxgsg8_cat.warning() dxgsg8_cat.warning()
<< "CreateIndexBuffer failed" << D3DERRORSTRING(hr); << "CreateIndexBuffer failed" << D3DERRORSTRING(hr);
_ibuffer = NULL; _ibuffer = NULL;
} else { } else {
upload_data(); if (dxgsg8_cat.is_debug()) {
dxgsg8_cat.debug()
<< "creating index buffer " << _ibuffer << ": "
<< get_data()->get_num_vertices() << " indices ("
<< get_data()->get_vertices()->get_array_format()->get_column(0)->get_numeric_type()
<< ")\n";
}
} }
} }

View File

@ -153,7 +153,8 @@ DXVertexBufferContext8::
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: DXVertexBufferContext8::create_vbuffer // Function: DXVertexBufferContext8::create_vbuffer
// Access: Public // Access: Public
// Description: Creates a new vertex buffer and uploads data to it. // Description: Creates a new vertex buffer (but does not upload data
// to it).
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void DXVertexBufferContext8:: void DXVertexBufferContext8::
create_vbuffer(DXScreenData &scrn) { create_vbuffer(DXScreenData &scrn) {
@ -169,9 +170,13 @@ create_vbuffer(DXScreenData &scrn) {
dxgsg8_cat.warning() dxgsg8_cat.warning()
<< "CreateVertexBuffer failed" << D3DERRORSTRING(hr); << "CreateVertexBuffer failed" << D3DERRORSTRING(hr);
_vbuffer = NULL; _vbuffer = NULL;
} else { } else {
upload_data(); if (dxgsg8_cat.is_debug()) {
dxgsg8_cat.debug()
<< "created vertex buffer " << _vbuffer << ": "
<< get_data()->get_num_rows() << " vertices "
<< *get_data()->get_array_format() << "\n";
}
} }
} }

View File

@ -87,7 +87,7 @@ operator = (const ReferenceCount &) {
// instance of a class that derives from ReferenceCount. Or maybe // instance of a class that derives from ReferenceCount. Or maybe
// your headers are out of sync, and you need to make clean in // your headers are out of sync, and you need to make clean in
// direct or some higher tree. // direct or some higher tree.
nassertv(_ref_count != -100); nassertv(_ref_count != deleted_ref_count);
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
@ -112,7 +112,13 @@ INLINE ReferenceCount::
// automatic (local variable) instance of a class that derives from // automatic (local variable) instance of a class that derives from
// ReferenceCount. Or maybe your headers are out of sync, and you // ReferenceCount. Or maybe your headers are out of sync, and you
// need to make clean in direct or some higher tree. // need to make clean in direct or some higher tree.
nassertv(_ref_count != -100); nassertv(_ref_count != deleted_ref_count);
// If this assertion fails, we're trying to delete a static object
// that still has an outstanding reference count. You should make
// sure that all references to your static objects are gone by the
// time the object itself destructs.
nassertv(_ref_count <= local_ref_count);
// If this assertion fails, the reference counts are all screwed // If this assertion fails, the reference counts are all screwed
// up altogether. Maybe some errant code stomped all over memory // up altogether. Maybe some errant code stomped all over memory
@ -129,7 +135,7 @@ INLINE ReferenceCount::
// constructor for a ReferenceCount object, and then bitwise // constructor for a ReferenceCount object, and then bitwise
// copied a dynamically allocated value--reference count and // copied a dynamically allocated value--reference count and
// all--onto a locally allocated one. // all--onto a locally allocated one.
nassertv(_ref_count == 0); nassertv(_ref_count == 0 || _ref_count == local_ref_count);
// Tell our weak reference holders that we're going away now. // Tell our weak reference holders that we're going away now.
if (_weak_list != (WeakReferenceList *)NULL) { if (_weak_list != (WeakReferenceList *)NULL) {
@ -138,10 +144,10 @@ INLINE ReferenceCount::
} }
#ifndef NDEBUG #ifndef NDEBUG
// Ok, all clear to delete. Now set the reference count to -100, // Ok, all clear to delete. Now set the reference count to
// so we'll have a better chance of noticing if we happen to have // deleted_ref_count, so we'll have a better chance of noticing if
// a stray pointer to it still out there. // we happen to have a stray pointer to it still out there.
_ref_count = -100; _ref_count = deleted_ref_count;
#endif #endif
#ifdef DO_MEMORY_USAGE #ifdef DO_MEMORY_USAGE
@ -151,7 +157,7 @@ INLINE ReferenceCount::
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: ReferenceCount::get_ref_count // Function: ReferenceCount::get_ref_count
// Access: Public // Access: Published
// Description: Returns the current reference count. // Description: Returns the current reference count.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
INLINE int ReferenceCount:: INLINE int ReferenceCount::
@ -164,7 +170,7 @@ get_ref_count() const {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: ReferenceCount::ref // Function: ReferenceCount::ref
// Access: Public // Access: Published
// Description: Explicitly increments the reference count. User code // Description: Explicitly increments the reference count. User code
// should avoid using ref() and unref() directly, which // should avoid using ref() and unref() directly, which
// can result in missed reference counts. Instead, let // can result in missed reference counts. Instead, let
@ -191,7 +197,7 @@ ref() const {
// automatic (local variable) instance of a class that derives from // automatic (local variable) instance of a class that derives from
// ReferenceCount. Or maybe your headers are out of sync, and you // ReferenceCount. Or maybe your headers are out of sync, and you
// need to make clean in direct or some higher tree. // need to make clean in direct or some higher tree.
nassertr(_ref_count != -100, 0); nassertr(_ref_count != deleted_ref_count, 0);
// If this assertion fails, the reference counts are all screwed // If this assertion fails, the reference counts are all screwed
// up altogether. Maybe some errant code stomped all over memory // up altogether. Maybe some errant code stomped all over memory
@ -203,7 +209,7 @@ ref() const {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: ReferenceCount::unref // Function: ReferenceCount::unref
// Access: Public // Access: Published
// Description: Explicitly decrements the reference count. Note that // Description: Explicitly decrements the reference count. Note that
// the object will not be implicitly deleted by unref() // the object will not be implicitly deleted by unref()
// simply because the reference count drops to zero. // simply because the reference count drops to zero.
@ -237,7 +243,7 @@ unref() const {
// automatic (local variable) instance of a class that derives from // automatic (local variable) instance of a class that derives from
// ReferenceCount. Or maybe your headers are out of sync, and you // ReferenceCount. Or maybe your headers are out of sync, and you
// need to make clean in direct or some higher tree. // need to make clean in direct or some higher tree.
nassertr(_ref_count != -100, false); nassertr(_ref_count != deleted_ref_count, false);
// If this assertion fails, the reference counts are all screwed // If this assertion fails, the reference counts are all screwed
// up altogether. Maybe some errant code stomped all over memory // up altogether. Maybe some errant code stomped all over memory
@ -254,7 +260,7 @@ unref() const {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: ReferenceCount::test_ref_count_integrity // Function: ReferenceCount::test_ref_count_integrity
// Access: Public // Access: Published
// Description: Does some easy checks to make sure that the reference // Description: Does some easy checks to make sure that the reference
// count isn't completely bogus. // count isn't completely bogus.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
@ -270,7 +276,7 @@ test_ref_count_integrity() const {
// automatic (local variable) instance of a class that derives from // automatic (local variable) instance of a class that derives from
// ReferenceCount. Or maybe your headers are out of sync, and you // ReferenceCount. Or maybe your headers are out of sync, and you
// need to make clean in direct or some higher tree. // need to make clean in direct or some higher tree.
nassertv(_ref_count != -100); nassertv(_ref_count != deleted_ref_count);
// If this assertion fails, the reference counts are all screwed // If this assertion fails, the reference counts are all screwed
// up altogether. Maybe some errant code stomped all over memory // up altogether. Maybe some errant code stomped all over memory
@ -279,6 +285,30 @@ test_ref_count_integrity() const {
#endif #endif
} }
////////////////////////////////////////////////////////////////////
// Function: ReferenceCount::local_object
// Access: Public
// Description: This function should be called, once, immediately
// after creating a new instance of some
// ReferenceCount-derived object on the stack.
//
// This allows the object to be passed to functions that
// will increment and decrement the object's reference
// count temporarily, and it will prevent the object
// from being deleted (inappropriately), when the
// reference count returns to zero. It actually
// achieves this by setting a large positive value in
// the reference count field.
////////////////////////////////////////////////////////////////////
INLINE void ReferenceCount::
local_object() {
// If this assertion fails, you didn't call this immediately after
// creating a local object.
nassertv(_ref_count == 0);
_ref_count = local_ref_count;
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: ReferenceCount::has_weak_list // Function: ReferenceCount::has_weak_list
// Access: Public // Access: Public

View File

@ -54,6 +54,7 @@ PUBLISHED:
INLINE void test_ref_count_integrity() const; INLINE void test_ref_count_integrity() const;
public: public:
INLINE void local_object();
INLINE bool has_weak_list() const; INLINE bool has_weak_list() const;
INLINE WeakReferenceList *get_weak_list() const; INLINE WeakReferenceList *get_weak_list() const;
@ -61,6 +62,22 @@ public:
INLINE void weak_unref(WeakPointerToVoid *ptv); INLINE void weak_unref(WeakPointerToVoid *ptv);
private: private:
enum {
// We use this value as a flag to indicate an object has been
// indicated as a local object, and should not be deleted except
// by its own destructor. Really, any nonzero value would do, but
// having a large specific number makes the sanity checks easier.
local_ref_count = 10000000,
// This value is used as a flag to indicate that an object has
// just been deleted, and you're looking at deallocated memory.
// It's not guaranteed to stick around, of course (since the
// deleted memory might be repurposed for anything else, including
// a new object), but if you ever do encounter this value in a
// reference count field, you screwed up.
deleted_ref_count = -100,
};
int _ref_count; int _ref_count;
WeakReferenceList *_weak_list; WeakReferenceList *_weak_list;
@ -137,8 +154,6 @@ private:
static TypeHandle _type_handle; static TypeHandle _type_handle;
}; };
#include "referenceCount.I" #include "referenceCount.I"
#endif #endif

View File

@ -206,14 +206,19 @@ ConfigVariableString fake_texture_image
"the same texture file, which will presumably only be loaded " "the same texture file, which will presumably only be loaded "
"once.")); "once."));
ConfigVariableInt vertex_convert_cache ConfigVariableInt geom_cache_size
("vertex-convert-cache", 4194304, // 4 MB ("geom-cache-size", 5000,
PRC_DESC("This is the amount of memory, in bytes, that should be set " PRC_DESC("Specifies the maximum number of entries in the cache "
"aside for storing pre-processed data for rendering vertices. " "for storing pre-processed data for rendering "
"This is not a limit on the actual vertex data, which is " "vertices. This limit is flexible, and may be "
"determined by the model; it is also not a limit on the " "temporarily exceeded if many different Geoms are "
"amount of memory used by the video driver or the system " "pre-processed during the space of a single frame."));
"graphics interface, which Panda has no control over."));
ConfigVariableInt geom_cache_min_frames
("geom-cache-min-frames", 1,
PRC_DESC("Specifies the minimum number of frames any one particular "
"object will remain in the geom cache, even if geom-cache-size "
"is exceeded."));
ConfigVariableDouble default_near ConfigVariableDouble default_near
("default-near", 1.0, ("default-near", 1.0,

View File

@ -66,7 +66,8 @@ extern EXPCL_PANDA ConfigVariableEnum<AutoTextureScale> textures_power_2;
extern EXPCL_PANDA ConfigVariableEnum<AutoTextureScale> textures_square; extern EXPCL_PANDA ConfigVariableEnum<AutoTextureScale> textures_square;
extern EXPCL_PANDA ConfigVariableString fake_texture_image; extern EXPCL_PANDA ConfigVariableString fake_texture_image;
extern EXPCL_PANDA ConfigVariableInt vertex_convert_cache; extern EXPCL_PANDA ConfigVariableInt geom_cache_size;
extern EXPCL_PANDA ConfigVariableInt geom_cache_min_frames;
extern ConfigVariableDouble default_near; extern ConfigVariableDouble default_near;
extern ConfigVariableDouble default_far; extern ConfigVariableDouble default_far;

View File

@ -187,11 +187,34 @@ get_modified() const {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
INLINE qpGeom::CacheEntry:: INLINE qpGeom::CacheEntry::
CacheEntry(const qpGeomVertexData *source_data, const qpGeomMunger *modifier) : CacheEntry(const qpGeomVertexData *source_data, const qpGeomMunger *modifier) :
_source(NULL),
_source_data(source_data), _source_data(source_data),
_modifier(modifier) _modifier(modifier),
_geom_result(NULL),
_data_result(NULL)
{ {
} }
////////////////////////////////////////////////////////////////////
// Function: qpGeom::CacheEntry::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
INLINE qpGeom::CacheEntry::
CacheEntry(qpGeom *source, const qpGeomVertexData *source_data,
const qpGeomMunger *modifier, const qpGeom *geom_result,
const qpGeomVertexData *data_result) :
_source(source),
_source_data(source_data),
_modifier(modifier),
_geom_result(geom_result),
_data_result(data_result)
{
if (_geom_result != _source) {
_geom_result->ref();
}
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: qpGeom::CacheEntry::operator < // Function: qpGeom::CacheEntry::operator <
// Access: Public // Access: Public

View File

@ -469,7 +469,7 @@ munge_geom(const qpGeomMunger *munger,
{ {
CDReader cdata(_cycler); CDReader cdata(_cycler);
CacheEntry temp_entry(source_data, munger); CacheEntry temp_entry(source_data, munger);
temp_entry.ref(); // big ugly hack to allow a stack-allocated ReferenceCount object. temp_entry.local_object();
Cache::const_iterator ci = cdata->_cache.find(&temp_entry); Cache::const_iterator ci = cdata->_cache.find(&temp_entry);
if (ci != cdata->_cache.end()) { if (ci != cdata->_cache.end()) {
CacheEntry *entry = (*ci); CacheEntry *entry = (*ci);
@ -483,7 +483,6 @@ munge_geom(const qpGeomMunger *munger,
entry->refresh(); entry->refresh();
result = entry->_geom_result; result = entry->_geom_result;
data = entry->_data_result; data = entry->_data_result;
temp_entry.unref();
return; return;
} }
@ -496,7 +495,6 @@ munge_geom(const qpGeomMunger *munger,
CDWriter cdataw(((qpGeom *)this)->_cycler, cdata); CDWriter cdataw(((qpGeom *)this)->_cycler, cdata);
cdataw->_cache.erase(entry); cdataw->_cache.erase(entry);
} }
temp_entry.unref();
} }
// Ok, invoke the munger. // Ok, invoke the munger.
@ -513,10 +511,8 @@ munge_geom(const qpGeomMunger *munger,
CacheEntry *entry; CacheEntry *entry;
{ {
CDWriter cdata(((qpGeom *)this)->_cycler); CDWriter cdata(((qpGeom *)this)->_cycler);
entry = new CacheEntry(source_data, munger); entry = new CacheEntry((qpGeom *)this, source_data, munger,
entry->_source = (qpGeom *)this; result, data);
entry->_geom_result = result;
entry->_data_result = data;
bool inserted = cdata->_cache.insert(entry).second; bool inserted = cdata->_cache.insert(entry).second;
nassertv(inserted); nassertv(inserted);
} }
@ -880,6 +876,18 @@ fillin(DatagramIterator &scan, BamReader *manager) {
manager->read_cdata(scan, _cycler); manager->read_cdata(scan, _cycler);
} }
////////////////////////////////////////////////////////////////////
// Function: qpGeom::CacheEntry::Destructor
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
qpGeom::CacheEntry::
~CacheEntry() {
if (_geom_result != _source) {
unref_delete(_geom_result);
}
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: qpGeom::CacheEntry::evict_callback // Function: qpGeom::CacheEntry::evict_callback
// Access: Public, Virtual // Access: Public, Virtual
@ -900,17 +908,6 @@ evict_callback() {
_source->_cycler.release_write_stage(0, cdata); _source->_cycler.release_write_stage(0, cdata);
} }
////////////////////////////////////////////////////////////////////
// Function: qpGeom::CacheEntry::get_result_size
// Access: Public, Virtual
// Description: Returns the approximate number of bytes represented
// by the computed result.
////////////////////////////////////////////////////////////////////
int qpGeom::CacheEntry::
get_result_size() const {
return _geom_result->get_num_bytes() + _data_result->get_num_bytes();
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: qpGeom::CacheEntry::output // Function: qpGeom::CacheEntry::output
// Access: Public, Virtual // Access: Public, Virtual

View File

@ -134,22 +134,27 @@ private:
// We have to use reference-counting pointers here instead of having // We have to use reference-counting pointers here instead of having
// explicit cleanup in the GeomVertexFormat destructor, because the // explicit cleanup in the GeomVertexFormat destructor, because the
// cache needs to be stored in the CycleData, which makes accurate // cache needs to be stored in the CycleData, which makes accurate
// cleanup more difficult. We use the GeomVertexCacheManager class // cleanup more difficult. We use the GeomCacheManager class to
// to avoid cache bloat. // avoid cache bloat.
class CacheEntry : public qpGeomCacheEntry { class CacheEntry : public qpGeomCacheEntry {
public: public:
INLINE CacheEntry(const qpGeomVertexData *source_data, INLINE CacheEntry(const qpGeomVertexData *source_data,
const qpGeomMunger *modifier); const qpGeomMunger *modifier);
INLINE CacheEntry(qpGeom *source,
const qpGeomVertexData *source_data,
const qpGeomMunger *modifier,
const qpGeom *geom_result,
const qpGeomVertexData *data_result);
virtual ~CacheEntry();
INLINE bool operator < (const CacheEntry &other) const; INLINE bool operator < (const CacheEntry &other) const;
virtual void evict_callback(); virtual void evict_callback();
virtual int get_result_size() const;
virtual void output(ostream &out) const; virtual void output(ostream &out) const;
qpGeom *_source; qpGeom *_source;
CPT(qpGeomVertexData) _source_data; CPT(qpGeomVertexData) _source_data;
CPT(qpGeomMunger) _modifier; CPT(qpGeomMunger) _modifier;
CPT(qpGeom) _geom_result; const qpGeom *_geom_result; // ref-counted if not same as _source
CPT(qpGeomVertexData) _data_result; CPT(qpGeomVertexData) _data_result;
}; };
typedef pset<PT(CacheEntry), IndirectLess<CacheEntry> > Cache; typedef pset<PT(CacheEntry), IndirectLess<CacheEntry> > Cache;

View File

@ -30,27 +30,6 @@ qpGeomCacheEntry() {
#endif #endif
} }
////////////////////////////////////////////////////////////////////
// Function: qpGeomCacheEntry::refresh
// Access: Public
// Description: Marks the cache entry recently used, so it will not
// be evicted for a while.
////////////////////////////////////////////////////////////////////
INLINE void qpGeomCacheEntry::
refresh() {
nassertv(_next != (qpGeomCacheEntry *)NULL && _prev != (qpGeomCacheEntry *)NULL);
qpGeomCacheManager *cache_mgr = qpGeomCacheManager::get_global_ptr();
MutexHolder holder(cache_mgr->_lock);
remove_from_list();
insert_before(cache_mgr->_list);
int new_size = get_result_size();
cache_mgr->_total_size += (new_size - _result_size);
_result_size = new_size;
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: qpGeomCacheEntry::remove_from_list // Function: qpGeomCacheEntry::remove_from_list
// Access: Private // Access: Private

View File

@ -20,6 +20,7 @@
#include "qpgeomCacheManager.h" #include "qpgeomCacheManager.h"
#include "mutexHolder.h" #include "mutexHolder.h"
#include "config_gobj.h" #include "config_gobj.h"
#include "clockObject.h"
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: qpGeomCacheEntry::Destructor // Function: qpGeomCacheEntry::Destructor
@ -41,21 +42,20 @@ record() {
nassertr(_next == (qpGeomCacheEntry *)NULL && _prev == (qpGeomCacheEntry *)NULL, NULL); nassertr(_next == (qpGeomCacheEntry *)NULL && _prev == (qpGeomCacheEntry *)NULL, NULL);
PT(qpGeomCacheEntry) keepme = this; PT(qpGeomCacheEntry) keepme = this;
_result_size = get_result_size();
qpGeomCacheManager *cache_mgr = qpGeomCacheManager::get_global_ptr(); qpGeomCacheManager *cache_mgr = qpGeomCacheManager::get_global_ptr();
MutexHolder holder(cache_mgr->_lock); MutexHolder holder(cache_mgr->_lock);
if (gobj_cat.is_debug()) { if (gobj_cat.is_debug()) {
gobj_cat.debug() gobj_cat.debug()
<< "recording cache entry: " << *this << ", total_size = " << "recording cache entry: " << *this << ", total_size = "
<< cache_mgr->_total_size + _result_size << "\n"; << cache_mgr->_total_size + 1 << "\n";
} }
insert_before(cache_mgr->_list); insert_before(cache_mgr->_list);
cache_mgr->_total_size += _result_size; ++cache_mgr->_total_size;
cache_mgr->_geom_cache_size_pcollector.set_level(cache_mgr->_total_size); cache_mgr->_geom_cache_size_pcollector.set_level(cache_mgr->_total_size);
cache_mgr->_geom_cache_record_pcollector.add_level(1); cache_mgr->_geom_cache_record_pcollector.add_level(1);
_last_frame_used = ClockObject::get_global_clock()->get_frame_count();
// Increment our own reference count while we're in the queue, just // Increment our own reference count while we're in the queue, just
// so we don't have to play games with it later--this is inner-loop // so we don't have to play games with it later--this is inner-loop
@ -70,6 +70,25 @@ record() {
return this; return this;
} }
////////////////////////////////////////////////////////////////////
// Function: qpGeomCacheEntry::refresh
// Access: Public
// Description: Marks the cache entry recently used, so it will not
// be evicted for a while.
////////////////////////////////////////////////////////////////////
void qpGeomCacheEntry::
refresh() {
nassertv(_next != (qpGeomCacheEntry *)NULL && _prev != (qpGeomCacheEntry *)NULL);
qpGeomCacheManager *cache_mgr = qpGeomCacheManager::get_global_ptr();
MutexHolder holder(cache_mgr->_lock);
remove_from_list();
insert_before(cache_mgr->_list);
_last_frame_used = ClockObject::get_global_clock()->get_frame_count();
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: qpGeomCacheEntry::erase // Function: qpGeomCacheEntry::erase
// Access: Public // Access: Public
@ -92,7 +111,7 @@ erase() {
MutexHolder holder(cache_mgr->_lock); MutexHolder holder(cache_mgr->_lock);
remove_from_list(); remove_from_list();
cache_mgr->_total_size -= _result_size; --cache_mgr->_total_size;
cache_mgr->_geom_cache_size_pcollector.set_level(cache_mgr->_total_size); cache_mgr->_geom_cache_size_pcollector.set_level(cache_mgr->_total_size);
cache_mgr->_geom_cache_erase_pcollector.add_level(1); cache_mgr->_geom_cache_erase_pcollector.add_level(1);
@ -109,17 +128,6 @@ void qpGeomCacheEntry::
evict_callback() { evict_callback() {
} }
////////////////////////////////////////////////////////////////////
// Function: qpGeomCacheEntry::get_result_size
// Access: Public, Virtual
// Description: Returns the approximate number of bytes represented
// by the computed result.
////////////////////////////////////////////////////////////////////
int qpGeomCacheEntry::
get_result_size() const {
return 0;
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: qpGeomCacheEntry::output // Function: qpGeomCacheEntry::output
// Access: Public, Virtual // Access: Public, Virtual

View File

@ -43,15 +43,14 @@ public:
virtual ~qpGeomCacheEntry(); virtual ~qpGeomCacheEntry();
PT(qpGeomCacheEntry) record(); PT(qpGeomCacheEntry) record();
INLINE void refresh(); void refresh();
PT(qpGeomCacheEntry) erase(); PT(qpGeomCacheEntry) erase();
virtual void evict_callback(); virtual void evict_callback();
virtual int get_result_size() const;
virtual void output(ostream &out) const; virtual void output(ostream &out) const;
private: private:
int _result_size; int _last_frame_used;
INLINE void remove_from_list(); INLINE void remove_from_list();
INLINE void insert_before(qpGeomCacheEntry *node); INLINE void insert_before(qpGeomCacheEntry *node);

View File

@ -20,37 +20,39 @@
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: qpGeomCacheManager::set_max_size // Function: qpGeomCacheManager::set_max_size
// Access: Published // Access: Published
// Description: Specifies the amount of memory, in bytes, that should // Description: Specifies the maximum number of entries in the cache
// be set aside for storing pre-processed data for // for storing pre-processed data for rendering
// rendering vertices. This is not a limit on the // vertices. This limit is flexible, and may be
// actual vertex data, which is what it is; it is also // temporarily exceeded if many different Geoms are
// not a limit on the amount of memory used by the video // pre-processed during the space of a single frame.
// driver or the system graphics interface, which Panda //
// has no control over. // This is not a limit on the actual vertex data, which
// is what it is; it is also not a limit on the amount
// of memory used by the video driver or the system
// graphics interface, which Panda has no control over.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
INLINE void qpGeomCacheManager:: INLINE void qpGeomCacheManager::
set_max_size(int max_size) const { set_max_size(int max_size) const {
// We directly change the config variable. // We directly change the config variable.
vertex_convert_cache = max_size; geom_cache_size = max_size;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: qpGeomCacheManager::get_max_size // Function: qpGeomCacheManager::get_max_size
// Access: Published // Access: Published
// Description: Returns the amount of memory, in bytes, that should // Description: Returns the maximum number of entries in the cache
// be set aside for storing pre-processed data for // for storing pre-processed data for rendering
// rendering vertices. See set_max_size(). // vertices. See set_max_size().
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
INLINE int qpGeomCacheManager:: INLINE int qpGeomCacheManager::
get_max_size() const { get_max_size() const {
return vertex_convert_cache; return geom_cache_size;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: qpGeomCacheManager::get_total_size // Function: qpGeomCacheManager::get_total_size
// Access: Published // Access: Published
// Description: Returns the amount of memory, in bytes, currently // Description: Returns the number of entries currently in the cache.
// consumed by the cache of pre-processed vertex data.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
INLINE int qpGeomCacheManager:: INLINE int qpGeomCacheManager::
get_total_size() const { get_total_size() const {

View File

@ -77,21 +77,36 @@ void qpGeomCacheManager::
evict_old_entries() { evict_old_entries() {
MutexHolder holder(_lock); MutexHolder holder(_lock);
int current_frame = ClockObject::get_global_clock()->get_frame_count();
int min_frames = geom_cache_min_frames;
int max_size = get_max_size(); int max_size = get_max_size();
while (_total_size > max_size) { while (_total_size > max_size) {
PT(qpGeomCacheEntry) entry = _list->_next; PT(qpGeomCacheEntry) entry = _list->_next;
nassertv(entry != _list); nassertv(entry != _list);
if (current_frame - entry->_last_frame_used < min_frames) {
// Never mind, this one is too new.
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Oldest element in cache is "
<< current_frame - entry->_last_frame_used
<< " frames; keeping cache at " << _total_size << " entries.\n";
}
break;
}
entry->unref(); entry->unref();
if (gobj_cat.is_debug()) { if (gobj_cat.is_debug()) {
gobj_cat.debug() gobj_cat.debug()
<< "cache total_size = " << _total_size << ", max_size = " << "cache total_size = " << _total_size << " entries, max_size = "
<< max_size << ", removing " << *entry << "\n"; << max_size << ", removing " << *entry << "\n";
} }
entry->evict_callback(); entry->evict_callback();
_total_size -= entry->_result_size; --_total_size;
entry->remove_from_list(); entry->remove_from_list();
_geom_cache_evict_pcollector.add_level(1); _geom_cache_evict_pcollector.add_level(1);
} }

View File

@ -252,17 +252,6 @@ do_unregister() {
_formats_by_animation.clear(); _formats_by_animation.clear();
} }
////////////////////////////////////////////////////////////////////
// Function: qpGeomMunger::CacheEntry::get_result_size
// Access: Public, Virtual
// Description: Returns the approximate number of bytes represented
// by the computed result.
////////////////////////////////////////////////////////////////////
int qpGeomMunger::CacheEntry::
get_result_size() const {
return sizeof(qpGeomMunger);
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: qpGeomMunger::CacheEntry::output // Function: qpGeomMunger::CacheEntry::output
// Access: Public, Virtual // Access: Public, Virtual

View File

@ -118,7 +118,6 @@ private:
private: private:
class CacheEntry : public qpGeomCacheEntry { class CacheEntry : public qpGeomCacheEntry {
public: public:
virtual int get_result_size() const;
virtual void output(ostream &out) const; virtual void output(ostream &out) const;
PT(qpGeomMunger) _munger; PT(qpGeomMunger) _munger;

View File

@ -300,12 +300,7 @@ clear_vertices() {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void qpGeomPrimitive:: void qpGeomPrimitive::
offset_vertices(int offset) { offset_vertices(int offset) {
CDWriter cdata(_cycler); qpGeomVertexRewriter index(modify_vertices(), 0);
cdata->_got_minmax = false;
qpGeomVertexRewriter index(cdata->_vertices, 0);
while (!index.is_at_end()) { while (!index.is_at_end()) {
index.set_data1i(index.get_data1i() + offset); index.set_data1i(index.get_data1i() + offset);
} }

View File

@ -136,3 +136,9 @@ CData(const qpGeomVertexArrayData::CData &copy) :
_modified(copy._modified) _modified(copy._modified)
{ {
} }
INLINE ostream &
operator << (ostream &out, const qpGeomVertexArrayData &obj) {
obj.output(out);
return out;
}

View File

@ -159,6 +159,26 @@ set_usage_hint(qpGeomVertexArrayData::UsageHint usage_hint) {
cdata->_modified = qpGeom::get_next_modified(); cdata->_modified = qpGeom::get_next_modified();
} }
////////////////////////////////////////////////////////////////////
// Function: qpGeomVertexArrayData::output
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
void qpGeomVertexArrayData::
output(ostream &out) const {
out << get_num_rows() << " rows: " << *get_array_format();
}
////////////////////////////////////////////////////////////////////
// Function: qpGeomVertexArrayData::write
// Access: Published
// Description:
////////////////////////////////////////////////////////////////////
void qpGeomVertexArrayData::
write(ostream &out, int indent_level) const {
_array_format->write_with_data(out, indent_level, this);
}
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: qpGeomVertexArrayData::modify_data // Function: qpGeomVertexArrayData::modify_data
// Access: Public // Access: Public

View File

@ -81,6 +81,9 @@ PUBLISHED:
INLINE int get_data_size_bytes() const; INLINE int get_data_size_bytes() const;
INLINE UpdateSeq get_modified() const; INLINE UpdateSeq get_modified() const;
void output(ostream &out) const;
void write(ostream &out, int indent_level = 0) const;
public: public:
INLINE CPTA_uchar get_data() const; INLINE CPTA_uchar get_data() const;
PTA_uchar modify_data(); PTA_uchar modify_data();
@ -158,6 +161,8 @@ private:
friend class PreparedGraphicsObjects; friend class PreparedGraphicsObjects;
}; };
INLINE ostream &operator << (ostream &out, const qpGeomVertexArrayData &obj);
#include "qpgeomVertexArrayData.I" #include "qpgeomVertexArrayData.I"
#endif #endif

View File

@ -435,13 +435,13 @@ write(ostream &out, int indent_level) const {
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void qpGeomVertexArrayFormat:: void qpGeomVertexArrayFormat::
write_with_data(ostream &out, int indent_level, write_with_data(ostream &out, int indent_level,
const qpGeomVertexData *data, int array_index) const { const qpGeomVertexArrayData *array_data) const {
consider_sort_columns(); consider_sort_columns();
int num_vertices = data->get_num_rows(); int num_rows = array_data->get_num_rows();
qpGeomVertexReader reader(data); qpGeomVertexReader reader(array_data);
for (int i = 0; i < num_vertices; i++) { for (int i = 0; i < num_rows; i++) {
indent(out, indent_level) indent(out, indent_level)
<< "row " << i << ":\n"; << "row " << i << ":\n";
reader.set_row(i); reader.set_row(i);
@ -449,7 +449,7 @@ write_with_data(ostream &out, int indent_level,
for (ci = _columns.begin(); ci != _columns.end(); ++ci) { for (ci = _columns.begin(); ci != _columns.end(); ++ci) {
const qpGeomVertexColumn *column = (*ci); const qpGeomVertexColumn *column = (*ci);
int num_values = min(column->get_num_values(), 4); int num_values = min(column->get_num_values(), 4);
reader.set_column(array_index, column); reader.set_column(0, column);
const LVecBase4f &d = reader.get_data4f(); const LVecBase4f &d = reader.get_data4f();
indent(out, indent_level + 2) indent(out, indent_level + 2)

View File

@ -29,6 +29,7 @@
class qpGeomVertexFormat; class qpGeomVertexFormat;
class qpGeomVertexData; class qpGeomVertexData;
class qpGeomVertexArrayData;
class InternalName; class InternalName;
class FactoryParams; class FactoryParams;
class BamWriter; class BamWriter;
@ -109,7 +110,7 @@ PUBLISHED:
void output(ostream &out) const; void output(ostream &out) const;
void write(ostream &out, int indent_level = 0) const; void write(ostream &out, int indent_level = 0) const;
void write_with_data(ostream &out, int indent_level, void write_with_data(ostream &out, int indent_level,
const qpGeomVertexData *data, int array_index) const; const qpGeomVertexArrayData *array_data) const;
public: public:
int compare_to(const qpGeomVertexArrayFormat &other) const; int compare_to(const qpGeomVertexArrayFormat &other) const;

View File

@ -553,7 +553,8 @@ convert_to(const qpGeomVertexFormat *new_format) const {
// Okay, convert the data to the new format. // Okay, convert the data to the new format.
if (gobj_cat.is_debug()) { if (gobj_cat.is_debug()) {
gobj_cat.debug() gobj_cat.debug()
<< "Converting " << get_num_rows() << " rows.\n"; << "Converting " << get_num_rows() << " rows from " << *_format
<< " to " << *new_format << "\n";
} }
PStatTimer timer(_convert_pcollector); PStatTimer timer(_convert_pcollector);

View File

@ -418,7 +418,7 @@ write_with_data(ostream &out, int indent_level,
CPTA_uchar array_data = data->get_array(i)->get_data(); CPTA_uchar array_data = data->get_array(i)->get_data();
indent(out, indent_level) indent(out, indent_level)
<< "Array " << i << " (" << (void *)array_data.p() << "):\n"; << "Array " << i << " (" << (void *)array_data.p() << "):\n";
_arrays[i]->write_with_data(out, indent_level + 2, data, i); _arrays[i]->write_with_data(out, indent_level + 2, data->get_array(i));
} }
} }

View File

@ -169,8 +169,8 @@ static LevelCollectorProperties level_properties[] = {
{ 1, "Vertex buffer switch", { 0.0, 0.6, 0.8 }, "", 500 }, { 1, "Vertex buffer switch", { 0.0, 0.6, 0.8 }, "", 500 },
{ 1, "Vertex buffer switch:Vertex", { 0.8, 0.0, 0.6 } }, { 1, "Vertex buffer switch:Vertex", { 0.8, 0.0, 0.6 } },
{ 1, "Vertex buffer switch:Index", { 0.8, 0.6, 0.3 } }, { 1, "Vertex buffer switch:Index", { 0.8, 0.6, 0.3 } },
{ 1, "Geom cache size", { 1.0, 0.8, 0.6 }, "MB", 12, 1048576 }, { 1, "Geom cache size", { 0.6, 0.8, 0.6 }, "", 500 },
{ 1, "Geom cache operations", { 1.0, 0.8, 0.6 }, "", 500 }, { 1, "Geom cache operations", { 1.0, 0.6, 0.6 }, "", 500 },
{ 1, "Geom cache operations:record", { 0.2, 0.4, 0.8 } }, { 1, "Geom cache operations:record", { 0.2, 0.4, 0.8 } },
{ 1, "Geom cache operations:erase", { 0.4, 0.8, 0.2 } }, { 1, "Geom cache operations:erase", { 0.4, 0.8, 0.2 } },
{ 1, "Geom cache operations:evict", { 0.8, 0.2, 0.4 } }, { 1, "Geom cache operations:evict", { 0.8, 0.2, 0.4 } },