svga: implement surface cache size limit
authorBrian Paul <brianp@vmware.com>
Wed, 7 Dec 2011 23:57:11 +0000 (16:57 -0700)
committerBrian Paul <brianp@vmware.com>
Thu, 23 Feb 2012 14:49:06 +0000 (07:49 -0700)
There was a SVGA_HOST_SURFACE_CACHE_BYTES symbol, but it was never
used.

Now when we go to add a newly deleted surface to the cache we check
if the cache size would be exceeded.  If so, try to free the least
recently "unused" surfaces until the cache is smaller.  If we can't
do that, simply don't cache the newly deleted surface.  The alternative
involves flushing and waiting and we don't want to do that.

Reviewed-by: José Fonseca <jfonseca@vmware.com>
src/gallium/drivers/svga/svga_screen_cache.c
src/gallium/drivers/svga/svga_screen_cache.h

index eff36e0..13df37f 100644 (file)
  *
  **********************************************************/
 
+#include "util/u_math.h"
 #include "util/u_memory.h"
 #include "util/u_hash.h"
 
 #include "svga_debug.h"
+#include "svga_format.h"
 #include "svga_winsys.h"
 #include "svga_screen.h"
 #include "svga_screen_cache.h"
 #define SVGA_SURFACE_CACHE_ENABLED 1
 
 
+/**
+ * Return the size of the surface described by the key (in bytes).
+ */
+static unsigned
+surface_size(const struct svga_host_surface_cache_key *key)
+{
+   unsigned bw, bh, bpb, total_size, i;
+
+   assert(key->numMipLevels > 0);
+   assert(key->numFaces > 0);
+
+   if (key->format == SVGA3D_BUFFER) {
+      /* Special case: we don't want to count vertex/index buffers
+       * against the cache size limit, so view them as zero-sized.
+       */
+      return 0;
+   }
+
+   svga_format_size(key->format, &bw, &bh, &bpb);
+
+   total_size = 0;
+
+   for (i = 0; i < key->numMipLevels; i++) {
+      unsigned w = u_minify(key->size.width, i);
+      unsigned h = u_minify(key->size.height, i);
+      unsigned d = u_minify(key->size.depth, i);
+      unsigned img_size = ((w + bw - 1) / bw) * ((h + bh - 1) / bh) * d * bpb;
+      total_size += img_size;
+   }
+
+   total_size *= key->numFaces;
+
+   return total_size;
+}
+
+
 /** 
  * Compute the bucket for this key. 
  */
@@ -45,6 +83,11 @@ svga_screen_cache_bucket(const struct svga_host_surface_cache_key *key)
 }
 
 
+/**
+ * Search the cache for a surface that matches the key.  If a match is
+ * found, remove it from the cache and return the surface pointer.
+ * Return NULL otherwise.
+ */
 static INLINE struct svga_winsys_surface *
 svga_screen_cache_lookup(struct svga_screen *svgascreen,
                          const struct svga_host_surface_cache_key *key)
