From 684810cbdd1fde354e723e6706998b9a73dfeea2 Mon Sep 17 00:00:00 2001 From: David Rose Date: Thu, 4 Sep 2008 21:26:38 +0000 Subject: [PATCH] AdaptiveLru --- panda/src/display/lru.cxx | 135 +--- panda/src/display/lru.h | 2 + panda/src/gobj/Sources.pp | 3 + panda/src/gobj/adaptiveLru.I | 229 +++++++ panda/src/gobj/adaptiveLru.cxx | 676 +++++++++++++++++++++ panda/src/gobj/adaptiveLru.h | 207 +++++++ panda/src/gobj/bufferContext.I | 24 + panda/src/gobj/bufferContext.h | 2 + panda/src/gobj/bufferContextChain.cxx | 18 + panda/src/gobj/bufferContextChain.h | 2 + panda/src/gobj/bufferResidencyTracker.cxx | 29 + panda/src/gobj/bufferResidencyTracker.h | 2 + panda/src/gobj/config_gobj.cxx | 12 + panda/src/gobj/config_gobj.h | 2 + panda/src/gobj/gobj_composite1.cxx | 1 + panda/src/gobj/indexBufferContext.I | 4 +- panda/src/gobj/indexBufferContext.cxx | 20 + panda/src/gobj/indexBufferContext.h | 7 +- panda/src/gobj/preparedGraphicsObjects.cxx | 30 + panda/src/gobj/preparedGraphicsObjects.h | 5 +- panda/src/gobj/savedContext.cxx | 21 + panda/src/gobj/savedContext.h | 12 +- panda/src/gobj/simpleLru.I | 15 +- panda/src/gobj/simpleLru.cxx | 79 ++- panda/src/gobj/simpleLru.h | 21 +- panda/src/gobj/textureContext.I | 4 +- panda/src/gobj/textureContext.cxx | 20 + panda/src/gobj/textureContext.h | 7 +- panda/src/gobj/vertexBufferContext.I | 4 +- panda/src/gobj/vertexBufferContext.cxx | 20 + panda/src/gobj/vertexBufferContext.h | 7 +- panda/src/gobj/vertexDataPage.cxx | 20 + panda/src/gobj/vertexDataPage.h | 8 + 33 files changed, 1499 insertions(+), 149 deletions(-) create mode 100644 panda/src/gobj/adaptiveLru.I create mode 100644 panda/src/gobj/adaptiveLru.cxx create mode 100644 panda/src/gobj/adaptiveLru.h diff --git a/panda/src/display/lru.cxx b/panda/src/display/lru.cxx index eb879ccd49..f7af672f6b 100644 --- a/panda/src/display/lru.cxx +++ b/panda/src/display/lru.cxx @@ -14,7 +14,7 @@ //#include "stdafx.h" -#define LRU_UNIT_TEST 0 +#define LRU_UNIT_TEST 1 #include #include @@ -22,6 +22,7 @@ #include "lru.h" +#define OutputDebugString(string) cerr << string static const int HIGH_PRIORITY_SCALE = 4; static const int LOW_PRIORITY_RANGE = 25; @@ -145,7 +146,7 @@ Lru::~Lru ( ) LruPage::LruPage ( ) { if(this) { - memset(&this->_m, 0, sizeof (LruPageVariables)); + memset(&this->_m, 0, (char *)&this->_m.name - (char *)&this->_m); _m.name = ""; } } @@ -331,6 +332,7 @@ void Lru::free_page (LruPage *lru_page) //////////////////////////////////////////////////////////////////// void Lru::add_page (LruPagePriority priority, LruPage *lru_page) { + cerr << "adding " << lru_page << " with " << priority << "\n"; if(lru_page) { LruMutexHolder(this->_m.mutex); @@ -358,6 +360,7 @@ void Lru::add_page (LruPagePriority priority, LruPage *lru_page) //////////////////////////////////////////////////////////////////// void Lru::add_cached_page (LruPagePriority priority, LruPage *lru_page) { + cerr << "adding " << lru_page << " with " << priority << "\n"; if(lru_page) { LruMutexHolder(this->_m.mutex); @@ -385,6 +388,7 @@ void Lru::add_cached_page (LruPagePriority priority, LruPage *lru_page) //////////////////////////////////////////////////////////////////// void Lru::remove_page (LruPage *lru_page) { + cerr << "removing " << lru_page << "\n"; if(this) { if(this->_m.total_pages > 0) { if(lru_page) { @@ -595,7 +599,7 @@ void Lru::update_lru_page (LruPage *lru_page) { #if LRU_UNIT_TEST - if(false) { + if(true) { char string[256]; sprintf(string, " UPDATE %d\n", lru_page->_m.identifier); @@ -659,6 +663,7 @@ void Lru::update_lru_page (LruPage *lru_page) } delta_priority = target_priority - lru_page->_m.priority; + cerr << "target_priority = " << target_priority << ", delta = " << delta_priority << ", util = " << lru_page->_m.average_frame_utilization << "\n"; lru_page->change_priority(delta_priority); } } @@ -689,127 +694,7 @@ void Lru::update_lru_page (LruPage *lru_page) //////////////////////////////////////////////////////////////////// void Lru::update_lru_page_old (LruPage *lru_page) { - -#if LRU_UNIT_TEST - if(false) { - char string[256]; - - sprintf(string, " UPDATE %d\n", lru_page->_m.identifier); - OutputDebugString(string); - } -#endif - - if(lru_page->_m.v.lock == false) { - int delta_priority; - - delta_priority = 0; - if(false && lru_page->_m.total_usage > 0) { - int lifetime_frames; - - lifetime_frames = this->_m.current_frame_identifier - - lru_page->_m.first_frame_identifier; - if(lifetime_frames >= 10) { - float one_over_update_frames; - - if(lru_page->_m.update_frame_identifier) { - int target_priority; - int integer_update_frames; - float update_frames; - float update_average_frame_utilization; - float average_frame_bandwidth_utilization; - - integer_update_frames = (this->_m.current_frame_identifier - - lru_page->_m.update_frame_identifier); - if(integer_update_frames > 0) { - update_frames = ( float ) integer_update_frames; - one_over_update_frames = 1.0f / update_frames; - - update_average_frame_utilization = - (float) (lru_page->_m.update_total_usage) * - one_over_update_frames; - - lru_page->_m.average_frame_utilization = - calculate_exponential_moving_average ( - update_average_frame_utilization, this->_m.weight, - lru_page->_m.average_frame_utilization); - - average_frame_bandwidth_utilization = - lru_page->_m.average_frame_utilization * - lru_page->_m.size; - - target_priority = (int) (average_frame_bandwidth_utilization * - this->_m.frame_bandwidth_factor); - - target_priority = (LPP_TotalPriorities - 1) - target_priority; - if(target_priority < 0) { - target_priority = 0; - } - if(target_priority >= LPP_TotalPriorities) { - target_priority = LPP_TotalPriorities - 1; - } - - delta_priority = target_priority - lru_page->_m.priority; - lru_page->change_priority(delta_priority); - } - } - - lru_page->_m.update_frame_identifier = - this->_m.current_frame_identifier; - - lru_page->_m.update_total_usage = 0; - } - } - - if(delta_priority == 0) { - if(this->_m.current_frame_identifier - == lru_page->_m.current_frame_identifier) { - // page used during this frame twice or more => - // increase priority - if(lru_page->_m.current_frame_usage >= 2) { - if(lru_page->_m.priority >= LPP_High) { - lru_page->change_priority(-2); - } - } - - if(lru_page->_m.total_frame_page_faults >= 1) { - // multiple page faults this frame => increase priority - if(lru_page->_m.total_frame_page_faults >= 2) { - if(lru_page->_m.priority >= LPP_High) { - lru_page->change_priority(-2); - } - } - else { - // single page faults this frame => increase priority - if(lru_page->_m.priority >= LPP_High) { - lru_page->change_priority(-1); - } - } - } - } - else { - // page not used this frame - int last_access_delta; - - last_access_delta - = this->_m.current_frame_identifier - - lru_page->_m.current_frame_identifier; - if(last_access_delta > 1) { - if(lru_page->_m.priority < LPP_Low) { - lru_page->change_priority(+1); - } - } - } - } - - if(lru_page->_m.priority_change) { - if(this->_m.total_lru_page_priority_changes - < FRAME_MAXIMUM_PRIORITY_CHANGES) { - this->_m.lru_page_priority_change_array - [this->_m.total_lru_page_priority_changes]= lru_page; - this->_m.total_lru_page_priority_changes++; - } - } - } + nassertv(false); } //////////////////////////////////////////////////////////////////// @@ -1154,6 +1039,8 @@ void Lru::calculate_lru_statistics (void) float calculate_exponential_moving_average(float value, float weight, float average) { + cerr << "exp(" << value << ", " << weight << ", " << average << " = " + << ((value - average) * weight) + average << "\n"; return ((value - average) * weight) + average; } diff --git a/panda/src/display/lru.h b/panda/src/display/lru.h index 96ed91b4b8..7046e65219 100644 --- a/panda/src/display/lru.h +++ b/panda/src/display/lru.h @@ -263,7 +263,9 @@ float calculate_exponential_moving_average (float value, float weight, float ave bool default_page_in_function (LruPage *lru_page); bool default_page_out_function (LruPage *lru_page); +BEGIN_PUBLISH void test_ema (void); void test_lru (void); +END_PUBLISH #endif diff --git a/panda/src/gobj/Sources.pp b/panda/src/gobj/Sources.pp index 90729349dd..513b38a67a 100644 --- a/panda/src/gobj/Sources.pp +++ b/panda/src/gobj/Sources.pp @@ -11,6 +11,7 @@ #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx #define SOURCES \ + adaptiveLru.I adaptiveLru.h \ bufferContext.I bufferContext.h \ bufferContextChain.I bufferContextChain.h \ bufferResidencyTracker.I bufferResidencyTracker.h \ @@ -75,6 +76,7 @@ videoTexture.I videoTexture.h #define INCLUDED_SOURCES \ + adaptiveLru.cxx \ bufferContext.cxx \ bufferContextChain.cxx \ bufferResidencyTracker.cxx \ @@ -138,6 +140,7 @@ videoTexture.cxx #define INSTALL_HEADERS \ + adaptiveLru.I adaptiveLru.h \ bufferContext.I bufferContext.h \ bufferContextChain.I bufferContextChain.h \ bufferResidencyTracker.I bufferResidencyTracker.h \ diff --git a/panda/src/gobj/adaptiveLru.I b/panda/src/gobj/adaptiveLru.I new file mode 100644 index 0000000000..b707a520d6 --- /dev/null +++ b/panda/src/gobj/adaptiveLru.I @@ -0,0 +1,229 @@ +// Filename: adaptiveLru.I +// Created by: drose (03Sep08) +// +//////////////////////////////////////////////////////////////////// +// +// 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." +// +//////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::get_total_size +// Access: Published +// Description: Returns the total size of all objects currently +// active on the LRU. +//////////////////////////////////////////////////////////////////// +INLINE size_t AdaptiveLru:: +get_total_size() const { + MutexHolder holder(_lock); + return _total_size; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::get_max_size +// Access: Published +// Description: Returns the max size of all objects that are allowed +// to be active on the LRU. +//////////////////////////////////////////////////////////////////// +INLINE size_t AdaptiveLru:: +get_max_size() const { + MutexHolder holder(_lock); + return _max_size; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::set_max_size +// Access: Published +// Description: Changes the max size of all objects that are allowed +// to be active on the LRU. +// +// If the size is (size_t)-1, there is no limit. +//////////////////////////////////////////////////////////////////// +INLINE void AdaptiveLru:: +set_max_size(size_t max_size) { + MutexHolder holder(_lock); + _max_size = max_size; + if (_total_size > _max_size) { + do_evict_to(_max_size, false); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::consider_evict +// Access: Published +// Description: Evicts a sequence of objects if the queue is full. +//////////////////////////////////////////////////////////////////// +INLINE void AdaptiveLru:: +consider_evict() { + MutexHolder holder(_lock); + if (_total_size > _max_size) { + do_evict_to(_max_size, false); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::evict_to +// Access: Published +// Description: Evicts a sequence of objects until the queue fits +// within the indicated target size, regardless of its +// normal max size. +//////////////////////////////////////////////////////////////////// +INLINE void AdaptiveLru:: +evict_to(size_t target_size) { + MutexHolder holder(_lock); + if (_total_size > target_size) { + do_evict_to(target_size, true); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::validate +// Access: Published +// Description: Checks that the LRU is internally self-consistent. +// Returns true if successful, false if there is some +// problem. +//////////////////////////////////////////////////////////////////// +INLINE bool AdaptiveLru:: +validate() { + MutexHolder holder(_lock); + return do_validate(); +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::set_weight +// Access: Published +// Description: Specifies the weight value used to compute the +// exponential moving average. +//////////////////////////////////////////////////////////////////// +INLINE void AdaptiveLru:: +set_weight(float weight) { + _weight = weight; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::get_weight +// Access: Published +// Description: Returns the weight value used to compute the +// exponential moving average. +//////////////////////////////////////////////////////////////////// +INLINE float AdaptiveLru:: +get_weight() const { + return _weight; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::set_max_updates_per_frame +// Access: Published +// Description: Specifies the maximum number of pages the AdaptiveLru +// will update each frame. This is a performance +// optimization: keeping this number low limits the +// impact of the AdaptiveLru's adaptive algorithm. +//////////////////////////////////////////////////////////////////// +INLINE void AdaptiveLru:: +set_max_updates_per_frame(int max_updates_per_frame) { + _max_updates_per_frame = max_updates_per_frame; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::get_max_updates_per_frame +// Access: Published +// Description: Returns the maximum number of pages the AdaptiveLru +// will update each frame. +//////////////////////////////////////////////////////////////////// +INLINE int AdaptiveLru:: +get_max_updates_per_frame() const { + return _max_updates_per_frame; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::calculate_exponential_moving_average +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +INLINE float AdaptiveLru:: +calculate_exponential_moving_average(float value, float average) const { + return ((value - average) * _weight) + average; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::enqueue_lru +// Access: Published +// Description: Returns the LRU that manages this page, or NULL if it +// is not currently managed by any LRU. +//////////////////////////////////////////////////////////////////// +INLINE AdaptiveLru *AdaptiveLruPage:: +get_lru() const { + return _lru; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::dequeue_lru +// Access: Published +// Description: Removes the page from its AdaptiveLru. +//////////////////////////////////////////////////////////////////// +INLINE void AdaptiveLruPage:: +dequeue_lru() { + enqueue_lru(NULL); +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::mark_used_lru +// Access: Published +// Description: To be called when the page is used; this will move it +// to the tail of the AdaptiveLru queue it is already on. +// +// This method is const because it's not technically +// modifying the contents of the page itself. +//////////////////////////////////////////////////////////////////// +INLINE void AdaptiveLruPage:: +mark_used_lru() const { + if (_lru != (AdaptiveLru *)NULL) { + ((AdaptiveLruPage *)this)->mark_used_lru(_lru); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::mark_used_lru +// Access: Published +// Description: To be called when the page is used; this will move it +// to the tail of the specified AdaptiveLru queue. +//////////////////////////////////////////////////////////////////// +INLINE void AdaptiveLruPage:: +mark_used_lru(AdaptiveLru *lru) { + enqueue_lru(lru); +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::get_lru_size +// Access: Published +// Description: Returns the size of this page as reported to the LRU, +// presumably in bytes. +//////////////////////////////////////////////////////////////////// +INLINE size_t AdaptiveLruPage:: +get_lru_size() const { + return _lru_size; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::set_lru_size +// Access: Published +// Description: Specifies the size of this page, presumably in bytes, +// although any unit is possible. +//////////////////////////////////////////////////////////////////// +INLINE void AdaptiveLruPage:: +set_lru_size(size_t lru_size) { + if (_lru != (AdaptiveLru *)NULL) { + MutexHolder holder(_lru->_lock); + _lru->_total_size -= _lru_size; + _lru->_total_size += lru_size; + _lru_size = lru_size; + } else { + _lru_size = lru_size; + } +} diff --git a/panda/src/gobj/adaptiveLru.cxx b/panda/src/gobj/adaptiveLru.cxx new file mode 100644 index 0000000000..f11e9aabbf --- /dev/null +++ b/panda/src/gobj/adaptiveLru.cxx @@ -0,0 +1,676 @@ +// Filename: adaptiveLru.cxx +// Created by: drose (03Sep08) +// +//////////////////////////////////////////////////////////////////// +// +// 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 "adaptiveLru.h" +#include "config_gobj.h" +#include "indent.h" + +static const int HIGH_PRIORITY_SCALE = 4; +static const int LOW_PRIORITY_RANGE = 25; + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::Constructor +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +AdaptiveLru:: +AdaptiveLru(const string &name, size_t max_size) : + Namable(name) +{ + _total_size = 0; + _max_size = max_size; + + _current_frame_identifier = 0; + _weight = adaptive_lru_weight; + _max_updates_per_frame = adaptive_lru_max_updates_per_frame; + + // Initialize our list heads to empty. + _static_list._next = &_static_list; + _static_list._prev = &_static_list; + + int index; + for (index = 0; index < LPP_TotalPriorities; ++index) { + _page_array[index]._next = &_page_array[index]; + _page_array[index]._prev = &_page_array[index]; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::Destructor +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +AdaptiveLru:: +~AdaptiveLru() { +#ifndef NDEBUG + // We're shutting down. Force-remove everything remaining, but + // don't explicitly evict it (that would force vertex buffers to + // write themselves to disk unnecessarily). + + while (_static_list._next != &_static_list) { + nassertv(_static_list._next != (LinkedListNode *)NULL); + AdaptiveLruPage *page = (AdaptiveLruPage *)(AdaptiveLruPageStaticList *)_static_list._next; + + page->_lru = NULL; + ((AdaptiveLruPageDynamicList *)page)->remove_from_list(); + ((AdaptiveLruPageStaticList *)page)->remove_from_list(); + } +#endif +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::do_partial_lru_update +// Access: Private +// Description: This only updates a number of pages up to the +// specified maximum_updates. Assumes the lock is held. +//////////////////////////////////////////////////////////////////// +void AdaptiveLru:: +do_partial_lru_update(int num_updates) { + // Iterate sequentially through the static list of pages. As we + // process each page, pop it and push it back on the tail. Stop + // when we have processed num_updates, or come back to the starting + // one. + + AdaptiveLruPageStaticList *start_node = (AdaptiveLruPageStaticList *)_static_list._next; + if (start_node == &_static_list) { + // List is empty. + return; + } + + AdaptiveLruPageStaticList *node = start_node; + do { + nassertv(node != &_static_list); + AdaptiveLruPageStaticList *next = (AdaptiveLruPageStaticList *)node->_next; + if (--num_updates <= 0) { + return; + } + + update_page((AdaptiveLruPage *)node); + node->remove_from_list(); + node->insert_before(&_static_list); + node = next; + } while (node != start_node); +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::update_page +// Access: Private +// Description: This updates the page's average utilization. +// Priority LPP_New is considered to be average usage +// of 1.0 (which means the page is used once per frame +// on average). Priorities < LPP_New are for pages +// used more than once per frame and Priorities > +// LPP_New are for pages used less than once per frame. +// +// Assumes the lock is held. +//////////////////////////////////////////////////////////////////// +void AdaptiveLru:: +update_page(AdaptiveLruPage *page) { + int target_priority = page->_priority; + int lifetime_frames = _current_frame_identifier - page->_first_frame_identifier; + if (lifetime_frames >= 1) { + if (page->_update_frame_identifier) { + int update_frames; + + update_frames = (_current_frame_identifier - page->_update_frame_identifier); + if (update_frames > 0) { + float update_average_frame_utilization = + (float) (page->_update_total_usage) / (float)update_frames; + + page->_average_frame_utilization = + calculate_exponential_moving_average(update_average_frame_utilization, + page->_average_frame_utilization); + + target_priority = page->_priority; + if (page->_average_frame_utilization >= 1.0f) { + int integer_average_frame_utilization; + + integer_average_frame_utilization = + (int) ((page->_average_frame_utilization - 1.0f) * + (float) HIGH_PRIORITY_SCALE); + if (integer_average_frame_utilization >= LPP_New) { + integer_average_frame_utilization = LPP_New; + } + integer_average_frame_utilization = LPP_New - + integer_average_frame_utilization; + target_priority = integer_average_frame_utilization; + } else { + int integer_average_frame_utilization; + + integer_average_frame_utilization = (int) + (page->_average_frame_utilization * + (float) LOW_PRIORITY_RANGE); + integer_average_frame_utilization = LOW_PRIORITY_RANGE - + integer_average_frame_utilization; + target_priority = LPP_New + integer_average_frame_utilization; + } + } + } + + page->_update_frame_identifier = _current_frame_identifier; + page->_update_total_usage = 0; + } + + if (target_priority != page->_priority) { + page->_priority = min(max(target_priority, 0), LPP_TotalPriorities - 1); + ((AdaptiveLruPageDynamicList *)page)->remove_from_list(); + ((AdaptiveLruPageDynamicList *)page)->insert_before(&_page_array[page->_priority]); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::enqueue_lru +// Access: Published +// Description: Adds the page to the LRU for the first time, or marks +// it recently-accessed if it has already been added. +// +// If lru is NULL, it means to remove this page from its +// LRU. +//////////////////////////////////////////////////////////////////// +void AdaptiveLruPage:: +enqueue_lru(AdaptiveLru *lru) { + if (lru != _lru && _lru != (AdaptiveLru *)NULL) { + // It was previously on a different LRU. Remove it first. + _lru->do_remove_page(this); + _lru = NULL; + } + + if (lru == _lru) { + if (_lru != (AdaptiveLru *)NULL) { + // It's already on this LRU. Access it. + _lru->do_access_page(this); + } + } else { + nassertv(lru != (AdaptiveLru *)NULL); + // Add it to a new LRU. + _lru = lru; + + _priority = AdaptiveLru::LPP_New; + _first_frame_identifier = _lru->_current_frame_identifier; + _last_frame_identifier = _lru->_current_frame_identifier; + _lru->do_add_page(this); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::count_active_size +// Access: Published +// Description: Returns the total size of the pages that were +// enqueued since the last call to begin_epoch(). +//////////////////////////////////////////////////////////////////// +size_t AdaptiveLru:: +count_active_size() const { + size_t counted_size = 0; + + int minimum_frame_identifier = _current_frame_identifier - 1; + + AdaptiveLruPageStaticList *node = (AdaptiveLruPageStaticList *)_static_list._next; + while (node != &_static_list) { + AdaptiveLruPage *page = (AdaptiveLruPage *)node; + if (page->_current_frame_identifier >= minimum_frame_identifier) { + counted_size += page->_lru_size; + } + node = (AdaptiveLruPageStaticList *)node->_next; + } + + return counted_size; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::begin_epoch +// Access: Published +// Description: Marks the end of the previous epoch and the beginning +// of the next one. This will evict any objects that +// are pending eviction, and also update any internal +// bookkeeping. +//////////////////////////////////////////////////////////////////// +void AdaptiveLru:: +begin_epoch() { + MutexHolder holder(_lock); + do_partial_lru_update(_max_updates_per_frame); + if (_total_size > _max_size) { + do_evict_to(_max_size, false); + } + + ++_current_frame_identifier; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::output +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void AdaptiveLru:: +output(ostream &out) const { + MutexHolder holder(_lock); + out << "AdaptiveLru " << get_name() + << ", " << _total_size << " of " << _max_size; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::write +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void AdaptiveLru:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) << *this << ":\n"; + + // We write out the list backwards. Things we write out first are + // the freshest in the LRU. Things at the end of the list will be + // the next to be evicted. + + MutexHolder holder(_lock); + + int minimum_frame_identifier = _current_frame_identifier - 1; + + int index; + for (index = 0; index < LPP_TotalPriorities; ++index) { + AdaptiveLruPageDynamicList *node = (AdaptiveLruPageDynamicList *)_page_array[index]._prev; + if (node != &_page_array[index]) { + indent(out, indent_level + 2) << "Priority " << index << ":\n"; + while (node != &_page_array[index]) { + AdaptiveLruPage *page = (AdaptiveLruPage *)node; + indent(out, indent_level + 4) << *page; + + if (page->_current_frame_identifier >= minimum_frame_identifier) { + out << " (active)"; + } + out << "\n"; + + node = (AdaptiveLruPageDynamicList *)node->_prev; + } + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::do_add_page +// Access: Private +// Description: Adds a new page the the LRU. +//////////////////////////////////////////////////////////////////// +void AdaptiveLru:: +do_add_page(AdaptiveLruPage *page) { + nassertv(page != (AdaptiveLruPage *)NULL && page->_lru == this); + MutexHolder holder(_lock); + + _total_size += page->_lru_size; + ((AdaptiveLruPageDynamicList *)page)->insert_before(&_page_array[page->_priority]); + ((AdaptiveLruPageStaticList *)page)->insert_before(&_static_list); +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::do_remove_page +// Access: Private +// Description: Removes a page from the LRU. +//////////////////////////////////////////////////////////////////// +void AdaptiveLru:: +do_remove_page(AdaptiveLruPage *page) { + nassertv(page != (AdaptiveLruPage *)NULL && page->_lru == this); + MutexHolder holder(_lock); + + _total_size -= page->_lru_size; + ((AdaptiveLruPageDynamicList *)page)->remove_from_list(); + ((AdaptiveLruPageStaticList *)page)->remove_from_list(); +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::do_access_page +// Access: Private +// Description: Marks a page accessed. +//////////////////////////////////////////////////////////////////// +void AdaptiveLru:: +do_access_page(AdaptiveLruPage *page) { + nassertv(page != (AdaptiveLruPage *)NULL && page->_lru == this); + MutexHolder holder(_lock); + + if (page->_current_frame_identifier == _current_frame_identifier) { + // This is the second or more time this page is accessed this + // frame. + ++(page->_current_frame_usage); + + } else { + // This page has not yet been accessed this frame. Update it. + page->_last_frame_identifier = page->_current_frame_identifier; + page->_current_frame_identifier = _current_frame_identifier; + page->_last_frame_usage = page->_current_frame_usage; + page->_current_frame_usage = 1; + } + + // Move it to the tail of its priority list. + ((AdaptiveLruPageDynamicList *)page)->remove_from_list(); + ((AdaptiveLruPageDynamicList *)page)->insert_before(&_page_array[page->_priority]); + + ++(page->_update_total_usage); +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::do_evict_to +// Access: Private +// Description: Evicts pages until the LRU is within the indicated +// size. Assumes the lock is already held. If +// hard_evict is false, does not evict "active" pages +// that were added within this epoch. +//////////////////////////////////////////////////////////////////// +void AdaptiveLru:: +do_evict_to(size_t target_size, bool hard_evict) { + int attempts; + + attempts = 0; + do { + int minimum_frame_identifier = _current_frame_identifier - 1; + + // page out lower priority pages first + int index; + for (index = LPP_TotalPriorities - 1; index >= 0; index--) { + + // Store the current end of the list. If pages re-enqueue + // themselves during this traversal, we don't want to visit them + // twice. + AdaptiveLruPageDynamicList *end = (AdaptiveLruPageDynamicList *)_page_array[index]._prev; + + AdaptiveLruPageDynamicList *node = (AdaptiveLruPageDynamicList *)_page_array[index]._next; + + while (node != &_page_array[index]) { + AdaptiveLruPageDynamicList *next = (AdaptiveLruPageDynamicList *)node->_next; + AdaptiveLruPage *page = (AdaptiveLruPage *)node; + + if (attempts == 0 && (page->_current_frame_identifier >= minimum_frame_identifier)) { + // avoid swapping out pages used in the current and last + // frame on the first attempt + + } else { + // We must release the lock while we call evict_lru(). + cerr << "evicting page, " << page->_current_frame_identifier + << " vs. " << minimum_frame_identifier << ", attempts = " + << attempts << "\n"; + _lock.release(); + ((AdaptiveLruPage *)node)->evict_lru(); + _lock.lock(); + + if (_total_size <= target_size) { + // We've evicted enough to satisfy our target. + return; + } + } + if (node == end) { + // We've reached the former end of the list. Stop here; + // everything after has been re-queued. + break; + } + node = next; + } + } + attempts++; + } while (hard_evict && attempts < 2); +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLru::do_validate +// Access: Private +// Description: Checks that the LRU is internally consistent. Assume +// the lock is already held. +//////////////////////////////////////////////////////////////////// +bool AdaptiveLru:: +do_validate() { + bool okflag = true; + pset pages; + + // First, walk through the dynamic pages. + size_t counted_size = 0; + int index; + for (index = 0; index < LPP_TotalPriorities; ++index) { + AdaptiveLruPageDynamicList *node = (AdaptiveLruPageDynamicList *)_page_array[index]._next; + while (node != &_page_array[index]) { + AdaptiveLruPage *page = (AdaptiveLruPage *)node; + counted_size += page->_lru_size; + if (page->_priority != index) { + nout << "page " << page << " has priority " << page->_priority + << " but is in queue " << index << "\n"; + okflag = false; + } + + bool inserted_ok = pages.insert(page).second; + if (!inserted_ok) { + nout << "page " << page << " appears more than once in the dynamic index\n"; + okflag = false; + } + node = (AdaptiveLruPageDynamicList *)node->_next; + } + } + + if (counted_size != _total_size) { + nout << "count " << counted_size << " bytes in dynamic index, but have " << _total_size << " on record\n"; + okflag = false; + } + + // Now, walk through the static pages. + counted_size = 0; + AdaptiveLruPageStaticList *node = (AdaptiveLruPageStaticList *)_static_list._next; + while (node != &_static_list) { + AdaptiveLruPage *page = (AdaptiveLruPage *)node; + counted_size += page->_lru_size; + + if (pages.find(page) == pages.end()) { + nout << "page " << page << " appears in dynamic index, but not in static index (or multiple times in static index)\n"; + okflag = false; + } else { + pages.erase(page); + } + node = (AdaptiveLruPageStaticList *)node->_next; + } + + if (counted_size != _total_size) { + nout << "count " << counted_size << " bytes in static index, but have " << _total_size << " on record\n"; + okflag = false; + } + + return okflag; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::Constructor +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +AdaptiveLruPage:: +AdaptiveLruPage(size_t lru_size) : + _lru(NULL), + _lru_size(lru_size), + _priority(0), + _first_frame_identifier(0), + _last_frame_identifier(0), + _current_frame_identifier(0), + _update_frame_identifier(0), + _current_frame_usage(0), + _last_frame_usage(0), + _total_usage(0), + _update_total_usage(0), + _average_frame_utilization(1.0f) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::Copy Constructor +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +AdaptiveLruPage:: +AdaptiveLruPage(const AdaptiveLruPage ©) : + _lru(NULL), + _lru_size(copy._lru_size), + _priority(0), + _first_frame_identifier(0), + _last_frame_identifier(0), + _current_frame_identifier(0), + _update_frame_identifier(0), + _current_frame_usage(0), + _last_frame_usage(0), + _total_usage(0), + _update_total_usage(0), + _average_frame_utilization(1.0f) +{ +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::Copy Assignment Operator +// Access: Protected +// Description: +//////////////////////////////////////////////////////////////////// +void AdaptiveLruPage:: +operator = (const AdaptiveLruPage ©) { + set_lru_size(copy.get_lru_size()); +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::Destructor +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +AdaptiveLruPage:: +~AdaptiveLruPage() { + if (_lru != NULL) { + dequeue_lru(); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::evict_lru +// Access: Published, Virtual +// Description: Evicts the page from the LRU. Called internally when +// the LRU determines that it is full. May also be +// called externally when necessary to explicitly evict +// the page. +// +// It is legal for this method to either evict the page +// as requested, do nothing (in which case the eviction +// will be requested again at the next epoch), or +// requeue itself on the tail of the queue (in which +// case the eviction will be requested again much +// later). +//////////////////////////////////////////////////////////////////// +void AdaptiveLruPage:: +evict_lru() { + dequeue_lru(); +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::output +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void AdaptiveLruPage:: +output(ostream &out) const { + out << "page " << this << ", " << _lru_size; +} + +//////////////////////////////////////////////////////////////////// +// Function: AdaptiveLruPage::write +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void AdaptiveLruPage:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) << *this << "\n"; +} + +#if 0 + +//////////////////////////////////////////////////////////////////// +// Function: test_adaptive_lru +// Access: +// Description: Unit test function for Lru. +//////////////////////////////////////////////////////////////////// +void +test_adaptive_lru() { + int maximum_memory = 3000000; + AdaptiveLru *lru = new AdaptiveLru("test", maximum_memory); + + AdaptiveLruPage *lru_page_0; + AdaptiveLruPage *lru_page_1; + AdaptiveLruPage *lru_page_2; + AdaptiveLruPage *lru_page_3; + AdaptiveLruPage *lru_page_4; + AdaptiveLruPage *lru_page_5; + + lru_page_0 = new AdaptiveLruPage(1000000); + cerr << "created lru_page_0: " << lru_page_0 << "\n"; + lru_page_0->enqueue_lru(lru); + + lru_page_1 = new AdaptiveLruPage(1000000); + cerr << "created lru_page_1: " << lru_page_1 << "\n"; + lru_page_1->enqueue_lru(lru); + + lru_page_2 = new AdaptiveLruPage(1000000); + cerr << "created lru_page_2: " << lru_page_2 << "\n"; + lru_page_2->enqueue_lru(lru); + + lru_page_3 = new AdaptiveLruPage(1000000); + cerr << "created lru_page_3: " << lru_page_3 << "\n"; + lru_page_3->enqueue_lru(lru); + + lru_page_4 = new AdaptiveLruPage(1000000); + cerr << "created lru_page_4: " << lru_page_4 << "\n"; + lru_page_4->enqueue_lru(lru); + + lru_page_5 = new AdaptiveLruPage(1000000); + cerr << "created lru_page_5: " << lru_page_5 << "\n"; + lru_page_5->enqueue_lru(lru); + + int total_frames = 300; + int index; + for (index = 0; index < total_frames; index++) { + cerr << "FRAME " << index << "\n"; + + lru->begin_epoch(); + + if (index <= 5) { + lru_page_0->mark_used_lru(lru); + } + + lru_page_1->mark_used_lru(lru); + lru_page_1->mark_used_lru(lru); + + if (index & 0x01) { + lru_page_2->mark_used_lru(lru); + } + + if ((index % 10) == 0) { + lru_page_3->mark_used_lru(lru); + } + + if (index >= 100) { + lru_page_4->mark_used_lru(lru); + } + + if (index >= 200) { + lru_page_5->mark_used_lru(lru); + } + + if (!lru->validate()) { + cerr << "Failed validation\n"; + break; + } + } + + delete lru; + delete lru_page_0; + delete lru_page_1; + delete lru_page_2; + delete lru_page_3; + delete lru_page_4; + delete lru_page_5; +} + +#endif // test_adaptive_lru diff --git a/panda/src/gobj/adaptiveLru.h b/panda/src/gobj/adaptiveLru.h new file mode 100644 index 0000000000..662245fd59 --- /dev/null +++ b/panda/src/gobj/adaptiveLru.h @@ -0,0 +1,207 @@ +// Filename: adaptiveLru.h +// Created by: drose (03Sep08) +// +//////////////////////////////////////////////////////////////////// +// +// 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." +// +//////////////////////////////////////////////////////////////////// + +#ifndef ADAPTIVELRU_H +#define ADAPTIVELRU_H + +#include "pandabase.h" +#include "linkedListNode.h" +#include "namable.h" +#include "pmutex.h" +#include "mutexHolder.h" + +class AdaptiveLruPage; + +// See the comment in the head of AdaptiveLruPage, below, for an +// explanation of these two silly little classes. +class EXPCL_PANDA_GOBJ AdaptiveLruPageDynamicList : public LinkedListNode { +public: + friend class AdaptiveLru; +}; + +class EXPCL_PANDA_GOBJ AdaptiveLruPageStaticList : public LinkedListNode { +public: + friend class AdaptiveLru; +}; + +//////////////////////////////////////////////////////////////////// +// Class : AdaptiveLru +// Description : A basic LRU-type algorithm, except that it is +// adaptive and attempts to avoid evicting pages that +// have been used more frequently (even if less +// recently) than other pages. +// +// The interface is designed to be identical to that for +// SimpleLru, so that it may be used as a drop-in +// replacement. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_GOBJ AdaptiveLru : public Namable { +PUBLISHED: + AdaptiveLru(const string &name, size_t max_size); + ~AdaptiveLru(); + + INLINE size_t get_total_size() const; + INLINE size_t get_max_size() const; + INLINE void set_max_size(size_t max_size); + size_t count_active_size() const; + + INLINE void consider_evict(); + INLINE void evict_to(size_t target_size); + void begin_epoch(); + + INLINE bool validate(); + + void output(ostream &out) const; + void write(ostream &out, int indent_level) const; + + // The following methods are specific to AdaptiveLru, and do not + // exist in the SimpleLru implementation. In most cases, the + // defaults will be sufficient, so you do not need to mess with + // them. + INLINE void set_weight(float weight); + INLINE float get_weight() const; + + INLINE void set_max_updates_per_frame(int max_updates_per_frame); + INLINE int get_max_updates_per_frame() const; + +private: + enum LruPagePriority { + LPP_Highest = 0, + LPP_High = 10, + LPP_New = 20, + LPP_Normal = 25, + LPP_Intermediate = 30, + LPP_Low = 40, + LPP_TotalPriorities = 50, + }; + + INLINE float calculate_exponential_moving_average(float value, float average) const; + + void do_partial_lru_update(int num_updates); + void update_page(AdaptiveLruPage *page); + + void do_add_page(AdaptiveLruPage *page); + void do_remove_page(AdaptiveLruPage *page); + void do_access_page(AdaptiveLruPage *page); + + void do_evict_to(size_t target_size, bool hard_evict); + bool do_validate(); + + Mutex _lock; + + size_t _total_size; + size_t _max_size; + + int _current_frame_identifier; + double _weight; + int _max_updates_per_frame; + + // This array of linked lists keeps all of the active pages, grouped + // by priority. We reshuffle pages among these lists as they are + // accessed and as they change priority in update_page(). + AdaptiveLruPageDynamicList _page_array[LPP_TotalPriorities]; + + // This linked list keeps all of the active pages, in arbitrary + // order. This list exists solely to allow us to incrementally + // update pages without having to iterate through the complex lists + // above and worry about losing our place. New pages are added to + // the tail. We also move pages from the head to the tail of this + // list in do_partial_lru_update() as we process each page with + // update_page(). Pages do not move within this list other that + // that. + AdaptiveLruPageStaticList _static_list; + + friend class AdaptiveLruPage; +}; + +//////////////////////////////////////////////////////////////////// +// Class : AdaptiveLruPage +// Description : One atomic piece that may be managed by a AdaptiveLru +// chain. To use this class, inherit from it and +// override evict_lru(). +// +// This class multiply inherits from two classes which +// in turn both inherit from LinkedListNode. This is +// just a sneaky C++ trick to allow this class to +// inherit from LinkedListNode twice, so that pages can +// be stored on two different linked lists +// simultaneously. The AdaptiveLru class depends on +// this; it maintains its pages in two different lists, +// one grouped by priority, and one in order by next +// partial update needs. +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDA_GOBJ AdaptiveLruPage : public AdaptiveLruPageDynamicList, public AdaptiveLruPageStaticList { +PUBLISHED: + AdaptiveLruPage(size_t lru_size); + AdaptiveLruPage(const AdaptiveLruPage ©); + void operator = (const AdaptiveLruPage ©); + + virtual ~AdaptiveLruPage(); + + INLINE AdaptiveLru *get_lru() const; + + void enqueue_lru(AdaptiveLru *lru); + INLINE void dequeue_lru(); + + INLINE void mark_used_lru() const; + INLINE void mark_used_lru(AdaptiveLru *lru); + + INLINE size_t get_lru_size() const; + INLINE void set_lru_size(size_t lru_size); + + virtual void evict_lru(); + + virtual void output(ostream &out) const; + virtual void write(ostream &out, int indent_level) const; + +private: + AdaptiveLru *_lru; + + size_t _lru_size; + int _priority; + + int _first_frame_identifier; // creation time + int _last_frame_identifier; // last time page used + int _current_frame_identifier; + int _update_frame_identifier; + + int _current_frame_usage; + int _last_frame_usage; + int _total_usage; + int _update_total_usage; + + float _average_frame_utilization; + + friend class AdaptiveLru; +}; + +inline ostream &operator << (ostream &out, const AdaptiveLru &lru) { + lru.output(out); + return out; +} + +inline ostream &operator << (ostream &out, const AdaptiveLruPage &page) { + page.output(out); + return out; +} + +#if 0 +BEGIN_PUBLISH +void test_adaptive_lru(); +END_PUBLISH +#endif + +#include "adaptiveLru.I" + +#endif diff --git a/panda/src/gobj/bufferContext.I b/panda/src/gobj/bufferContext.I index 1f8de08337..d931238e9e 100644 --- a/panda/src/gobj/bufferContext.I +++ b/panda/src/gobj/bufferContext.I @@ -38,6 +38,30 @@ get_modified() const { return _modified; } +//////////////////////////////////////////////////////////////////// +// Function: BufferContext::get_active +// Access: Public +// Description: Returns the active flag associated with this object. +// An object is considered "active" if it was rendered +// in the current frame. +//////////////////////////////////////////////////////////////////// +INLINE bool BufferContext:: +get_active() const { + return (_residency_state & BufferResidencyTracker::S_active) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: BufferContext::get_resident +// Access: Public +// Description: Returns the resident flag associated with this +// object. An object is considered "resident" if it +// appears to be resident in texture memory. +//////////////////////////////////////////////////////////////////// +INLINE bool BufferContext:: +get_resident() const { + return (_residency_state & BufferResidencyTracker::S_resident) != 0; +} + //////////////////////////////////////////////////////////////////// // Function: BufferContext::set_active // Access: Public diff --git a/panda/src/gobj/bufferContext.h b/panda/src/gobj/bufferContext.h index 96f9e7f044..3eff1d29d3 100644 --- a/panda/src/gobj/bufferContext.h +++ b/panda/src/gobj/bufferContext.h @@ -46,6 +46,8 @@ public: PUBLISHED: INLINE size_t get_data_size_bytes() const; INLINE UpdateSeq get_modified() const; + INLINE bool get_active() const; + INLINE bool get_resident() const; public: INLINE void set_active(bool flag); diff --git a/panda/src/gobj/bufferContextChain.cxx b/panda/src/gobj/bufferContextChain.cxx index bf74907a1e..3fbccd7a04 100755 --- a/panda/src/gobj/bufferContextChain.cxx +++ b/panda/src/gobj/bufferContextChain.cxx @@ -14,6 +14,7 @@ #include "bufferContextChain.h" #include "bufferContext.h" +#include "indent.h" //////////////////////////////////////////////////////////////////// // Function: BufferContextChain::get_first @@ -57,3 +58,20 @@ take_from(BufferContextChain &other) { take_list_from(&other); } + +//////////////////////////////////////////////////////////////////// +// Function: BufferContextChain::write +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void BufferContextChain:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) + << _count << " objects, consuming " << _total_size << " bytes:\n"; + + LinkedListNode *llnode = _next; + while (llnode != this) { + ((BufferContext *)llnode)->write(out, indent_level + 2); + llnode = ((BufferContext *)llnode)->_next; + } +} diff --git a/panda/src/gobj/bufferContextChain.h b/panda/src/gobj/bufferContextChain.h index 95174277f2..dab47947c3 100755 --- a/panda/src/gobj/bufferContextChain.h +++ b/panda/src/gobj/bufferContextChain.h @@ -43,6 +43,8 @@ public: void take_from(BufferContextChain &other); + void write(ostream &out, int indent_level) const; + private: INLINE void adjust_bytes(int delta); size_t _total_size; diff --git a/panda/src/gobj/bufferResidencyTracker.cxx b/panda/src/gobj/bufferResidencyTracker.cxx index c7e471c6be..1451ae29ec 100644 --- a/panda/src/gobj/bufferResidencyTracker.cxx +++ b/panda/src/gobj/bufferResidencyTracker.cxx @@ -15,6 +15,7 @@ #include "bufferResidencyTracker.h" #include "bufferContext.h" #include "clockObject.h" +#include "indent.h" PStatCollector BufferResidencyTracker::_gmem_collector("Graphics memory"); @@ -71,6 +72,34 @@ end_frame(Thread *current_thread) { _active_resident_collector.set_level(_chains[S_active_resident].get_total_size()); } +//////////////////////////////////////////////////////////////////// +// Function: BufferResidencyTracker::write +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void BufferResidencyTracker:: +write(ostream &out, int indent_level) const { + if (_chains[S_inactive_nonresident].get_count() != 0) { + indent(out, indent_level) << "Inactive nonresident:\n"; + _chains[S_inactive_nonresident].write(out, indent_level + 2); + } + + if (_chains[S_active_nonresident].get_count() != 0) { + indent(out, indent_level) << "Active nonresident:\n"; + _chains[S_active_nonresident].write(out, indent_level + 2); + } + + if (_chains[S_inactive_resident].get_count() != 0) { + indent(out, indent_level) << "Inactive resident:\n"; + _chains[S_inactive_resident].write(out, indent_level + 2); + } + + if (_chains[S_active_resident].get_count() != 0) { + indent(out, indent_level) << "Active resident:\n"; + _chains[S_active_resident].write(out, indent_level + 2); + } +} + //////////////////////////////////////////////////////////////////// // Function: BufferResidencyTracker::move_inactive // Access: Private diff --git a/panda/src/gobj/bufferResidencyTracker.h b/panda/src/gobj/bufferResidencyTracker.h index 9e5e912525..0d03e5fed2 100644 --- a/panda/src/gobj/bufferResidencyTracker.h +++ b/panda/src/gobj/bufferResidencyTracker.h @@ -45,6 +45,8 @@ public: INLINE BufferContextChain &get_inactive_resident(); INLINE BufferContextChain &get_active_resident(); + void write(ostream &out, int indent_level) const; + private: void move_inactive(BufferContextChain &inactive, BufferContextChain &active); diff --git a/panda/src/gobj/config_gobj.cxx b/panda/src/gobj/config_gobj.cxx index d1d3daf0cf..caf5208dcb 100644 --- a/panda/src/gobj/config_gobj.cxx +++ b/panda/src/gobj/config_gobj.cxx @@ -387,6 +387,18 @@ ConfigVariableInt graphics_memory_limit "Set this to -1 to have no limit other than the normal " "hardware-imposed limit.")); +ConfigVariableDouble adaptive_lru_weight +("adaptive-lru-weight", 0.2, + PRC_DESC("Specifies the weight factor used to compute the AdaptiveLru's " + "exponential moving average.")); + +ConfigVariableInt adaptive_lru_max_updates_per_frame +("adaptive-lru-max-updates-per-frame", 40, + PRC_DESC("The number of pages the AdaptiveLru class will update per " + "frame. Do not set this too high or it will degrade " + "performance.")); + + ConfigureFn(config_gobj) { BufferContext::init_type(); Geom::init_type(); diff --git a/panda/src/gobj/config_gobj.h b/panda/src/gobj/config_gobj.h index c6784265d9..ea1e3e196c 100644 --- a/panda/src/gobj/config_gobj.h +++ b/panda/src/gobj/config_gobj.h @@ -93,6 +93,8 @@ extern EXPCL_PANDA_GOBJ ConfigVariableString vertex_save_file_prefix; extern EXPCL_PANDA_GOBJ ConfigVariableInt vertex_data_small_size; extern EXPCL_PANDA_GOBJ ConfigVariableInt vertex_data_page_threads; extern EXPCL_PANDA_GOBJ ConfigVariableInt graphics_memory_limit; +extern EXPCL_PANDA_GOBJ ConfigVariableDouble adaptive_lru_weight; +extern EXPCL_PANDA_GOBJ ConfigVariableInt adaptive_lru_max_updates_per_frame; #endif diff --git a/panda/src/gobj/gobj_composite1.cxx b/panda/src/gobj/gobj_composite1.cxx index 0f4de25e18..44636350ec 100644 --- a/panda/src/gobj/gobj_composite1.cxx +++ b/panda/src/gobj/gobj_composite1.cxx @@ -1,3 +1,4 @@ +#include "adaptiveLru.cxx" #include "bufferContext.cxx" #include "bufferContextChain.cxx" #include "bufferResidencyTracker.cxx" diff --git a/panda/src/gobj/indexBufferContext.I b/panda/src/gobj/indexBufferContext.I index 0c3f199272..b6b86e6ffd 100644 --- a/panda/src/gobj/indexBufferContext.I +++ b/panda/src/gobj/indexBufferContext.I @@ -21,7 +21,7 @@ INLINE IndexBufferContext:: IndexBufferContext(PreparedGraphicsObjects *pgo, GeomPrimitive *data) : BufferContext(&pgo->_ibuffer_residency), - SimpleLruPage(0), + AdaptiveLruPage(0), _data(data) { } @@ -82,7 +82,7 @@ was_modified(const GeomPrimitivePipelineReader *reader) const { INLINE void IndexBufferContext:: update_data_size_bytes(size_t new_data_size_bytes) { BufferContext::update_data_size_bytes(new_data_size_bytes); - SimpleLruPage::set_lru_size(new_data_size_bytes); + AdaptiveLruPage::set_lru_size(new_data_size_bytes); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/gobj/indexBufferContext.cxx b/panda/src/gobj/indexBufferContext.cxx index 9639ee63e9..d013d18ece 100644 --- a/panda/src/gobj/indexBufferContext.cxx +++ b/panda/src/gobj/indexBufferContext.cxx @@ -15,3 +15,23 @@ #include "indexBufferContext.h" TypeHandle IndexBufferContext::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: IndexBufferContext::output +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void IndexBufferContext:: +output(ostream &out) const { + out << *get_data() << ", " << get_data_size_bytes(); +} + +//////////////////////////////////////////////////////////////////// +// Function: IndexBufferContext::write +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void IndexBufferContext:: +write(ostream &out, int indent_level) const { + SavedContext::write(out, indent_level); +} diff --git a/panda/src/gobj/indexBufferContext.h b/panda/src/gobj/indexBufferContext.h index e810a40bb2..ec650b4b53 100644 --- a/panda/src/gobj/indexBufferContext.h +++ b/panda/src/gobj/indexBufferContext.h @@ -20,7 +20,7 @@ #include "bufferContext.h" #include "geomPrimitive.h" #include "preparedGraphicsObjects.h" -#include "simpleLru.h" +#include "adaptiveLru.h" //////////////////////////////////////////////////////////////////// // Class : IndexBufferContext @@ -33,7 +33,7 @@ // allocate a vertex buffer for the array. OpenGL can // create a buffer object. //////////////////////////////////////////////////////////////////// -class EXPCL_PANDA_GOBJ IndexBufferContext : public BufferContext, public SimpleLruPage { +class EXPCL_PANDA_GOBJ IndexBufferContext : public BufferContext, public AdaptiveLruPage { public: INLINE IndexBufferContext(PreparedGraphicsObjects *pgo, GeomPrimitive *data); @@ -49,6 +49,9 @@ public: INLINE void mark_loaded(const GeomPrimitivePipelineReader *reader); INLINE void mark_unloaded(); + virtual void output(ostream &out) const; + virtual void write(ostream &out, int indent_level) const; + private: // This cannot be a PT(GeomPrimitive), because the data and // the GSG both own their IndexBufferContexts! That would create a diff --git a/panda/src/gobj/preparedGraphicsObjects.cxx b/panda/src/gobj/preparedGraphicsObjects.cxx index 63d1c027dc..18bb967127 100644 --- a/panda/src/gobj/preparedGraphicsObjects.cxx +++ b/panda/src/gobj/preparedGraphicsObjects.cxx @@ -100,6 +100,36 @@ PreparedGraphicsObjects:: _released_index_buffers.clear(); } +//////////////////////////////////////////////////////////////////// +// 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 diff --git a/panda/src/gobj/preparedGraphicsObjects.h b/panda/src/gobj/preparedGraphicsObjects.h index 4ec56db61f..0e70ffa785 100644 --- a/panda/src/gobj/preparedGraphicsObjects.h +++ b/panda/src/gobj/preparedGraphicsObjects.h @@ -27,6 +27,7 @@ #include "pset.h" #include "reMutex.h" #include "bufferResidencyTracker.h" +#include "adaptiveLru.h" class TextureContext; class GeomContext; @@ -64,6 +65,8 @@ PUBLISHED: INLINE void set_graphics_memory_limit(size_t limit); INLINE size_t get_graphics_memory_limit() const; + void show_graphics_memory_lru(ostream &out) const; + void show_residency_trackers(ostream &out) const; INLINE void release_all(); INLINE int get_num_queued() const; @@ -199,7 +202,7 @@ public: BufferResidencyTracker _vbuffer_residency; BufferResidencyTracker _ibuffer_residency; - SimpleLru _graphics_memory_lru; + AdaptiveLru _graphics_memory_lru; public: // This is only public as a temporary hack. Don't mess with it diff --git a/panda/src/gobj/savedContext.cxx b/panda/src/gobj/savedContext.cxx index d3cfbdec4d..674fa42bb9 100644 --- a/panda/src/gobj/savedContext.cxx +++ b/panda/src/gobj/savedContext.cxx @@ -13,5 +13,26 @@ //////////////////////////////////////////////////////////////////// #include "savedContext.h" +#include "indent.h" TypeHandle SavedContext::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: SavedContext::output +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void SavedContext:: +output(ostream &out) const { + out << "SavedContext " << this; +} + +//////////////////////////////////////////////////////////////////// +// Function: SavedContext::write +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void SavedContext:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) << *this << "\n"; +} diff --git a/panda/src/gobj/savedContext.h b/panda/src/gobj/savedContext.h index 76cff7f0b4..63a65dcdd8 100644 --- a/panda/src/gobj/savedContext.h +++ b/panda/src/gobj/savedContext.h @@ -24,14 +24,15 @@ // Description : This is the base class for all GSG-specific context // objects, such as TextureContext and GeomContext. It // exists mainly to provide some structural -// organization. At the moment, there are no methods -// common to all of these objects, but there might be -// one day. +// organization. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_GOBJ SavedContext : public TypedObject { public: INLINE SavedContext(); + virtual void output(ostream &out) const; + virtual void write(ostream &out, int indent_level) const; + PUBLISHED: static TypeHandle get_class_type() { return _type_handle; @@ -51,6 +52,11 @@ private: static TypeHandle _type_handle; }; +inline ostream &operator << (ostream &out, const SavedContext &context) { + context.output(out); + return out; +} + #include "savedContext.I" #endif diff --git a/panda/src/gobj/simpleLru.I b/panda/src/gobj/simpleLru.I index 2d71911bc6..6c53024a08 100644 --- a/panda/src/gobj/simpleLru.I +++ b/panda/src/gobj/simpleLru.I @@ -96,6 +96,19 @@ begin_epoch() { _active_marker->enqueue_lru(this); } +//////////////////////////////////////////////////////////////////// +// Function: SimpleLru::validate +// Access: Published +// Description: Checks that the LRU is internally self-consistent. +// Returns true if successful, false if there is some +// problem. +//////////////////////////////////////////////////////////////////// +INLINE bool SimpleLru:: +validate() { + MutexHolder holder(_global_lock); + return do_validate(); +} + //////////////////////////////////////////////////////////////////// // Function: SimpleLruPage::Constructor // Access: Protected @@ -131,7 +144,7 @@ operator = (const SimpleLruPage ©) { } //////////////////////////////////////////////////////////////////// -// Function: SimpleLruPage::enqueue_lru +// Function: SimpleLruPage::get_lru // Access: Published // Description: Returns the LRU that manages this page, or NULL if it // is not currently managed by any LRU. diff --git a/panda/src/gobj/simpleLru.cxx b/panda/src/gobj/simpleLru.cxx index df2794f794..a7ca35ecc5 100644 --- a/panda/src/gobj/simpleLru.cxx +++ b/panda/src/gobj/simpleLru.cxx @@ -13,7 +13,7 @@ //////////////////////////////////////////////////////////////////// #include "simpleLru.h" -#include "clockObject.h" +#include "indent.h" // We define this as a reference to an allocated object, instead of as // a concrete object, so that it won't get destructed when the program @@ -60,8 +60,11 @@ SimpleLru:: //////////////////////////////////////////////////////////////////// // Function: SimpleLruPage::enqueue_lru // Access: Published -// Description: Adds the page to the tail of the SimpleLru. When it -// reaches the head, it will be the next to be evicted. +// Description: Adds the page to the LRU for the first time, or marks +// it recently-accessed if it has already been added. +// +// If lru is NULL, it means to remove this page from its +// LRU. //////////////////////////////////////////////////////////////////// void SimpleLruPage:: enqueue_lru(SimpleLru *lru) { @@ -113,6 +116,48 @@ count_active_size() const { return total; } +//////////////////////////////////////////////////////////////////// +// Function: SimpleLru::output +// Access: Published +// Description: +//////////////////////////////////////////////////////////////////// +void SimpleLru:: +output(ostream &out) const { + MutexHolder holder(_global_lock); + out << "SimpleLru " << get_name() + << ", " << _total_size << " of " << _max_size; +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleLru::write +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void SimpleLru:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) << *this << ":\n"; + + // We write out the list backwards. Things we write out first are + // the freshest in the LRU. Things at the end of the list will be + // the next to be evicted. + + MutexHolder holder(_global_lock); + LinkedListNode *node = _prev; + while (node != _active_marker && node != this) { + SimpleLruPage *page = (SimpleLruPage *)node; + indent(out, indent_level + 2) << *page << " (active)\n"; + node = page->_prev; + } + if (node == _active_marker) { + node = _active_marker->_prev; + while (node != this) { + SimpleLruPage *page = (SimpleLruPage *)node; + indent(out, indent_level + 2) << *page << "\n"; + node = page->_prev; + } + } +} + //////////////////////////////////////////////////////////////////// // Function: SimpleLru::do_evict_to // Access: Private @@ -157,13 +202,13 @@ do_evict_to(size_t target_size, bool hard_evict) { } //////////////////////////////////////////////////////////////////// -// Function: SimpleLru::do_validate_size +// Function: SimpleLru::do_validate // Access: Private -// Description: Checks that _total_size is consistent. Assume the -// lock is already held. +// Description: Checks that the LRU is internally consistent. Assume +// the lock is already held. //////////////////////////////////////////////////////////////////// bool SimpleLru:: -do_validate_size() { +do_validate() { size_t total = 0; LinkedListNode *node = _next; @@ -206,3 +251,23 @@ void SimpleLruPage:: evict_lru() { dequeue_lru(); } + +//////////////////////////////////////////////////////////////////// +// Function: SimpleLruPage::output +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void SimpleLruPage:: +output(ostream &out) const { + out << "page " << this << ", " << _lru_size; +} + +//////////////////////////////////////////////////////////////////// +// Function: SimpleLruPage::write +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void SimpleLruPage:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) << *this << "\n"; +} diff --git a/panda/src/gobj/simpleLru.h b/panda/src/gobj/simpleLru.h index 1b7ee0a4cb..db3a749672 100644 --- a/panda/src/gobj/simpleLru.h +++ b/panda/src/gobj/simpleLru.h @@ -26,6 +26,7 @@ class SimpleLruPage; //////////////////////////////////////////////////////////////////// // Class : SimpleLru // Description : An implementation of a very simple LRU algorithm. +// Also see AdaptiveLru. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA_GOBJ SimpleLru : public LinkedListNode, public Namable { PUBLISHED: @@ -41,12 +42,17 @@ PUBLISHED: INLINE void evict_to(size_t target_size); INLINE void begin_epoch(); + INLINE bool validate(); + + void output(ostream &out) const; + void write(ostream &out, int indent_level) const; + public: static Mutex &_global_lock; private: void do_evict_to(size_t target_size, bool hard_evict); - bool do_validate_size(); + bool do_validate(); size_t _total_size; size_t _max_size; @@ -82,6 +88,9 @@ PUBLISHED: virtual void evict_lru(); + virtual void output(ostream &out) const; + virtual void write(ostream &out, int indent_level) const; + private: SimpleLru *_lru; @@ -90,6 +99,16 @@ private: friend class SimpleLru; }; +inline ostream &operator << (ostream &out, const SimpleLru &lru) { + lru.output(out); + return out; +} + +inline ostream &operator << (ostream &out, const SimpleLruPage &page) { + page.output(out); + return out; +} + #include "simpleLru.I" #endif diff --git a/panda/src/gobj/textureContext.I b/panda/src/gobj/textureContext.I index f37eac86f5..af74abface 100644 --- a/panda/src/gobj/textureContext.I +++ b/panda/src/gobj/textureContext.I @@ -21,7 +21,7 @@ INLINE TextureContext:: TextureContext(PreparedGraphicsObjects *pgo, Texture *tex) : BufferContext(&pgo->_texture_residency), - SimpleLruPage(0), + AdaptiveLruPage(0), _texture(tex) { } @@ -94,7 +94,7 @@ was_simple_image_modified() const { INLINE void TextureContext:: update_data_size_bytes(size_t new_data_size_bytes) { BufferContext::update_data_size_bytes(new_data_size_bytes); - SimpleLruPage::set_lru_size(new_data_size_bytes); + AdaptiveLruPage::set_lru_size(new_data_size_bytes); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/gobj/textureContext.cxx b/panda/src/gobj/textureContext.cxx index 66f668fb92..c676a29ee3 100644 --- a/panda/src/gobj/textureContext.cxx +++ b/panda/src/gobj/textureContext.cxx @@ -15,3 +15,23 @@ #include "textureContext.h" TypeHandle TextureContext::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: TextureContext::output +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void TextureContext:: +output(ostream &out) const { + out << *get_texture() << ", " << get_data_size_bytes(); +} + +//////////////////////////////////////////////////////////////////// +// Function: TextureContext::write +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void TextureContext:: +write(ostream &out, int indent_level) const { + SavedContext::write(out, indent_level); +} diff --git a/panda/src/gobj/textureContext.h b/panda/src/gobj/textureContext.h index 8343249603..6fe546ee03 100644 --- a/panda/src/gobj/textureContext.h +++ b/panda/src/gobj/textureContext.h @@ -20,7 +20,7 @@ #include "bufferContext.h" #include "texture.h" #include "preparedGraphicsObjects.h" -#include "simpleLru.h" +#include "adaptiveLru.h" //////////////////////////////////////////////////////////////////// // Class : TextureContext @@ -35,7 +35,7 @@ // texture and store it here. The texture stores all of // these handles internally. //////////////////////////////////////////////////////////////////// -class EXPCL_PANDA_GOBJ TextureContext : public BufferContext, public SimpleLruPage { +class EXPCL_PANDA_GOBJ TextureContext : public BufferContext, public AdaptiveLruPage { public: INLINE TextureContext(PreparedGraphicsObjects *pgo, Texture *tex); @@ -53,6 +53,9 @@ public: INLINE void mark_simple_loaded(); INLINE void mark_unloaded(); + virtual void output(ostream &out) const; + virtual void write(ostream &out, int indent_level) const; + private: // This cannot be a PT(Texture), because the texture and the GSG // both own their TextureContexts! That would create a circular diff --git a/panda/src/gobj/vertexBufferContext.I b/panda/src/gobj/vertexBufferContext.I index 91e5094a32..5e86268205 100644 --- a/panda/src/gobj/vertexBufferContext.I +++ b/panda/src/gobj/vertexBufferContext.I @@ -21,7 +21,7 @@ INLINE VertexBufferContext:: VertexBufferContext(PreparedGraphicsObjects *pgo, GeomVertexArrayData *data) : BufferContext(&pgo->_vbuffer_residency), - SimpleLruPage(0), + AdaptiveLruPage(0), _data(data) { } @@ -82,7 +82,7 @@ was_modified(const GeomVertexArrayDataHandle *reader) const { INLINE void VertexBufferContext:: update_data_size_bytes(size_t new_data_size_bytes) { BufferContext::update_data_size_bytes(new_data_size_bytes); - SimpleLruPage::set_lru_size(new_data_size_bytes); + AdaptiveLruPage::set_lru_size(new_data_size_bytes); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/gobj/vertexBufferContext.cxx b/panda/src/gobj/vertexBufferContext.cxx index b6a37aa9d0..be6e48b980 100644 --- a/panda/src/gobj/vertexBufferContext.cxx +++ b/panda/src/gobj/vertexBufferContext.cxx @@ -15,3 +15,23 @@ #include "vertexBufferContext.h" TypeHandle VertexBufferContext::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: VertexBufferContext::output +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void VertexBufferContext:: +output(ostream &out) const { + out << *get_data() << ", " << get_data_size_bytes(); +} + +//////////////////////////////////////////////////////////////////// +// Function: VertexBufferContext::write +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void VertexBufferContext:: +write(ostream &out, int indent_level) const { + SavedContext::write(out, indent_level); +} diff --git a/panda/src/gobj/vertexBufferContext.h b/panda/src/gobj/vertexBufferContext.h index b2550fbbbe..3ff6732432 100644 --- a/panda/src/gobj/vertexBufferContext.h +++ b/panda/src/gobj/vertexBufferContext.h @@ -20,7 +20,7 @@ #include "bufferContext.h" #include "geomVertexArrayData.h" #include "preparedGraphicsObjects.h" -#include "simpleLru.h" +#include "adaptiveLru.h" //////////////////////////////////////////////////////////////////// // Class : VertexBufferContext @@ -33,7 +33,7 @@ // allocate a vertex buffer for the array. OpenGL can // create a buffer object. //////////////////////////////////////////////////////////////////// -class EXPCL_PANDA_GOBJ VertexBufferContext : public BufferContext, public SimpleLruPage { +class EXPCL_PANDA_GOBJ VertexBufferContext : public BufferContext, public AdaptiveLruPage { public: INLINE VertexBufferContext(PreparedGraphicsObjects *pgo, GeomVertexArrayData *data); @@ -50,6 +50,9 @@ public: INLINE void mark_loaded(const GeomVertexArrayDataHandle *reader); INLINE void mark_unloaded(); + virtual void output(ostream &out) const; + virtual void write(ostream &out, int indent_level) const; + private: // This cannot be a PT(GeomVertexArrayData), because the data and // the GSG both own their VertexBufferContexts! That would create a diff --git a/panda/src/gobj/vertexDataPage.cxx b/panda/src/gobj/vertexDataPage.cxx index daba94c768..b10e43ac07 100644 --- a/panda/src/gobj/vertexDataPage.cxx +++ b/panda/src/gobj/vertexDataPage.cxx @@ -217,6 +217,26 @@ flush_threads() { } } +//////////////////////////////////////////////////////////////////// +// Function: VertexDataPage::output +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void VertexDataPage:: +output(ostream &out) const { + SimpleAllocator::output(out); +} + +//////////////////////////////////////////////////////////////////// +// Function: VertexDataPage::write +// Access: Published, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void VertexDataPage:: +write(ostream &out, int indent_level) const { + SimpleAllocator::write(out); +} + //////////////////////////////////////////////////////////////////// // Function: VertexDataPage::make_block // Access: Protected, Virtual diff --git a/panda/src/gobj/vertexDataPage.h b/panda/src/gobj/vertexDataPage.h index 157805da8c..71c7670ce1 100644 --- a/panda/src/gobj/vertexDataPage.h +++ b/panda/src/gobj/vertexDataPage.h @@ -76,6 +76,9 @@ PUBLISHED: static void stop_threads(); static void flush_threads(); + virtual void output(ostream &out) const; + virtual void write(ostream &out, int indent_level) const; + public: INLINE unsigned char *get_page_data(bool force); INLINE bool operator < (const VertexDataPage &other) const; @@ -229,6 +232,11 @@ private: friend class VertexDataBook; }; +inline ostream &operator << (ostream &out, const VertexDataPage &page) { + page.output(out); + return out; +} + #include "vertexDataPage.I" #endif