Bladeren bron

moved here from dxgsg9

aignacio_sf 20 jaren geleden
bovenliggende
commit
1d798c9df2
2 gewijzigde bestanden met toevoegingen van 1615 en 0 verwijderingen
  1. 1344 0
      panda/src/display/lru.cxx
  2. 271 0
      panda/src/display/lru.h

+ 1344 - 0
panda/src/display/lru.cxx

@@ -0,0 +1,1344 @@
+// Filename: lru.cxx
+// Created by: aignacio (12Dec05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2006, Disney Enterprises, Inc.  All rights
+// reserved.
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+//#include "stdafx.h"
+
+#define LRU_UNIT_TEST 0
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+
+#include "lru.h"
+
+
+static const int HIGH_PRIORITY_SCALE = 4;
+static const int LOW_PRIORITY_RANGE = 25;
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+Lru::Lru (int maximum_memory, int maximum_pages, int maximum_page_types)
+{
+  if(this) {
+    int  index;
+
+    memset(&this->_m, 0, sizeof (LruVariables));
+
+    this->_m.maximum_memory = maximum_memory;
+    this->_m.maximum_pages  = maximum_pages;
+    this->_m.maximum_page_types = maximum_page_types;
+    this->_m.available_memory = maximum_memory;
+    this->_m.current_frame_identifier = 1;
+    this->_m.weight = 0.20f;
+
+    this->set_maximum_frame_bandwidth_utilization(2000000.0f);
+
+    for(index = 0; index < MAXIMUM_LRU_PAGE_TYPES; index++) {
+      this->_m.page_in_function_array[index] = default_page_in_function;
+      this->_m.page_out_function_array[index] = default_page_out_function;
+    }
+
+    if(maximum_pages > 0) {
+      this -> _m.lru_page_pool = new LruPage * [maximum_pages];
+      this -> _m.lru_page_free_pool = new LruPage * [maximum_pages];
+      for(index = 0; index < maximum_pages; index++) {
+        LruPage  * lru_page;
+
+        lru_page = new LruPage ( );
+        if(lru_page) {
+          lru_page->_m.pre_allocated = true;
+          this->_m.lru_page_pool[index] = lru_page;
+        }
+        else {
+// ERROR
+        }
+      }
+    }
+
+    if(maximum_page_types > 0) {
+      this -> _m.page_type_statistics_array =
+        new PageTypeStatistics [maximum_page_types];
+    }
+
+#if ENABLE_MUTEX
+    this -> _m.mutex = new Mutex ( );
+#endif
+
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::Destructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+Lru::~Lru ( )
+{
+  int        index;
+  LruPage  * lru_page;
+
+  // free pre-allocated LruPages
+  if(this->_m.maximum_pages > 0) {
+    if(this->_m.lru_page_free_pool) {
+      for(index = 0; index < this->_m.maximum_pages; index++) {
+        lru_page = this->_m.lru_page_pool[index];
+        if(lru_page->_m.in_lru) {
+          this->remove_page(lru_page);
+        }
+
+        delete lru_page;
+      }
+
+      delete this -> _m.lru_page_free_pool;
+    }
+    if(this->_m.lru_page_pool) {
+      delete this -> _m.lru_page_pool;
+    }
+  }
+
+  // free dynamically allocated LruPages
+  for(index = 0; index < LPP_TotalPriorities; index++) {
+    LruPage  * next_lru_page;
+
+    lru_page = this->_m.lru_page_array[index];
+    while(lru_page) {
+      next_lru_page = lru_page->_m.next;
+
+      delete  lru_page;
+
+      lru_page = next_lru_page;
+    }
+  }
+
+  if(this->_m.page_type_statistics_array) {
+    delete this -> _m.page_type_statistics_array;
+  }
+
+#if ENABLE_MUTEX
+  if(this->_m.mutex) {
+    delete this -> _m.mutex;
+  }
+#endif
+
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LruPage::Constructor
+//       Access: Protected
+//  Description: Internal function only.
+//               Call  Lru::allocate_page instead.
+////////////////////////////////////////////////////////////////////
+LruPage::LruPage ( )
+{
+  if(this) {
+    memset(&this->_m, 0, sizeof (LruPageVariables));
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LruPage::Destructor
+//       Access: Protected
+//  Description: Internal function only.
+//               Call  Lru::free_page instead.
+////////////////////////////////////////////////////////////////////
+LruPage::~LruPage ( )
+{
+
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: LruPage::change_priority
+//       Access: Protected
+//  Description:
+////////////////////////////////////////////////////////////////////
+void LruPage::change_priority (int delta)
+{
+  this->_m.priority_change += delta;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::register_lru_page_type
+//       Access: Public
+//  Description: Registers a specific type of page and its
+//               required page in and out functions.
+////////////////////////////////////////////////////////////////////
+bool Lru::register_lru_page_type (int index,
+  LruPageTypeFunction page_in_function,
+  LruPageTypeFunction page_out_function)
+{
+  bool  state;
+
+  state = false;
+  if(index >= 0 && index < MAXIMUM_LRU_PAGE_TYPES) {
+    this->_m.page_in_function_array[index] = page_in_function;
+    this->_m.page_out_function_array[index] = page_out_function;
+    state = true;
+  }
+
+  return state;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::allocate_page
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+LruPage *Lru::allocate_page (int size)
+{
+  LruPage  * lru_page;
+
+  lru_page = 0;
+  if(size <= this->_m.maximum_memory) {
+    if(this->_m.maximum_pages) {
+      if(this->_m.total_lru_pages_in_free_pool > 0) {
+        lru_page =
+          this->_m.lru_page_free_pool [this->_m.total_lru_pages_in_free_pool - 1];
+        this->_m.total_lru_pages_in_free_pool--;
+
+        memset (&lru_page -> _m, 0, sizeof (LruPage::LruPageVariables));
+        lru_page->_m.pre_allocated = true;
+      }
+      else {
+        if(this->_m.total_lru_pages_in_pool < this->_m.maximum_pages) {
+          lru_page = this->_m.lru_page_pool[this->_m.total_lru_pages_in_pool];
+          this->_m.total_lru_pages_in_pool++;
+        }
+        else {
+          // out of pre-allocated LruPages so dynamically allocate a page
+          lru_page = new LruPage ( );
+        }
+      }
+    }
+    else {
+      lru_page = new LruPage;
+    }
+    if(lru_page) {
+      lru_page->_m.lru = this;
+      lru_page->_m.size = size;
+      lru_page->_m.first_frame_identifier = this->_m.current_frame_identifier;
+      lru_page->_m.last_frame_identifier = this->_m.current_frame_identifier;
+
+      lru_page->_m.allocated = true;
+      lru_page->_m.identifier = this->_m.identifier;
+
+      lru_page->_m.average_frame_utilization = 1.0f;
+
+      this->_m.total_pages++;
+      this->_m.identifier++;
+    }
+    else {
+
+// ERROR: could not allocate LruPage
+
+    }
+  }
+  else {
+
+// ERROR: requested page size is larger than maximum memory size
+
+  }
+
+  return lru_page;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::update_start_update_lru_page
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void Lru::update_start_update_lru_page (LruPage *lru_page)
+{
+  if(lru_page) {
+    if(this->_m.start_update_lru_page == lru_page) {
+      if(lru_page->_m.next) {
+        this->_m.start_update_lru_page = lru_page->_m.next;
+      }
+      else {
+        if((this->_m.start_priority_index + 1) >= LPP_TotalPriorities) {
+          this->_m.start_priority_index = 0;
+        }
+        else {
+          this->_m.start_priority_index = this->_m.start_priority_index + 1;
+        }
+
+        this->_m.start_update_lru_page = 0;
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::free_page
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void Lru::free_page (LruPage *lru_page)
+{
+  if(this->_m.total_pages > 0) {
+    if(lru_page) {
+      LruMutexHolder(this->_m.mutex);
+
+      this->update_start_update_lru_page(lru_page);
+
+      if(lru_page->_m.in_cache) {
+        this->_m.available_memory += lru_page->_m.size;
+      }
+
+      if(lru_page->_m.pre_allocated) {
+        if(this->_m.maximum_pages) {
+          lru_page->_m.allocated = false;
+          this->_m.lru_page_free_pool [this->_m.total_lru_pages_in_free_pool] =
+            lru_page;
+          this->_m.total_lru_pages_in_free_pool++;
+        }
+        else {
+// ERROR: this case should not happen
+        }
+      }
+      else {
+        delete lru_page;
+      }
+
+      this->_m.total_pages--;
+    }
+  }
+  else {
+
+// ERROR: tried to free a page when 0 pages allocated
+
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::add_page
+//       Access: Public
+//  Description: Adds a page to the LRU based on the given priority.
+////////////////////////////////////////////////////////////////////
+void Lru::add_page (LruPagePriority priority, LruPage *lru_page)
+{
+  if(lru_page) {
+    LruMutexHolder(this->_m.mutex);
+
+    LruPage * first_lru_page;
+
+    lru_page->_m.priority = priority;
+
+    first_lru_page = this->_m.lru_page_array[lru_page->_m.priority];
+    if(first_lru_page) {
+      first_lru_page->_m.previous = lru_page;
+      lru_page->_m.next = first_lru_page;
+    }
+
+    this->_m.lru_page_array[lru_page->_m.priority] = lru_page;
+
+    lru_page->_m.in_lru = true;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::add_cached_page
+//       Access: Public
+//  Description: Adds a page that is already paged in to the LRU
+//               based on the given priority.
+////////////////////////////////////////////////////////////////////
+void Lru::add_cached_page (LruPagePriority priority, LruPage *lru_page)
+{
+  if(lru_page) {
+    LruMutexHolder(this->_m.mutex);
+
+    lru_page->_m.in_cache = true;
+
+    if(lru_page->_m.size > this->_m.available_memory) {
+      int  memory_required;
+
+      memory_required = lru_page->_m.size - this->_m.available_memory;
+
+      // unload page(s)
+      this->page_out_lru(memory_required);
+    }
+
+    this->_m.available_memory -= lru_page->_m.size;
+
+    this->add_page(priority, lru_page);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::remove_page
+//       Access: Public
+//  Description: Removes a page from the LRU.
+////////////////////////////////////////////////////////////////////
+void Lru::remove_page (LruPage *lru_page)
+{
+  if(this) {
+    if(this->_m.total_pages > 0) {
+      if(lru_page) {
+        LruMutexHolder(this->_m.mutex);
+
+        this->update_start_update_lru_page(lru_page);
+
+        if(lru_page->_m.previous) {
+          lru_page->_m.previous->_m.next = lru_page->_m.next;
+          if(lru_page->_m.next) {
+            lru_page->_m.next->_m.previous = lru_page->_m.previous;
+          }
+        }
+        else {
+          this->_m.lru_page_array[lru_page->_m.priority] =
+            lru_page->_m.next;
+          if(lru_page->_m.next) {
+            lru_page->_m.next->_m.previous = 0;
+          }
+        }
+
+        lru_page->_m.next = 0;
+        lru_page->_m.previous = 0;
+
+        lru_page->_m.in_lru = false;
+      }
+    }
+    else {
+
+// ERROR: tried to remove a page when 0 pages are allocated
+
+    }
+  }
+  else {
+
+// ERROR: Lru == 0, this should not happen
+
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::lock_page
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void Lru::lock_page (LruPage *lru_page)
+{
+  lru_page->_m.lock = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::unlock_page
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void Lru::unlock_page (LruPage *lru_page)
+{
+  lru_page->_m.lock = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::access_page
+//       Access: Public
+//  Description: This must always be called before accessing or
+//               using a page's memory since it pages in the page
+//               if it is currently paged out.
+////////////////////////////////////////////////////////////////////
+void Lru::access_page (LruPage *lru_page)
+{
+  if(lru_page) {
+    if(lru_page->_m.current_frame_identifier
+       == this->_m.current_frame_identifier) {
+      lru_page->_m.current_frame_usage++;
+      this->_m.total_page_all_access_size += lru_page->_m.size;
+    }
+    else {
+      // first update this frame
+      lru_page->_m.last_frame_identifier = lru_page->_m.current_frame_identifier;
+      lru_page->_m.current_frame_identifier = this->_m.current_frame_identifier;
+      lru_page->_m.last_frame_usage = lru_page->_m.current_frame_usage;
+      lru_page->_m.current_frame_usage = 1;
+      lru_page->_m.total_frame_page_faults = 0;
+
+      this->_m.total_page_access_size += lru_page->_m.size;
+    }
+
+    // check if the page is out
+    if(lru_page->_m.in_cache == false) {
+      bool  state;
+
+      state = true;
+
+      LruMutexHolder(this->_m.mutex);
+
+      // check memory usage
+      if(lru_page->_m.size > this->_m.available_memory) {
+        int  memory_required;
+
+        memory_required = lru_page->_m.size - this->_m.available_memory;
+
+        // unload page(s)
+        state = this->page_out_lru(memory_required);
+      }
+
+      // load the page in
+      if(state) {
+        // PAGE IN CALLBACK
+        if(this->_m.page_in_function_array[lru_page->_m.type](lru_page)) {
+          this->_m.available_memory -= lru_page->_m.size;
+          lru_page->_m.in_cache = true;
+
+          // CHANGE THE PAGE PRIORITY FROM LPP_PageOut TO LPP_New
+          this->remove_page(lru_page);
+          this->add_page(LPP_New, lru_page);
+
+          this->_m.total_lifetime_page_ins++;
+        }
+      }
+
+      lru_page->_m.total_frame_page_faults++;
+      lru_page->_m.total_page_faults++;
+    }
+
+    lru_page->_m.total_usage++;
+    lru_page->_m.update_total_usage++;
+
+    this->_m.total_page_access++;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::set_maximum_frame_bandwidth_utilization
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void Lru::set_maximum_frame_bandwidth_utilization
+  (float maximum_frame_bandwidth_utilization)
+{
+  this->_m.maximum_frame_bandwidth_utilization =
+    maximum_frame_bandwidth_utilization;
+
+  this->_m.frame_bandwidth_factor = (float) LPP_TotalPriorities
+    / this->_m.maximum_frame_bandwidth_utilization;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::begin_frame
+//       Access: Public
+//  Description: This must be called before each frame.
+////////////////////////////////////////////////////////////////////
+void Lru::begin_frame ( )
+{
+  this->_m.current_frame_identifier++;
+
+  this->_m.total_page_ins_last_frame = this->_m.total_page_ins;
+  this->_m.total_page_outs = this->_m.total_page_outs;
+
+  this->_m.total_page_ins = 0;
+  this->_m.total_page_outs = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::update_page_priorities
+//       Access: Public
+//  Description: This updates the priority of a page that has a
+//               change in priority.
+////////////////////////////////////////////////////////////////////
+void Lru::update_page_priorities (void)
+{
+  int index;
+  LruPage *lru_page;
+
+  for(index = 0; index < this->_m.total_lru_page_priority_changes; index++) {
+    int priority;
+
+    lru_page = this->_m.lru_page_priority_change_array[index];
+
+    this->remove_page(lru_page);
+
+    priority = (( int ) lru_page->_m.priority + lru_page->_m.priority_change);
+    if(priority < 0) {
+      priority = 0;
+    }
+    if(priority >= LPP_TotalPriorities) {
+      priority = LPP_TotalPriorities - 1;
+    }
+
+    this->add_page((LruPagePriority) priority, lru_page);
+    lru_page->_m.priority_change = 0;
+  }
+  this->_m.total_lru_page_priority_changes = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::update_lru_page
+//       Access: Public
+//  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.
+//               If there was a change in priority, then adds it to
+//               the array of lru pages with changed priorities
+//               which will be updated later.
+////////////////////////////////////////////////////////////////////
+void Lru::update_lru_page (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.lock == false && lru_page->_m.in_cache) {
+    int delta_priority;
+    int lifetime_frames;
+
+    delta_priority = 0;
+
+    lifetime_frames = this->_m.current_frame_identifier -
+      lru_page->_m.first_frame_identifier;
+    if(lifetime_frames >= 1) {
+      if(lru_page->_m.update_frame_identifier) {
+        int target_priority;
+        int integer_update_frames;
+        float update_frames;
+        float one_over_update_frames;
+        float update_average_frame_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);
+
+          target_priority = lru_page->_m.priority;
+          if(lru_page->_m.average_frame_utilization >= 1.0f) {
+            int integer_average_frame_utilization;
+
+            integer_average_frame_utilization =
+              (int) ((lru_page->_m.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)
+               (lru_page->_m.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;
+          }
+
+          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(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++;
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::update_lru_page_old
+//       Access: Public
+//  Description: This updates the page's average utilization and
+//               adds it to the array of pages with changed
+//               priorities if there was a change in priority.
+//               Old method.
+////////////////////////////////////////////////////////////////////
+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.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++;
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::update_entire_lru
+//       Access: Public
+//  Description: This updates all the pages in the Lru.
+//               Lru::partial_lru_update should be called instead
+//               due to performance reasons.
+////////////////////////////////////////////////////////////////////
+void Lru::update_entire_lru ( )
+{
+  if(this->_m.total_pages > 0) {
+    int index;
+    LruPage *lru_page;
+
+    LruMutexHolder(this->_m.mutex);
+
+    for(index = 0; index < LPP_TotalPriorities; index++) {
+
+      LruPage  * next_lru_page;
+
+      lru_page = this->_m.lru_page_array[index];
+      while(lru_page) {
+        next_lru_page = lru_page->_m.next;
+
+        this->update_lru_page(lru_page);
+
+        lru_page = next_lru_page;
+      }
+    }
+
+    this->update_page_priorities( );
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::partial_lru_update
+//       Access: Public
+//  Description: This only updates a number of pages up to the
+//               specified maximum_updates.
+////////////////////////////////////////////////////////////////////
+void Lru::partial_lru_update (int maximum_updates)
+{
+  int total_page_updates;
+
+  if (maximum_updates <= 0) {
+    // enforce a minimum number of updates
+    maximum_updates = 1;
+  }
+
+  total_page_updates = 0;
+  if(this->_m.total_pages > 0) {
+    int index;
+    int start_priority;
+    LruPage *lru_page;
+
+    LruMutexHolder(this->_m.mutex);
+
+    start_priority = this->_m.start_priority_index;
+
+    {
+      for(index = start_priority;  index < LPP_TotalPriorities; index++) {
+
+        LruPage *next_lru_page;
+
+        if(index == start_priority) {
+          if(this->_m.start_update_lru_page) {
+            lru_page = this->_m.start_update_lru_page;
+          }
+          else {
+            lru_page = this->_m.lru_page_array[index];
+          }
+        }
+        else {
+          lru_page = this->_m.lru_page_array[index];
+        }
+        while(lru_page) {
+          next_lru_page = lru_page->_m.next;
+
+          this->update_lru_page(lru_page);
+
+          total_page_updates++;
+          if(total_page_updates >= maximum_updates) {
+            if(next_lru_page) {
+              this->_m.start_priority_index = index;
+              this->_m.start_update_lru_page = next_lru_page;
+            }
+            else {
+              if((index + 1) >= LPP_TotalPriorities) {
+                this->_m.start_priority_index = 0;
+              }
+              else {
+                this->_m.start_priority_index = index + 1;
+              }
+
+              this->_m.start_update_lru_page = 0;
+            }
+
+            break;
+          }
+
+          lru_page = next_lru_page;
+        }
+
+        if(total_page_updates >= maximum_updates) {
+          break;
+        }
+      }
+    }
+
+    if(total_page_updates < maximum_updates) {
+      for(index = 0;  index <= start_priority;  index++) {
+        LruPage *next_lru_page;
+
+        lru_page = this->_m.lru_page_array[index];
+        while(lru_page) {
+          next_lru_page = lru_page->_m.next;
+
+          this->update_lru_page(lru_page);
+
+          total_page_updates++;
+          if(total_page_updates >= maximum_updates) {
+            if(next_lru_page) {
+              this->_m.start_priority_index = index;
+              this->_m.start_update_lru_page = next_lru_page;
+            }
+            else {
+              if((index + 1) >= LPP_TotalPriorities) {
+                this->_m.start_priority_index = 0;
+              }
+              else {
+                this->_m.start_priority_index = index + 1;
+              }
+
+              this->_m.start_update_lru_page = 0;
+            }
+
+            break;
+          }
+
+          lru_page = next_lru_page;
+        }
+
+        if(total_page_updates >= maximum_updates) {
+          break;
+        }
+      }
+    }
+
+    if(total_page_updates < maximum_updates) {
+      this->_m.start_priority_index  = 0;
+      this->_m.start_update_lru_page = 0;
+    }
+
+    this->update_page_priorities( );
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::unlock_all_pages
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void Lru::unlock_all_pages (void)
+{
+  if(this->_m.total_pages > 0) {
+    int  index;
+
+    for(index = 0;  index < LPP_TotalPriorities; index++) {
+      LruPage *lru_page;
+      LruPage *next_lru_page;
+
+      lru_page = this->_m.lru_page_array[index];
+      while(lru_page) {
+        next_lru_page = lru_page->_m.next;
+
+        lru_page->_m.lock = false;
+
+        lru_page = next_lru_page;
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::page_out_lru
+//       Access: Public
+//  Description: Pages out the lowest priority pages until the
+//               memory_required is satisfied.  This will unlock
+//               all pages if needed.
+////////////////////////////////////////////////////////////////////
+bool Lru::page_out_lru (int memory_required)
+{
+  bool state;
+  int attempts;
+
+  state = false;
+  attempts = 0;
+  if(this->_m.total_pages > 0) {
+    LruMutexHolder(this->_m.mutex);
+
+    do {
+      int index;
+
+      // page out lower priority pages first
+      for(index = LPP_PageOut - 1; index >= 0; index--) {
+        LruPage *lru_page;
+        LruPage *next_lru_page;
+
+        lru_page = this->_m.lru_page_array[index];
+        while(lru_page) {
+          next_lru_page = lru_page->_m.next;
+
+          if(lru_page->_m.lock == false && lru_page->_m.in_cache) {
+            memory_required -= lru_page->_m.size;
+            this->_m.available_memory += lru_page->_m.size;
+            lru_page->_m.in_cache = false;
+
+            // PAGE OUT CALLBACK
+            this->_m.page_out_function_array[lru_page->_m.type](lru_page);
+            this->_m.total_lifetime_page_outs++;
+
+            // MOVE THE PAGE TO THE LPP_PageOut PRIORITY
+            this->remove_page(lru_page);
+            this->add_page(LPP_PageOut, lru_page);
+
+            if(memory_required <= 0) {
+              break;
+            }
+          }
+
+          lru_page = next_lru_page;
+        }
+
+        if(memory_required <= 0) {
+          break;
+        }
+      }
+
+      if(memory_required > 0) {
+        // WARNING: pages could not be freed, all pages unlocked
+        this->unlock_all_pages( );
+        state = false;
+      }
+      else {
+        state = true;
+      }
+
+      attempts++;
+    } while(state == false && attempts < 2);
+  }
+
+  return state;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::count_priority_level_pages
+//       Access: Public
+//  Description: Debug function. Counts the number of pages for each
+//               priority level.
+////////////////////////////////////////////////////////////////////
+void Lru::count_priority_level_pages (void)
+{
+  int  index;
+
+  LruMutexHolder(this->_m.mutex);
+
+  for(index = 0; index < LPP_TotalPriorities; index++) {
+    int total_pages;
+    LruPage *lru_page;
+    LruPage *next_lru_page;
+
+    total_pages = 0;
+    lru_page = this->_m.lru_page_array[index];
+    while(lru_page) {
+      next_lru_page = lru_page->_m.next;
+
+      total_pages++;
+
+      lru_page = next_lru_page;
+    }
+
+    this->_m.lru_page_count_array[index] = total_pages;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Lru::calculate_lru_statistics
+//       Access: Public
+//  Description: Debug function.
+////////////////////////////////////////////////////////////////////
+void Lru::calculate_lru_statistics (void)
+{
+  LruMutexHolder(this->_m.mutex);
+
+  if(this->_m.maximum_page_types > 0) {
+    int  index;
+
+    memset(this->_m.page_type_statistics_array, 0,
+           sizeof (PageTypeStatistics) * this->_m.maximum_page_types);
+    for(index = 0;  index < LPP_TotalPriorities;  index++) {
+      LruPage *lru_page;
+      LruPage *next_lru_page;
+      PageTypeStatistics *page_type_statistics;
+
+      lru_page = this->_m.lru_page_array[index];
+      while(lru_page) {
+        int  type;
+
+        next_lru_page = lru_page->_m.next;
+
+        type = lru_page->_m.type;
+        page_type_statistics = &this->_m.page_type_statistics_array[type];
+        page_type_statistics->total_pages++;
+
+        if(lru_page->_m.in_cache) {
+          page_type_statistics->total_pages_in++;
+          page_type_statistics->total_memory_in += lru_page->_m.size;
+        }
+        else {
+          page_type_statistics->total_pages_out++;
+          page_type_statistics->total_memory_out += lru_page->_m.size;
+        }
+
+        lru_page = next_lru_page;
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: calculate_exponential_moving_average
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+float calculate_exponential_moving_average(float value,
+  float weight, float average)
+{
+  return ((value - average) * weight) + average;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: default_page_in_function
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool default_page_in_function(LruPage *lru_page)
+{
+
+#if LRU_UNIT_TEST
+  char  string[256];
+
+  sprintf(string, "  PAGE IN %d\n", lru_page->_m.identifier);
+  OutputDebugString(string);
+#endif
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: default_page_out_function
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool default_page_out_function(LruPage *lru_page)
+{
+
+#if LRU_UNIT_TEST
+  char  string[256];
+
+  sprintf(string, "  PAGE OUT %d\n", lru_page->_m.identifier);
+  OutputDebugString(string);
+#endif
+
+  return true;
+}
+
+#if LRU_UNIT_TEST
+
+////////////////////////////////////////////////////////////////////
+//     Function: test_ema
+//       Access:
+//  Description: Unit test function for ema.
+////////////////////////////////////////////////////////////////////
+void test_ema(void)
+{
+  int    index;
+  float  usage;
+  float  weight;
+  float  average;
+
+  weight  = 0.2f;
+  average = 1.0f;
+  for(index = 0; index < 50; index++) {
+    if(index < 25) {
+      usage = (float) (index & 0x01);
+    }
+    else {
+      usage = 0.0f;
+    }
+    average =
+      calculate_exponential_moving_average(usage, weight, average);
+
+    char  string[256];
+    sprintf(string, "%d  %f\n", index, average);
+    OutputDebugString(string);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: test_lru
+//       Access:
+//  Description: Unit test function for Lru.
+////////////////////////////////////////////////////////////////////
+void test_lru(void)
+{
+  int maximum_memory;
+  int maximum_pages;
+  int maximum_page_types;
+  Lru *lru;
+
+  test_ema( );
+
+  maximum_memory = 3000000;
+  maximum_pages = 3;
+  maximum_page_types = 4;
+  lru = new Lru (maximum_memory, maximum_pages, maximum_page_types);
+  if(lru) {
+    lru->_m.minimum_memory = 1000000;
+
+    LruPage *lru_page_0;
+    LruPage *lru_page_1;
+    LruPage *lru_page_2;
+    LruPage *lru_page_3;
+    LruPage *lru_page_4;
+    LruPage *lru_page_5;
+
+    lru_page_0 = lru->allocate_page(1000000);
+    if(lru_page_0) {
+      lru->add_page(LPP_PageOut, lru_page_0);
+    }
+
+    lru_page_1 = lru->allocate_page(1000000);
+    if(lru_page_1) {
+      lru->add_page(LPP_PageOut, lru_page_1);
+    }
+
+    lru_page_2 = lru->allocate_page(1000000);
+    if(lru_page_2) {
+      lru->add_page(LPP_PageOut, lru_page_2);
+    }
+
+    lru_page_3 = lru->allocate_page(1000000);
+    if(lru_page_3) {
+      lru->add_page(LPP_PageOut, lru_page_3);
+    }
+
+    lru_page_4 = lru->allocate_page(1000000);
+    if(lru_page_4) {
+      lru->add_page(LPP_PageOut, lru_page_4);
+    }
+
+    lru_page_5 = lru->allocate_page(1000000);
+    if(lru_page_5) {
+      lru->add_page(LPP_PageOut, lru_page_5);
+    }
+
+    int index;
+    int total_frames;
+
+    total_frames = 300;
+    for(index = 0;  index < total_frames;  index++) {
+      char  string[256];
+
+      sprintf(string, "FRAME %d\n", index);
+      OutputDebugString(string);
+
+      lru->begin_frame( );
+
+      if(index <= 5) {
+        lru->access_page(lru_page_0);
+      }
+
+      lru->access_page(lru_page_1);
+      lru->access_page(lru_page_1);
+
+      if(index & 0x01) {
+        lru->access_page(lru_page_2);
+      }
+
+      if((index % 10) == 0) {
+        lru->access_page(lru_page_3);
+      }
+
+      if(index >= 100) {
+        lru->access_page(lru_page_4);
+      }
+
+      if(index >= 200) {
+        lru->access_page(lru_page_5);
+      }
+
+      if(false) {
+        lru->update_entire_lru( );
+      }
+      else {
+        int  maximum_updates;
+
+        maximum_updates = 3;
+        lru->partial_lru_update(maximum_updates);
+      }
+    }
+
+    if(!true) {
+      lru->remove_page(lru_page_2);
+      lru->free_page(lru_page_2);
+
+      lru->remove_page(lru_page_3);
+      lru->free_page(lru_page_3);
+
+      lru->remove_page(lru_page_1);
+      lru->free_page(lru_page_1);
+    }
+
+    delete lru;
+  }
+}
+
+#endif

+ 271 - 0
panda/src/display/lru.h

@@ -0,0 +1,271 @@
+// Filename: lru.h
+// Created by: aignacio (12Dec05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2006, Disney Enterprises, Inc.  All rights
+// reserved.
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef LRU_H
+#define LRU_H
+
+#define ENABLE_MUTEX 1
+
+#if ENABLE_MUTEX
+#include "pmutex.h"
+#include "mutexHolder.h"
+#define LruMutexHolder(mutex) MutexHolder(mutex)
+#else
+#define LruMutexHolder(mutex)
+#endif
+
+
+static const int MAXIMUM_LRU_PAGE_TYPES = 8;
+static const int FRAME_MAXIMUM_PRIORITY_CHANGES = 256;
+
+
+class Lru;
+class LruPage;
+
+enum LruPagePriority
+{
+  LPP_Highest = 0,
+  LPP_High = 10,
+  LPP_New = 20,
+  LPP_Normal = 25,
+  LPP_Intermediate = 30,
+  LPP_Low = 40,
+  LPP_TotalPriorities = 50,
+
+  LPP_PageOut = LPP_TotalPriorities - 1
+};
+
+typedef union _LruPageType
+{
+  void *pointer;
+
+}
+LruPageType;
+
+typedef struct
+{
+  int total_pages;
+  int total_pages_in;
+  int total_pages_out;
+  int total_memory_in;
+  int total_memory_out;
+}
+PageTypeStatistics;
+
+typedef bool (*LruPageTypeFunction) (LruPage *lru_page);
+
+class EXPCL_PANDADX LruPage
+{
+
+protected:
+
+  LruPage ( );
+  ~LruPage ( );
+  void change_priority (int delta);
+
+public:
+
+  typedef struct _LruPageVariables
+  {
+    LruPageType lru_page_type;  // pointer to memory type
+
+    int size;
+    LruPagePriority priority;
+    int priority_change;
+
+    struct
+    {
+      unsigned int type : 8;
+      unsigned int lock : 1;
+      unsigned int in_cache : 1;
+      unsigned int in_memory : 1;
+      unsigned int on_disk : 1;
+      unsigned int pre_allocated : 1;
+      unsigned int allocated : 1;
+      unsigned int in_lru : 1;
+    };
+
+    int first_frame_identifier;   // creation time
+    int last_frame_identifier;    // previous time page was used
+    int current_frame_identifier;
+    int update_frame_identifier;
+
+    int current_frame_usage;
+    int last_frame_usage;
+
+    int total_frame_page_faults;
+    int total_page_faults;
+
+    int total_usage;
+    int update_total_usage;
+
+    int identifier;
+
+    float average_frame_utilization;
+
+    LruPage *previous;
+    LruPage *next;
+    Lru *lru;
+  }
+  LruPageVariables;
+
+  LruPageVariables _m;
+
+  friend class Lru;
+};
+
+////////////////////////////////////////////////////////////////////
+//       Class : Lru
+// Description : Least Recently Used algorithm implementation:
+// In the Lru, each "memory page" has an associated class LruPage.
+// The Lru has a range of priorities from LPP_Highest to
+// LPP_PagedOut. Each priority has a doubly linked list of LruPages.
+// The algorithim uses an adaptive method based on the average
+// utilization of each page per frame (or time slice). The
+// average utilization is calculated with an exponetial moving
+// average. This is superior to a standard average since a standard
+// average becomes less and less adaptive the longer a page exists.
+// The average utilization is used to set the priority of each page.
+// A higher average utilization automatically raises the priority
+// of a page and a lower average utilization automatically lowers
+// the priority of a page. Therefore, pages with a higher average
+// utilization have a higher chance of being kept in memory or
+// cached and pages with a lower average utilization have a higher
+// chance of being paged out.  When a page is paged in and there
+// is not enough memory available, then the lowest priority pages
+// will be paged out first until there is enough memory available.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDADX Lru
+{
+public:
+
+  Lru (int maximum_memory, int maximum_pages, int maximum_page_types);
+  ~Lru ( );
+
+  bool register_lru_page_type (int index, LruPageTypeFunction page_in_function, LruPageTypeFunction page_out_function);
+
+  LruPage *allocate_page (int size);
+  void update_start_update_lru_page (LruPage *lru_page);
+
+  void free_page (LruPage *lru_page);
+
+  void add_page (LruPagePriority priority, LruPage *lru_page);
+  void add_cached_page (LruPagePriority priority, LruPage *lru_page);
+  void remove_page (LruPage *lru_page);
+
+  void lock_page (LruPage *lru_page);
+  void unlock_page (LruPage *lru_page);
+
+  void access_page (LruPage *lru_page);
+
+  void set_maximum_frame_bandwidth_utilization (float maximum_frame_bandwidth_utilization);
+
+  void begin_frame ( );
+
+  void update_entire_lru ( );
+  void partial_lru_update (int maximum_updates);
+
+  // set maximum number of page updates per frame
+  // pause/resume updates/current_frame_identifier
+
+  void unlock_all_pages (void);
+
+  void count_priority_level_pages (void);
+
+  void calculate_lru_statistics (void);
+
+  bool page_out_lru (int memory_required);
+
+private:
+  void update_page_priorities (void);
+  void update_lru_page (LruPage *lru_page);
+  void update_lru_page_old (LruPage *lru_page);
+
+public:
+  typedef struct _LruVariables
+  {
+    // LruPagePriority lists
+    LruPage *lru_page_array [LPP_TotalPriorities];
+
+    int total_pages;
+    int available_memory;
+    int current_frame_identifier;
+
+    int maximum_memory;
+    int minimum_memory; // target amount of memory to keep free if possible
+    int maximum_page_types;
+
+    int total_lifetime_page_ins;
+    int total_lifetime_page_outs;
+
+    int total_page_ins_last_frame;
+    int total_page_outs_last_frame;
+
+    int total_page_ins;
+    int total_page_outs;
+
+    int total_page_access;
+    double total_page_access_size;
+    double total_page_all_access_size;
+
+    int start_priority_index;
+    LruPage *start_update_lru_page;
+
+    int identifier; // the number of pages created during the lifetime of the LRU
+
+    float weight; // used for exponential moving average
+    float maximum_frame_bandwidth_utilization;
+
+    float frame_bandwidth_factor;
+
+    LruPageTypeFunction page_in_function_array [MAXIMUM_LRU_PAGE_TYPES];
+    LruPageTypeFunction page_out_function_array [MAXIMUM_LRU_PAGE_TYPES];
+
+    int total_lru_page_priority_changes;
+    LruPage *lru_page_priority_change_array [FRAME_MAXIMUM_PRIORITY_CHANGES];
+
+    int maximum_pages;
+    int total_lru_pages_in_pool;
+    int total_lru_pages_in_free_pool;
+    LruPage **lru_page_pool;
+    LruPage **lru_page_free_pool;
+
+    int lru_page_count_array [LPP_TotalPriorities];
+    PageTypeStatistics *page_type_statistics_array;
+
+    void *context;  // user specified data
+
+#if ENABLE_MUTEX
+    Mutex *mutex;
+#endif
+  }
+  LruVariables;
+
+  LruVariables _m;
+
+  friend class LruPage;
+};
+
+float calculate_exponential_moving_average (float value, float weight, float average);
+bool default_page_in_function (LruPage *lru_page);
+bool default_page_out_function (LruPage *lru_page);
+
+void test_ema (void);
+void test_lru (void);
+
+#endif