@@ -74,9 +117,11 @@ svga_screen_cache_lookup(struct svga_screen *svgascreen,
       
       if(memcmp(&entry->key, key, sizeof *key) == 0 &&
          sws->fence_signalled( sws, entry->fence, 0 ) == 0) {
+         unsigned surf_size;
+
          assert(sws->surface_is_flushed(sws, entry->handle));
          
-         handle = entry->handle; // Reference is transfered here.
+         handle = entry->handle; /* Reference is transfered here. */
          entry->handle = NULL;
          
          LIST_DEL(&entry->bucket_head);
@@ -85,6 +130,14 @@ svga_screen_cache_lookup(struct svga_screen *svgascreen,
          
          LIST_ADD(&entry->head, &cache->empty);
 
+         /* update the cache size */
+         surf_size = surface_size(&entry->key);
+         assert(surf_size <= cache->total_size);
+         if (surf_size > cache->total_size)
+            cache->total_size = 0; /* should never happen, but be safe */
+         else
+            cache->total_size -= surf_size;
+
          break;
       }
 
@@ -102,6 +155,45 @@ svga_screen_cache_lookup(struct svga_screen *svgascreen,
 }
 
 
+/**
+ * Free the least recently used entries in the surface cache until the
+ * cache size is <= the target size OR there are no unused entries left
+ * to discard.  We don't do any flushing to try to free up additional
+ * surfaces.
+ */
+static void
+svga_screen_cache_shrink(struct svga_screen *svgascreen,
+                         unsigned target_size)
+{
+   struct svga_host_surface_cache *cache = &svgascreen->cache;
+   struct svga_winsys_screen *sws = svgascreen->sws;
+   struct svga_host_surface_cache_entry *entry, *next_entry;
+
+   /* Walk over the list of unused buffers in reverse order: from oldest
+    * to newest.
+    */
+   LIST_FOR_EACH_ENTRY_SAFE_REV(entry, next_entry, &cache->unused, head) {
+      if (entry->key.format != SVGA3D_BUFFER) {
+         /* we don't want to discard vertex/index buffers */
+
+         cache->total_size -= surface_size(&entry->key);
+
+         assert(entry->handle);
+         sws->surface_reference(sws, &entry->handle, NULL);
+
+         LIST_DEL(&entry->bucket_head);
+         LIST_DEL(&entry->head);
+         LIST_ADD(&entry->head, &cache->empty);
+
+         if (cache->total_size <= target_size) {
+            /* all done */
+            break;
+         }
+      }
+   }
+}
+
+
 /*
  * Transfers a handle reference.
  */
@@ -115,6 +207,7 @@ svga_screen_cache_add(struct svga_screen *svgascreen,
    struct svga_winsys_screen *sws = svgascreen->sws;
    struct svga_host_surface_cache_entry *entry = NULL;
    struct svga_winsys_surface *handle = *p_handle;
+   unsigned surf_size;
    
    assert(key->cachable);
 
@@ -122,9 +215,37 @@ svga_screen_cache_add(struct svga_screen *svgascreen,
    if(!handle)
       return;
    
+   surf_size = surface_size(key);
+
    *p_handle = NULL;
    pipe_mutex_lock(cache->mutex);
    
+   if (surf_size >= SVGA_HOST_SURFACE_CACHE_BYTES) {
+      /* this surface is too large to cache, just free it */
+      sws->surface_reference(sws, &handle, NULL);
+      pipe_mutex_unlock(cache->mutex);
+      return;
+   }
+
+   if (cache->total_size + surf_size > SVGA_HOST_SURFACE_CACHE_BYTES) {
+      /* Adding this surface would exceed the cache size.
+       * Try to discard least recently used entries until we hit the
+       * new target cache size.
+       */
+      unsigned target_size = SVGA_HOST_SURFACE_CACHE_BYTES - surf_size;
+
+      svga_screen_cache_shrink(svgascreen, target_size);
+
+      if (cache->total_size > target_size) {
+         /* we weren't able to shrink the cache as much as we wanted so
+          * just discard this surface.
+          */
+         sws->surface_reference(sws, &handle, NULL);
+         pipe_mutex_unlock(cache->mutex);
+         return;
+      }
+   }
+
    if(!LIST_IS_EMPTY(&cache->empty)) {
       /* use the first empty entry */
       entry = LIST_ENTRY(struct svga_host_surface_cache_entry, cache->empty.next, head);
@@ -136,6 +257,9 @@ svga_screen_cache_add(struct svga_screen *svgascreen,
       entry = LIST_ENTRY(struct svga_host_surface_cache_entry, cache->unused.prev, head);
       SVGA_DBG(DEBUG_CACHE|DEBUG_DMA,
                "unref sid %p (make space)\n", entry->handle);
+
+      cache->total_size -= surface_size(&entry->key);
+
       sws->surface_reference(sws, &entry->handle, NULL);
 
       LIST_DEL(&entry->bucket_head);
@@ -150,6 +274,8 @@ svga_screen_cache_add(struct svga_screen *svgascreen,
       SVGA_DBG(DEBUG_CACHE|DEBUG_DMA,
                "cache sid %p\n", entry->handle);
       LIST_ADD(&entry->head, &cache->validated);
+
+      cache->total_size += surf_size;
    }
    else {
       /* Couldn't cache the buffer -- this really shouldn't happen */
@@ -216,6 +342,8 @@ svga_screen_cache_cleanup(struct svga_screen *svgascreen)
         SVGA_DBG(DEBUG_CACHE|DEBUG_DMA,
                   "unref sid %p (shutdown)\n", cache->entries[i].handle);
         sws->surface_reference(sws, &cache->entries[i].handle, NULL);
+
+         cache->total_size -= surface_size(&cache->entries[i].key);
       }
 
       if(cache->entries[i].fence)
@@ -232,6 +360,8 @@ svga_screen_cache_init(struct svga_screen *svgascreen)
    struct svga_host_surface_cache *cache = &svgascreen->cache;
    unsigned i;
 
+   assert(cache->total_size == 0);
+
    pipe_mutex_init(cache->mutex);
    
    for(i = 0; i < SVGA_HOST_SURFACE_CACHE_BUCKETS; ++i)
index 62156e3..55e1c66 100644 (file)
@@ -1,3 +1,4 @@
+
 /**********************************************************
  * Copyright 2008-2009 VMware, Inc.  All rights reserved.
  *
@@ -39,7 +40,7 @@
 /* Guess the storage size of cached surfaces and try and keep it under
  * this amount:
  */ 
-#define SVGA_HOST_SURFACE_CACHE_BYTES 16*1024*1024
+#define SVGA_HOST_SURFACE_CACHE_BYTES (16 * 1024 * 1024)
 
 /* Maximum number of discrete surfaces in the cache:
  */
@@ -89,7 +90,7 @@ struct svga_host_surface_cache_entry
  * Cache of the host surfaces.
  * 
  * A cache entry can be in the following stages:
- * 1. empty
+ * 1. empty (entry->handle = NULL)
  * 2. holding a buffer in a validate list
  * 3. holding a flushed buffer (not in any validate list) with an active fence
  * 4. holding a flushed buffer with an expired fence
@@ -117,6 +118,9 @@ struct svga_host_surface_cache
 
    /** The actual storage for the entries */
    struct svga_host_surface_cache_entry entries[SVGA_HOST_SURFACE_CACHE_SIZE];
+
+   /** Sum of sizes of all surfaces (in bytes) */
+   unsigned total_size;
 };