瀏覽代碼

gobj: Add generic circular allocator

Will use this in Vulkan renderer, but may be used for other renderers as well
rdb 4 年之前
父節點
當前提交
9f70c02000

+ 2 - 0
panda/src/gobj/CMakeLists.txt

@@ -4,6 +4,7 @@ set(P3GOBJ_HEADERS
   bufferContext.I bufferContext.h
   bufferContextChain.I bufferContextChain.h
   bufferResidencyTracker.I bufferResidencyTracker.h
+  circularAllocator.I circularAllocator.h
   config_gobj.h
   geom.h geom.I
   geomContext.I geomContext.h
@@ -87,6 +88,7 @@ set(P3GOBJ_SOURCES
   bufferContextChain.cxx
   bufferResidencyTracker.cxx
   config_gobj.cxx
+  circularAllocator.cxx
   geomContext.cxx
   geom.cxx
   geomEnums.cxx

+ 74 - 0
panda/src/gobj/circularAllocator.I

@@ -0,0 +1,74 @@
+/**
+ * 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."
+ *
+ * @file circularAllocator.I
+ * @author rdb
+ * @date 2022-01-16
+ */
+
+/**
+ * Creates an allocator for a buffer with the given size.  If an alignment is
+ * given, all allocations will satisfy the given alignment, even if a lower
+ * alignment value is given in the call to alloc().
+ */
+INLINE CircularAllocator::
+CircularAllocator(size_t capacity, size_t min_alignment) :
+  _capacity(capacity),
+  _min_alignment(min_alignment)
+{
+}
+
+/**
+ * Frees all memory.
+ */
+INLINE void CircularAllocator::
+reset() {
+  _head = 0;
+  _tail = 0;
+}
+
+/**
+ * Returns the total capacity of the buffer.
+ */
+INLINE size_t CircularAllocator::
+get_capacity() const {
+  return _capacity;
+}
+
+/**
+ * Returns the total size of all allocations.
+ */
+INLINE size_t CircularAllocator::
+get_size() const {
+  return (get_head() - get_tail()) % _capacity;
+}
+
+/**
+ * Returns the offset to the head of the buffer, or the "write pointer".
+ */
+INLINE size_t CircularAllocator::
+get_head() const {
+  return (size_t)AtomicAdjust::get(_head);
+}
+
+/**
+ * Returns the offset to the tail of the buffer, or the "read pointer".
+ */
+INLINE size_t CircularAllocator::
+get_tail() const {
+  return (size_t)AtomicAdjust::get(_tail);
+}
+
+/**
+ * Sets the tail of the buffer, or the "read pointer", freeng up the memory
+ * up to the given offset.
+ */
+INLINE void CircularAllocator::
+set_tail(size_t tail) {
+  AtomicAdjust::set(_tail, tail);
+}

+ 88 - 0
panda/src/gobj/circularAllocator.cxx

@@ -0,0 +1,88 @@
+/**
+ * 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."
+ *
+ * @file circularAllocator.cxx
+ * @author rdb
+ * @date 2022-01-16
+ */
+
+#include "circularAllocator.h"
+
+/**
+ * Advances the head.  Returns the new offset, or -1 if there was no space for
+ * a contiguous allocation of this size and alignment.
+ */
+ssize_t CircularAllocator::
+alloc(size_t size, size_t alignment) {
+  if (size > _capacity) {
+    return -1;
+  }
+
+  // If the head pointer changes while we calculate the new head pointer,
+  // repeat the process.
+  size_t prev_head;
+  size_t this_head;
+  size_t next_head;
+  do {
+    prev_head = get_head();
+
+    // Align.
+    this_head = prev_head;
+    if (alignment > 1) {
+      this_head = (prev_head + alignment - 1) / alignment * alignment;
+    }
+    next_head = this_head + size;
+
+    if ((size_t)next_head > _capacity) {
+      // Write to the beginning (we want a continuous block).
+      this_head = 0;
+      next_head = size;
+
+      // Add padding to ensure that the next allocation will be aligned.
+      size_t min_alignment = _min_alignment;
+      if (min_alignment > 0) {
+        next_head = (next_head + min_alignment - 1) / min_alignment * min_alignment;
+      }
+
+      size_t this_tail = get_tail();
+      if (next_head >= this_tail) {
+        return -1;
+      }
+    }
+    else if ((size_t)next_head == _capacity) {
+      // Write until the end.
+      // Set next_head to _capacity (instead of 0) so that we can fill
+      // up the buffer if the read pointer is at 0.  This is the only
+      // situation in which the buffer can be fully filled up.
+      next_head = _capacity;
+      size_t this_tail = get_tail();
+      if (this_tail > prev_head) {
+        return -1;
+      }
+    }
+    else {
+      // Add padding to ensure that the next allocation will be aligned to the
+      // minimum alignment.
+      size_t min_alignment = _min_alignment;
+      if (min_alignment > 1) {
+        // It's possible that the next head now falls past the capacity (if
+        // the capacity isn't a multiple of the alignment) so we clamp it.
+        next_head = std::min(_capacity, (next_head + min_alignment - 1) / min_alignment * min_alignment);
+      }
+
+      // Don't pass the read pointer.
+      size_t this_tail = get_tail();
+      if ((prev_head >= this_tail) != (next_head >= this_tail)) {
+        return -1;
+      }
+    }
+  }
+  while ((size_t)AtomicAdjust::compare_and_exchange(_head, prev_head, next_head) != prev_head);
+
+  return (ssize_t)this_head;
+}

+ 58 - 0
panda/src/gobj/circularAllocator.h

@@ -0,0 +1,58 @@
+/**
+ * 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."
+ *
+ * @file circularAllocator.h
+ * @author rdb
+ * @date 2022-01-16
+ */
+
+#ifndef CIRCULARALLOCATOR_H
+#define CIRCULARALLOCATOR_H
+
+#include "pandabase.h"
+#include "linkedListNode.h"
+#include "pmutex.h"
+#include "mutexHolder.h"
+
+/**
+ * An implementation of a very simple thread-safe circular allocator.
+ * Contiguousness of allocations is enforced.  In order to free memory, you
+ * need to call get_head() after an allocation, then call set_tail(offset)
+ * later.
+ *
+ * Note that it's not possible to fill the buffer up entirely unless the tail
+ * happens to be positioned at 0.
+ */
+class EXPCL_PANDA_GOBJ CircularAllocator {
+public:
+  constexpr CircularAllocator() = default;
+  INLINE explicit CircularAllocator(size_t capacity, size_t min_alignment=0);
+  CircularAllocator(CircularAllocator &&from) noexcept = default;
+
+  CircularAllocator &operator = (CircularAllocator &&from) noexcept = default;
+
+  ssize_t alloc(size_t size, size_t alignment=0);
+
+  INLINE void reset();
+
+  INLINE size_t get_capacity() const;
+  INLINE size_t get_size() const;
+  INLINE size_t get_head() const;
+  INLINE size_t get_tail() const;
+  INLINE void set_tail(size_t tail);
+
+protected:
+  AtomicAdjust::Integer _head = 0;
+  AtomicAdjust::Integer _tail = 0;
+  size_t _capacity = 0;
+  size_t _min_alignment = 0;
+};
+
+#include "circularAllocator.I"
+
+#endif

+ 1 - 0
panda/src/gobj/p3gobj_composite1.cxx

@@ -3,6 +3,7 @@
 #include "bufferContext.cxx"
 #include "bufferContextChain.cxx"
 #include "bufferResidencyTracker.cxx"
+#include "circularAllocator.cxx"
 #include "config_gobj.cxx"
 #include "geom.cxx"
 #include "geomCacheEntry.cxx"