Browse Source

chunk-a-time vertex compression

David Rose 17 years ago
parent
commit
6d25ba7710
3 changed files with 147 additions and 15 deletions
  1. 22 0
      panda/src/express/zStreamBuf.cxx
  2. 97 14
      panda/src/gobj/vertexDataPage.cxx
  3. 28 1
      panda/src/gobj/vertexDataPage.h

+ 22 - 0
panda/src/express/zStreamBuf.cxx

@@ -19,6 +19,18 @@
 #include "pnotify.h"
 #include "config_express.h"
 
+#if !defined(USE_MEMORY_NOWRAPPERS)
+// Define functions that hook zlib into panda's memory allocation system.
+static void *
+do_zlib_alloc(voidpf opaque, uInt items, uInt size) {
+  return PANDA_MALLOC_ARRAY(items * size);
+}
+static void 
+do_zlib_free(voidpf opaque, voidpf address) {
+  PANDA_FREE_ARRAY(address);
+}
+#endif  !USE_MEMORY_NOWRAPPERS
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ZStreamBuf::Constructor
 //       Access: Public
@@ -70,8 +82,13 @@ open_read(istream *source, bool owns_source) {
 
   _z_source.next_in = Z_NULL;
   _z_source.avail_in = 0;
+#ifdef USE_MEMORY_NOWRAPPERS
   _z_source.zalloc = Z_NULL;
   _z_source.zfree = Z_NULL;
+#else
+  _z_source.zalloc = (alloc_func)&do_zlib_alloc;
+  _z_source.zfree = (free_func)&do_zlib_free;
+#endif
   _z_source.opaque = Z_NULL;
   _z_source.msg = "no error message";
 
@@ -116,8 +133,13 @@ open_write(ostream *dest, bool owns_dest, int compression_level) {
   _dest = dest;
   _owns_dest = owns_dest;
 
+#ifdef USE_MEMORY_NOWRAPPERS
   _z_dest.zalloc = Z_NULL;
   _z_dest.zfree = Z_NULL;
+#else
+  _z_dest.zalloc = (alloc_func)&do_zlib_alloc;
+  _z_dest.zfree = (free_func)&do_zlib_free;
+#endif
   _z_dest.opaque = Z_NULL;
   _z_dest.msg = "no error message";
 

+ 97 - 14
panda/src/gobj/vertexDataPage.cxx

@@ -80,6 +80,20 @@ PStatCollector VertexDataPage::_thread_wait_pcollector("Wait:Idle");
 PStatCollector VertexDataPage::_alloc_pages_pcollector("System memory:MMap:Vertex data");
 
 TypeHandle VertexDataPage::_type_handle;
+TypeHandle VertexDataPage::DeflatePage::_type_handle;
+
+#if defined(HAVE_ZLIB) && !defined(USE_MEMORY_NOWRAPPERS)
+// Define functions that hook zlib into panda's memory allocation system.
+static void *
+do_zlib_alloc(voidpf opaque, uInt items, uInt size) {
+  return PANDA_MALLOC_ARRAY(items * size);
+}
+static void 
+do_zlib_free(voidpf opaque, voidpf address) {
+  PANDA_FREE_ARRAY(address);
+}
+#endif  // HAVE_ZLIB && !USE_MEMORY_NOWRAPPERS
+
 
 ////////////////////////////////////////////////////////////////////
 //     Function: VertexDataPage::Book Constructor
@@ -356,6 +370,7 @@ make_resident() {
       gobj_cat.error()
         << "Couldn't expand: zlib error " << result << "\n";
       nassert_raise("zlib error");
+      memset(new_data, 0, new_allocated_size);
     }
     nassertv(dest_len == _uncompressed_size);
 
@@ -396,28 +411,96 @@ make_compressed() {
 #ifdef HAVE_ZLIB
     PStatTimer timer(_vdata_compress_pcollector);
 
-    // According to the zlib manual, we need to provide this much
-    // buffer to the compress algorithm: 0.1% bigger plus twelve
-    // bytes.
-    uLongf buffer_size = _uncompressed_size + ((_uncompressed_size + 999) / 1000) + 12;
-    Bytef *buffer = (Bytef *)alloca(buffer_size);
+    DeflatePage *page = new DeflatePage;
+    DeflatePage *head = page;
 
-    int result = compress2(buffer, &buffer_size,
-                           _page_data, _uncompressed_size,
-                           vertex_data_compression_level);
-    if (result != Z_OK) {
-      gobj_cat.error()
-        << "Couldn't compress: zlib error " << result << "\n";
+    z_stream z_dest;
+#ifdef USE_MEMORY_NOWRAPPERS
+    z_dest.zalloc = Z_NULL;
+    z_dest.zfree = Z_NULL;
+#else
+    z_dest.zalloc = (alloc_func)&do_zlib_alloc;
+    z_dest.zfree = (free_func)&do_zlib_free;
+#endif
+
+    z_dest.opaque = Z_NULL;
+    z_dest.msg = "no error message";
+    
+    int result = deflateInit(&z_dest, vertex_data_compression_level);
+    if (result < 0) {
       nassert_raise("zlib error");
+      return;
     }
+    Thread::consider_yield();
 
-    size_t new_allocated_size = round_up(buffer_size);
+    z_dest.next_in = (Bytef *)(char *)_page_data;
+    z_dest.avail_in = _uncompressed_size;
+    size_t output_size = 0;
+
+    // Compress the data into one or more individual pages.  We have
+    // to compress it page-at-a-time, since we're not really sure how
+    // big the result will be (so we can't easily pre-allocate a
+    // buffer).
+    int flush = 0;
+    result = 0;
+    while (result != Z_STREAM_END) {
+      unsigned char *start_out = (page->_buffer + page->_used_size);
+      z_dest.next_out = (Bytef *)start_out;
+      z_dest.avail_out = (size_t)deflate_page_size - page->_used_size;
+      if (z_dest.avail_out == 0) {
+        DeflatePage *new_page = new DeflatePage;
+        page->_next = new_page;
+        page = new_page;
+        start_out = page->_buffer;
+        z_dest.next_out = (Bytef *)start_out;
+        z_dest.avail_out = deflate_page_size;
+      }
+
+      result = deflate(&z_dest, flush);
+      if (result < 0 && result != Z_BUF_ERROR) {
+        nassert_raise("zlib error");
+        return;
+      }
+      size_t bytes_produced = (size_t)((unsigned char *)z_dest.next_out - start_out);
+      page->_used_size += bytes_produced;
+      nassertv(page->_used_size <= deflate_page_size);
+      output_size += bytes_produced;
+      if (bytes_produced == 0) {
+        // If we ever produce no bytes, then start flushing the output.
+        flush = Z_FINISH;
+      }
+
+      Thread::consider_yield();
+    }
+    nassertv(z_dest.avail_in == 0);
+
+    result = deflateEnd(&z_dest);
+    nassertv(result == Z_OK);
+
+    // Now we know how big the result will be.  Allocate a buffer, and
+    // copy the data from the various pages.
+
+    size_t new_allocated_size = round_up(output_size);
     unsigned char *new_data = alloc_page_data(new_allocated_size);
-    memcpy(new_data, buffer, buffer_size);
 
+    size_t copied_size = 0;
+    unsigned char *p = new_data;
+    page = head;
+    while (page != NULL) {
+      memcpy(p, page->_buffer, page->_used_size);
+      copied_size += page->_used_size;
+      p += page->_used_size;
+      DeflatePage *next = page->_next;
+      delete page;
+      page = next;
+    }
+    nassertv(copied_size == output_size);
+    
+    // Now free the original, uncompressed data, and put this new
+    // compressed buffer in its place.
     free_page_data(_page_data, _allocated_size);
     _page_data = new_data;
-    _size = buffer_size;
+    _size = output_size;
     _allocated_size = new_allocated_size;
 
     if (gobj_cat.is_debug()) {

+ 28 - 1
panda/src/gobj/vertexDataPage.h

@@ -165,11 +165,38 @@ private:
   size_t _block_size;
 
   //Mutex _lock;  // Inherited from SimpleAllocator.  Protects above members.
-
   RamClass _pending_ram_class;  // Protected by _tlock.
 
   VertexDataBook *_book;  // never changes.
 
+  enum { deflate_page_size = 1024 };
+
+  // We build up a temporary linked list of these while deflating
+  // (compressing) the vertex data in-memory.
+  class DeflatePage {
+  public:
+    DeflatePage() {
+      _used_size = 0;
+      _next = NULL;
+    }
+    ALLOC_DELETED_CHAIN(DeflatePage);
+
+    unsigned char _buffer[deflate_page_size];
+    size_t _used_size;
+    DeflatePage *_next;
+    
+  public:
+    static TypeHandle get_class_type() {
+      return _type_handle;
+    }
+    static void init_type() {
+      register_type(_type_handle, "VertexDataPage::DeflatePage");
+    }
+    
+  private:
+    static TypeHandle _type_handle;
+  };
+
   static SimpleLru _resident_lru;
   static SimpleLru _compressed_lru;
   static SimpleLru _disk_lru;