Browse Source

cull: Use more efficient arena allocation for CullableObject

rdb 1 year ago
parent
commit
6823634f60
35 changed files with 182 additions and 255 deletions
  1. 2 10
      panda/src/bullet/bulletDebugNode.cxx
  2. 4 10
      panda/src/collide/collisionVisualizer.cxx
  3. 2 2
      panda/src/cull/binCullHandler.cxx
  4. 1 1
      panda/src/cull/binCullHandler.h
  5. 0 12
      panda/src/cull/cullBinBackToFront.cxx
  6. 0 1
      panda/src/cull/cullBinBackToFront.h
  7. 0 12
      panda/src/cull/cullBinFixed.cxx
  8. 0 1
      panda/src/cull/cullBinFixed.h
  9. 0 12
      panda/src/cull/cullBinFrontToBack.cxx
  10. 0 1
      panda/src/cull/cullBinFrontToBack.h
  11. 0 12
      panda/src/cull/cullBinStateSorted.cxx
  12. 0 1
      panda/src/cull/cullBinStateSorted.h
  13. 0 12
      panda/src/cull/cullBinUnsorted.cxx
  14. 0 1
      panda/src/cull/cullBinUnsorted.h
  15. 3 6
      panda/src/cull/drawCullHandler.cxx
  16. 1 1
      panda/src/cull/drawCullHandler.h
  17. 2 2
      panda/src/grutil/pipeOcclusionCullTraverser.I
  18. 14 28
      panda/src/grutil/pipeOcclusionCullTraverser.cxx
  19. 4 4
      panda/src/grutil/pipeOcclusionCullTraverser.h
  20. 2 2
      panda/src/grutil/shaderTerrainMesh.cxx
  21. 8 16
      panda/src/parametrics/ropeNode.cxx
  22. 2 4
      panda/src/parametrics/sheetNode.cxx
  23. 3 4
      panda/src/pgraph/cullHandler.cxx
  24. 1 1
      panda/src/pgraph/cullHandler.h
  25. 14 0
      panda/src/pgraph/cullResult.I
  26. 65 43
      panda/src/pgraph/cullResult.cxx
  27. 17 1
      panda/src/pgraph/cullResult.h
  28. 6 14
      panda/src/pgraph/cullTraverser.cxx
  29. 3 3
      panda/src/pgraph/cullableObject.h
  30. 8 11
      panda/src/pgraph/geomNode.cxx
  31. 6 8
      panda/src/pgraph/occluderNode.cxx
  32. 2 4
      panda/src/pgraph/planeNode.cxx
  33. 4 5
      panda/src/pgraphnodes/callbackNode.cxx
  34. 4 5
      panda/src/pgraphnodes/computeNode.cxx
  35. 4 5
      panda/src/pgraphnodes/nodeCullCallbackData.cxx

+ 2 - 10
panda/src/bullet/bulletDebugNode.cxx

@@ -248,16 +248,8 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
 
   // Record them without any state or transform.
   trav->_geoms_pcollector.add_level(2);
-  {
-    CullableObject *object =
-      new CullableObject(std::move(debug_lines), RenderState::make_empty(), trav->get_scene()->get_cs_world_transform());
-    trav->get_cull_handler()->record_object(object, trav);
-  }
-  {
-    CullableObject *object =
-      new CullableObject(std::move(debug_triangles), RenderState::make_empty(), trav->get_scene()->get_cs_world_transform());
-    trav->get_cull_handler()->record_object(object, trav);
-  }
+  trav->get_cull_handler()->record_object(CullableObject(std::move(debug_lines), RenderState::make_empty(), trav->get_scene()->get_cs_world_transform()), trav);
+  trav->get_cull_handler()->record_object(CullableObject(std::move(debug_triangles), RenderState::make_empty(), trav->get_scene()->get_cs_world_transform()), trav);
 }
 
 /**

+ 4 - 10
panda/src/collide/collisionVisualizer.cxx

@@ -207,11 +207,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
           PT(Geom) geom = new Geom(point_vdata);
           geom->add_primitive(points);
 
-          CullableObject *object =
-            new CullableObject(geom, point_state,
-                               xform_data.get_internal_transform(trav));
-
-          trav->get_cull_handler()->record_object(object, trav);
+          trav->get_cull_handler()->record_object(CullableObject(
+            geom, point_state, xform_data.get_internal_transform(trav)), trav);
         }
 
         // Draw the normal vector at the surface point.
@@ -236,11 +233,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
           PT(Geom) geom = new Geom(line_vdata);
           geom->add_primitive(lines);
 
-          CullableObject *object =
-            new CullableObject(geom, empty_state,
-                               xform_data.get_internal_transform(trav));
-
-          trav->get_cull_handler()->record_object(object, trav);
+          trav->get_cull_handler()->record_object(CullableObject(
+            geom, empty_state, xform_data.get_internal_transform(trav)), trav);
         }
       }
     }

+ 2 - 2
panda/src/cull/binCullHandler.cxx

@@ -19,6 +19,6 @@
  * This is called as each Geom is discovered by the CullTraverser.
  */
 void BinCullHandler::
-record_object(CullableObject *object, const CullTraverser *traverser) {
-  _cull_result->add_object(object, traverser);
+record_object(CullableObject &&object, const CullTraverser *traverser) {
+  _cull_result->add_object(std::move(object), traverser);
 }

+ 1 - 1
panda/src/cull/binCullHandler.h

@@ -28,7 +28,7 @@ class EXPCL_PANDA_CULL BinCullHandler : public CullHandler {
 public:
   INLINE BinCullHandler(CullResult *cull_result);
 
-  virtual void record_object(CullableObject *object,
+  virtual void record_object(CullableObject &&object,
                              const CullTraverser *traverser);
 
 private:

+ 0 - 12
panda/src/cull/cullBinBackToFront.cxx

@@ -23,18 +23,6 @@
 
 TypeHandle CullBinBackToFront::_type_handle;
 
-/**
- *
- */
-CullBinBackToFront::
-~CullBinBackToFront() {
-  Objects::iterator oi;
-  for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
-    CullableObject *object = (*oi)._object;
-    delete object;
-  }
-}
-
 /**
  * Factory constructor for passing to the CullBinManager.
  */

+ 0 - 1
panda/src/cull/cullBinBackToFront.h

@@ -33,7 +33,6 @@ public:
   INLINE CullBinBackToFront(const std::string &name,
                             GraphicsStateGuardianBase *gsg,
                             const PStatCollector &draw_region_pcollector);
-  virtual ~CullBinBackToFront();
 
   static CullBin *make_bin(const std::string &name,
                            GraphicsStateGuardianBase *gsg,

+ 0 - 12
panda/src/cull/cullBinFixed.cxx

@@ -23,18 +23,6 @@
 
 TypeHandle CullBinFixed::_type_handle;
 
-/**
- *
- */
-CullBinFixed::
-~CullBinFixed() {
-  Objects::iterator oi;
-  for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
-    CullableObject *object = (*oi)._object;
-    delete object;
-  }
-}
-
 /**
  * Factory constructor for passing to the CullBinManager.
  */

+ 0 - 1
panda/src/cull/cullBinFixed.h

@@ -35,7 +35,6 @@ public:
   INLINE CullBinFixed(const std::string &name,
                       GraphicsStateGuardianBase *gsg,
                       const PStatCollector &draw_region_pcollector);
-  virtual ~CullBinFixed();
 
   static CullBin *make_bin(const std::string &name,
                            GraphicsStateGuardianBase *gsg,

+ 0 - 12
panda/src/cull/cullBinFrontToBack.cxx

@@ -23,18 +23,6 @@
 
 TypeHandle CullBinFrontToBack::_type_handle;
 
-/**
- *
- */
-CullBinFrontToBack::
-~CullBinFrontToBack() {
-  Objects::iterator oi;
-  for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
-    CullableObject *object = (*oi)._object;
-    delete object;
-  }
-}
-
 /**
  * Factory constructor for passing to the CullBinManager.
  */

+ 0 - 1
panda/src/cull/cullBinFrontToBack.h

@@ -34,7 +34,6 @@ public:
   INLINE CullBinFrontToBack(const std::string &name,
                             GraphicsStateGuardianBase *gsg,
                             const PStatCollector &draw_region_pcollector);
-  virtual ~CullBinFrontToBack();
 
   static CullBin *make_bin(const std::string &name,
                            GraphicsStateGuardianBase *gsg,

+ 0 - 12
panda/src/cull/cullBinStateSorted.cxx

@@ -22,18 +22,6 @@
 
 TypeHandle CullBinStateSorted::_type_handle;
 
-/**
- *
- */
-CullBinStateSorted::
-~CullBinStateSorted() {
-  Objects::iterator oi;
-  for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
-    CullableObject *object = (*oi)._object;
-    delete object;
-  }
-}
-
 /**
  * Factory constructor for passing to the CullBinManager.
  */

+ 0 - 1
panda/src/cull/cullBinStateSorted.h

@@ -37,7 +37,6 @@ public:
   INLINE CullBinStateSorted(const std::string &name,
                             GraphicsStateGuardianBase *gsg,
                             const PStatCollector &draw_region_pcollector);
-  virtual ~CullBinStateSorted();
 
   static CullBin *make_bin(const std::string &name,
                            GraphicsStateGuardianBase *gsg,

+ 0 - 12
panda/src/cull/cullBinUnsorted.cxx

@@ -19,18 +19,6 @@
 
 TypeHandle CullBinUnsorted::_type_handle;
 
-/**
- *
- */
-CullBinUnsorted::
-~CullBinUnsorted() {
-  Objects::iterator oi;
-  for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
-    CullableObject *object = (*oi);
-    delete object;
-  }
-}
-
 /**
  * Factory constructor for passing to the CullBinManager.
  */

+ 0 - 1
panda/src/cull/cullBinUnsorted.h

@@ -29,7 +29,6 @@ public:
   INLINE CullBinUnsorted(const std::string &name,
                          GraphicsStateGuardianBase *gsg,
                          const PStatCollector &draw_region_pcollector);
-  ~CullBinUnsorted();
 
   static CullBin *make_bin(const std::string &name,
                            GraphicsStateGuardianBase *gsg,

+ 3 - 6
panda/src/cull/drawCullHandler.cxx

@@ -25,17 +25,14 @@
  * This is called as each Geom is discovered by the CullTraverser.
  */
 void DrawCullHandler::
-record_object(CullableObject *object, const CullTraverser *traverser) {
+record_object(CullableObject &&object, const CullTraverser *traverser) {
   // Munge vertices as needed for the GSG's requirements, and the object's
   // current state.
   bool force = !_gsg->get_effective_incomplete_render();
   Thread *current_thread = traverser->get_current_thread();
 
-  if (object->munge_geom(_gsg, _gsg->get_geom_munger(object->_state, current_thread), traverser, force)) {
+  if (object.munge_geom(_gsg, _gsg->get_geom_munger(object._state, current_thread), traverser, force)) {
     // Now we can immediately draw the object.
-    draw(object, _gsg, force, current_thread);
+    draw(&object, _gsg, force, current_thread);
   }
-
-  // Dispense with the object.
-  delete object;
 }

+ 1 - 1
panda/src/cull/drawCullHandler.h

@@ -31,7 +31,7 @@ class EXPCL_PANDA_CULL DrawCullHandler : public CullHandler {
 public:
   INLINE DrawCullHandler(GraphicsStateGuardianBase *gsg);
 
-  virtual void record_object(CullableObject *object,
+  virtual void record_object(CullableObject &&object,
                              const CullTraverser *traverser);
 
 private:

+ 2 - 2
panda/src/grutil/pipeOcclusionCullTraverser.I

@@ -42,8 +42,8 @@ get_occlusion_mask() const {
  *
  */
 INLINE PipeOcclusionCullTraverser::PendingObject::
-PendingObject(CullableObject *object) :
-  _object(object)
+PendingObject(CullableObject &&object) :
+  _object(std::move(object))
 {
 }
 

+ 14 - 28
panda/src/grutil/pipeOcclusionCullTraverser.cxx

@@ -217,28 +217,19 @@ end_traverse() {
   _current_query = nullptr;
   _next_query = nullptr;
 
-  PendingObjects::iterator oi;
-  for (oi = _pending_objects.begin(); oi != _pending_objects.end(); ++oi) {
-    PendingObject &pobj = (*oi);
+  for (PendingObject &pobj :  _pending_objects) {
     if (pobj._query == nullptr) {
       _occlusion_untested_pcollector.add_level(1);
-      _true_cull_handler->record_object(pobj._object, this);
+      _true_cull_handler->record_object(std::move(pobj._object), this);
     } else {
       int num_fragments = pobj._query->get_num_fragments();
       if (num_fragments != 0) {
         _occlusion_passed_pcollector.add_level(1);
-        _true_cull_handler->record_object(pobj._object, this);
+        _true_cull_handler->record_object(std::move(pobj._object), this);
       } else {
         _occlusion_failed_pcollector.add_level(1);
-        delete pobj._object;
       }
     }
-
-    // The CullableObject has by now either been recorded (which will
-    // eventually delete it) or deleted directly.
-#ifndef NDEBUG
-    pobj._object = nullptr;
-#endif  // NDEBUG
   }
   _pending_objects.clear();
   CullTraverser::end_traverse();
@@ -363,9 +354,9 @@ traverse_below(CullTraverserData &data) {
  * the end of the scene.
  */
 void PipeOcclusionCullTraverser::
-record_object(CullableObject *object, const CullTraverser *traverser) {
+record_object(CullableObject &&object, const CullTraverser *traverser) {
   nassertv(traverser == this);
-  PendingObject pobj(object);
+  PendingObject pobj(std::move(object));
 
   Thread *current_thread = get_current_thread();
 
@@ -379,13 +370,13 @@ record_object(CullableObject *object, const CullTraverser *traverser) {
     // ancestor.  Don't perform another one.
     pobj._query = _current_query;
 
-  } else if (object->_geom->get_nested_vertices(current_thread) < min_occlusion_vertices) {
+  } else if (pobj._object._geom->get_nested_vertices(current_thread) < min_occlusion_vertices) {
     // This object is too small to bother testing for occlusions.
 
   } else {
     // Issue an occlusion test for this object.
-    CPT(BoundingVolume) vol = object->_geom->get_bounds(current_thread);
-    CPT(TransformState) net_transform = _inv_cs_world_transform->compose(object->_internal_transform);
+    CPT(BoundingVolume) vol = pobj._object._geom->get_bounds(current_thread);
+    CPT(TransformState) net_transform = _inv_cs_world_transform->compose(pobj._object._internal_transform);
     CPT(TransformState) internal_transform;
     CPT(Geom) geom;
     if (get_volume_viz(vol, geom, net_transform, internal_transform)) {
@@ -394,7 +385,7 @@ record_object(CullableObject *object, const CullTraverser *traverser) {
     }
   }
 
-  _pending_objects.push_back(pobj);
+  _pending_objects.push_back(std::move(pobj));
 }
 
 /**
@@ -608,14 +599,13 @@ perform_occlusion_test(const Geom *geom, const TransformState *net_transform,
 
   gsg->begin_occlusion_query();
 
-  CullableObject *viz =
-    new CullableObject(geom, _solid_test_state, internal_transform);
+  CullableObject viz(geom, _solid_test_state, internal_transform);
 
   static ConfigVariableBool test_occlude("test-occlude", false);
   if (test_occlude) {
-    _true_cull_handler->record_object(viz, _internal_trav);
+    _true_cull_handler->record_object(std::move(viz), _internal_trav);
   } else {
-    _internal_cull_handler->record_object(viz, _internal_trav);
+    _internal_cull_handler->record_object(std::move(viz), _internal_trav);
   }
 
   PT(OcclusionQueryContext) query = gsg->end_occlusion_query();
@@ -654,13 +644,9 @@ show_results(int num_fragments, const Geom *geom,
      TransparencyAttrib::make(TransparencyAttrib::M_alpha),
      ColorAttrib::make_flat(color));
 
-  CullableObject *internal_viz =
-    new CullableObject(geom, state, internal_transform);
-  _internal_cull_handler->record_object(internal_viz, _internal_trav);
+  _internal_cull_handler->record_object(CullableObject(geom, state, internal_transform), _internal_trav);
 
   // Also render the viz in the main scene.
   internal_transform = get_scene()->get_cs_world_transform()->compose(net_transform);
-  CullableObject *main_viz =
-    new CullableObject(geom, state, internal_transform);
-  _true_cull_handler->record_object(main_viz, this);
+  _true_cull_handler->record_object(CullableObject(geom, state, internal_transform), this);
 }

+ 4 - 4
panda/src/grutil/pipeOcclusionCullTraverser.h

@@ -39,7 +39,7 @@ class GraphicsStateGuardian;
  * in grutil instead, for lack of any better ideas.
  */
 class EXPCL_PANDA_GRUTIL PipeOcclusionCullTraverser : public CullTraverser,
-                                               public CullHandler {
+                                                      public CullHandler {
 PUBLISHED:
   explicit PipeOcclusionCullTraverser(GraphicsOutput *host);
   PipeOcclusionCullTraverser(const PipeOcclusionCullTraverser &copy) = delete;
@@ -59,7 +59,7 @@ protected:
   virtual bool is_in_view(CullTraverserData &data);
   virtual void traverse_below(CullTraverserData &data);
 
-  virtual void record_object(CullableObject *object,
+  virtual void record_object(CullableObject &&object,
                              const CullTraverser *traverser);
 
 private:
@@ -110,10 +110,10 @@ private:
 
   class PendingObject {
   public:
-    INLINE PendingObject(CullableObject *object);
+    INLINE PendingObject(CullableObject &&object);
     INLINE ~PendingObject();
 
-    CullableObject *_object;
+    CullableObject _object;
     PT(OcclusionQueryContext) _query;
   };
   typedef pvector<PendingObject> PendingObjects;

+ 2 - 2
panda/src/grutil/shaderTerrainMesh.cxx

@@ -544,8 +544,8 @@ void ShaderTerrainMesh::add_for_draw(CullTraverser *trav, CullTraverserData &dat
   state = state->set_attrib(current_shader_attrib, 10000);
 
   // Emit chunk
-  CullableObject *object = new CullableObject(_chunk_geom, std::move(state), std::move(modelview_transform));
-  trav->get_cull_handler()->record_object(object, trav);
+  trav->get_cull_handler()->record_object(CullableObject(
+    _chunk_geom, std::move(state), std::move(modelview_transform)), trav);
 
   // After rendering, increment the view index
   ++_current_view_index;

+ 8 - 16
panda/src/parametrics/ropeNode.cxx

@@ -329,10 +329,8 @@ render_thread(CullTraverser *trav, CullTraverserData &data,
     state = state->add_attrib(ColorAttrib::make_vertex());
   }
 
-  CullableObject *object =
-    new CullableObject(geom, state,
-                       data.get_internal_transform(trav));
-  trav->get_cull_handler()->record_object(object, trav);
+  trav->get_cull_handler()->record_object(CullableObject(
+    std::move(geom), std::move(state), data.get_internal_transform(trav)), trav);
 }
 
 /**
@@ -375,10 +373,8 @@ render_tape(CullTraverser *trav, CullTraverserData &data,
     state = state->add_attrib(ColorAttrib::make_vertex());
   }
 
-  CullableObject *object =
-    new CullableObject(geom, state,
-                       data.get_internal_transform(trav));
-  trav->get_cull_handler()->record_object(object, trav);
+  trav->get_cull_handler()->record_object(CullableObject(
+    std::move(geom), std::move(state), data.get_internal_transform(trav)), trav);
 }
 
 /**
@@ -428,10 +424,8 @@ render_billboard(CullTraverser *trav, CullTraverserData &data,
     state = state->add_attrib(ColorAttrib::make_vertex());
   }
 
-  CullableObject *object =
-    new CullableObject(geom, state,
-                       data.get_internal_transform(trav));
-  trav->get_cull_handler()->record_object(object, trav);
+  trav->get_cull_handler()->record_object(CullableObject(
+    std::move(geom), std::move(state), data.get_internal_transform(trav)), trav);
 }
 
 /**
@@ -489,10 +483,8 @@ render_tube(CullTraverser *trav, CullTraverserData &data,
     state = state->add_attrib(ColorAttrib::make_vertex());
   }
 
-  CullableObject *object =
-    new CullableObject(geom, state,
-                       data.get_internal_transform(trav));
-  trav->get_cull_handler()->record_object(object, trav);
+  trav->get_cull_handler()->record_object(CullableObject(
+    std::move(geom), std::move(state), data.get_internal_transform(trav)), trav);
 }
 
 /**

+ 2 - 4
panda/src/parametrics/sheetNode.cxx

@@ -319,10 +319,8 @@ render_sheet(CullTraverser *trav, CullTraverserData &data,
     state = state->add_attrib(ColorAttrib::make_vertex());
   }
 
-  CullableObject *object =
-    new CullableObject(geom, state,
-                       data.get_internal_transform(trav));
-  trav->get_cull_handler()->record_object(object, trav);
+  trav->get_cull_handler()->record_object(CullableObject(
+    std::move(geom), std::move(state), data.get_internal_transform(trav)), trav);
 }
 
 /**

+ 3 - 4
panda/src/pgraph/cullHandler.cxx

@@ -40,10 +40,9 @@ CullHandler::
  * expected to delete it later.
  */
 void CullHandler::
-record_object(CullableObject *object, const CullTraverser *traverser) {
-  nout << *object->_geom << " " << *object->_internal_transform << " "
-       << *object->_state << "\n";
-  delete object;
+record_object(CullableObject &&object, const CullTraverser *traverser) {
+  nout << *object._geom << " " << *object._internal_transform << " "
+       << *object._state << "\n";
 }
 
 /**

+ 1 - 1
panda/src/pgraph/cullHandler.h

@@ -30,7 +30,7 @@ public:
   CullHandler();
   virtual ~CullHandler();
 
-  virtual void record_object(CullableObject *object,
+  virtual void record_object(CullableObject &&object,
                              const CullTraverser *traverser);
   virtual void end_traverse();
 

+ 14 - 0
panda/src/pgraph/cullResult.I

@@ -16,6 +16,7 @@
  */
 INLINE CullResult::
 ~CullResult() {
+  delete_page(_page);
 }
 
 /**
@@ -32,6 +33,19 @@ get_bin(int bin_index) {
   return make_new_bin(bin_index);
 }
 
+/**
+ * Allocates memory for a new CullableObject that is associated with this
+ * CullResult.
+ */
+INLINE CullableObject *CullResult::
+alloc_object(CullableObject &&object) {
+  AllocationPage *page = _page;
+  if (page->_size >= page->_capacity) {
+    page = new_page();
+  }
+  return new (page->_memory + sizeof(CullableObject) * (page->_size++)) CullableObject(std::move(object));
+}
+
 /**
  * If the user configured flash-bin-binname, then update the object's state to
  * flash all the geometry in the bin.

+ 65 - 43
panda/src/pgraph/cullResult.cxx

@@ -61,6 +61,8 @@ CullResult(GraphicsStateGuardianBase *gsg,
   _gsg(gsg),
   _draw_region_pcollector(draw_region_pcollector)
 {
+  _page = &_first_page;
+
 #ifdef DO_MEMORY_USAGE
   MemoryUsage::update_type(this, get_class_type());
 #endif
@@ -100,13 +102,13 @@ make_next() const {
  * the owner of the object pointer, and will eventually delete it.
  */
 void CullResult::
-add_object(CullableObject *object, const CullTraverser *traverser) {
+add_object(CullableObject &&object, const CullTraverser *traverser) {
   static const LColor flash_alpha_color(0.92, 0.96, 0.10, 1.0f);
   static const LColor flash_binary_color(0.21f, 0.67f, 0.24, 1.0f);
   static const LColor flash_multisample_color(0.78f, 0.05f, 0.81f, 1.0f);
   static const LColor flash_dual_color(0.92, 0.01f, 0.01f, 1.0f);
 
-  nassertv(object->_draw_callback != nullptr || object->_geom != nullptr);
+  nassertv(object._draw_callback != nullptr || object._geom != nullptr);
 
   bool force = !traverser->get_effective_incomplete_render();
   Thread *current_thread = traverser->get_current_thread();
@@ -114,62 +116,60 @@ add_object(CullableObject *object, const CullTraverser *traverser) {
 
   // This is probably a good time to check for an auto rescale setting.
   const RescaleNormalAttrib *rescale;
-  object->_state->get_attrib_def(rescale);
+  object._state->get_attrib_def(rescale);
   if (rescale->get_mode() == RescaleNormalAttrib::M_auto) {
     RescaleNormalAttrib::Mode mode;
 
-    if (object->_internal_transform->has_identity_scale()) {
+    if (object._internal_transform->has_identity_scale()) {
       mode = RescaleNormalAttrib::M_none;
-    } else if (object->_internal_transform->has_uniform_scale()) {
+    } else if (object._internal_transform->has_uniform_scale()) {
       mode = RescaleNormalAttrib::M_rescale;
     } else {
       mode = RescaleNormalAttrib::M_normalize;
     }
 
-    object->_state = object->_state->compose(get_rescale_normal_state(mode));
+    object._state = object._state->compose(get_rescale_normal_state(mode));
   }
 
   // Check for a special wireframe setting.
   const RenderModeAttrib *rmode;
-  if (object->_state->get_attrib(rmode)) {
+  if (object._state->get_attrib(rmode)) {
     if (rmode->get_mode() == RenderModeAttrib::M_filled_wireframe) {
-      CullableObject *wireframe_part = new CullableObject(*object);
+      CullableObject wireframe_part(object);
       const ShaderAttrib *shader = nullptr;
-      object->_state->get_attrib(shader);
-      wireframe_part->_state = get_wireframe_overlay_state(rmode, shader);
+      object._state->get_attrib(shader);
+      wireframe_part._state = get_wireframe_overlay_state(rmode, shader);
 
-      if (wireframe_part->munge_geom
-          (_gsg, _gsg->get_geom_munger(wireframe_part->_state, current_thread),
+      if (wireframe_part.munge_geom
+          (_gsg, _gsg->get_geom_munger(wireframe_part._state, current_thread),
            traverser, force)) {
         int wireframe_bin_index = bin_manager->find_bin("fixed");
         CullBin *bin = get_bin(wireframe_bin_index);
         nassertv(bin != nullptr);
-        check_flash_bin(wireframe_part->_state, bin_manager, wireframe_bin_index);
-        bin->add_object(wireframe_part, current_thread);
-      } else {
-        delete wireframe_part;
+        check_flash_bin(wireframe_part._state, bin_manager, wireframe_bin_index);
+        bin->add_object(alloc_object(std::move(wireframe_part)), current_thread);
       }
 
-      object->_state = object->_state->compose(get_wireframe_filled_state());
+      object._state = object._state->compose(get_wireframe_filled_state());
     }
   }
 
   // Check to see if there's a special transparency setting.
   const TransparencyAttrib *trans;
-  if (object->_state->get_attrib(trans)) {
+  if (object._state->get_attrib(trans)) {
     switch (trans->get_mode()) {
     case TransparencyAttrib::M_alpha:
     case TransparencyAttrib::M_premultiplied_alpha:
       // M_alpha implies an alpha-write test, so we don't waste time writing
       // 0-valued pixels.
-      object->_state = object->_state->compose(get_alpha_state());
-      check_flash_transparency(object->_state, flash_alpha_color);
+      object._state = object._state->compose(get_alpha_state());
+      check_flash_transparency(object._state, flash_alpha_color);
       break;
 
     case TransparencyAttrib::M_binary:
       // M_binary is implemented by explicitly setting the alpha test.
-      object->_state = object->_state->compose(get_binary_state());
-      check_flash_transparency(object->_state, flash_binary_color);
+      object._state = object._state->compose(get_binary_state());
+      check_flash_transparency(object._state, flash_binary_color);
       break;
 
     case TransparencyAttrib::M_multisample:
@@ -177,14 +177,14 @@ add_object(CullableObject *object, const CullTraverser *traverser) {
       // The multisample modes are implemented using M_binary if the GSG in
       // use doesn't support multisample.
       if (!_gsg->get_supports_multisample()) {
-        object->_state = object->_state->compose(get_binary_state());
+        object._state = object._state->compose(get_binary_state());
       }
-      check_flash_transparency(object->_state, flash_multisample_color);
+      check_flash_transparency(object._state, flash_multisample_color);
       break;
 
     case TransparencyAttrib::M_dual:
 #ifndef NDEBUG
-      check_flash_transparency(object->_state, flash_dual_color);
+      check_flash_transparency(object._state, flash_dual_color);
 #endif
       if (!m_dual) {
         // If m_dual is configured off, it becomes M_alpha.
@@ -198,7 +198,7 @@ add_object(CullableObject *object, const CullTraverser *traverser) {
       // falls back to M_alpha.
       {
         const CullBinAttrib *bin_attrib;
-        if (!object->_state->get_attrib(bin_attrib) ||
+        if (!object._state->get_attrib(bin_attrib) ||
             bin_attrib->get_bin_name().empty()) {
           // We make a copy of the object to draw the transparent part; this
           // gets placed in the transparent bin.
@@ -206,28 +206,25 @@ add_object(CullableObject *object, const CullTraverser *traverser) {
           if (m_dual_transparent)
 #endif
             {
-              CullableObject *transparent_part = new CullableObject(*object);
+              CullableObject transparent_part(object);
               CPT(RenderState) transparent_state = get_dual_transparent_state();
-              transparent_part->_state = object->_state->compose(transparent_state);
-              if (transparent_part->munge_geom
-                  (_gsg, _gsg->get_geom_munger(transparent_part->_state, current_thread),
+              transparent_part._state = object._state->compose(transparent_state);
+              if (transparent_part.munge_geom
+                  (_gsg, _gsg->get_geom_munger(transparent_part._state, current_thread),
                    traverser, force)) {
-                int transparent_bin_index = transparent_part->_state->get_bin_index();
+                int transparent_bin_index = transparent_part._state->get_bin_index();
                 CullBin *bin = get_bin(transparent_bin_index);
                 nassertv(bin != nullptr);
-                check_flash_bin(transparent_part->_state, bin_manager, transparent_bin_index);
-                bin->add_object(transparent_part, current_thread);
-              } else {
-                delete transparent_part;
+                check_flash_bin(transparent_part._state, bin_manager, transparent_bin_index);
+                bin->add_object(alloc_object(std::move(transparent_part)), current_thread);
               }
             }
 
           // Now we can draw the opaque part.  This will end up in the opaque
           // bin.
-          object->_state = object->_state->compose(get_dual_opaque_state());
+          object._state = object._state->compose(get_dual_opaque_state());
 #ifndef NDEBUG
           if (!m_dual_opaque) {
-            delete object;
             return;
           }
 #endif
@@ -242,20 +239,18 @@ add_object(CullableObject *object, const CullTraverser *traverser) {
     }
   }
 
-  int bin_index = object->_state->get_bin_index();
+  int bin_index = object._state->get_bin_index();
   CullBin *bin = get_bin(bin_index);
   nassertv(bin != nullptr);
-  check_flash_bin(object->_state, bin_manager, bin_index);
+  check_flash_bin(object._state, bin_manager, bin_index);
 
   // Munge vertices as needed for the GSG's requirements, and the object's
   // current state.
-  if (object->munge_geom(_gsg, _gsg->get_geom_munger(object->_state, current_thread), traverser, force)) {
+  if (object.munge_geom(_gsg, _gsg->get_geom_munger(object._state, current_thread), traverser, force)) {
     // The object may or may not now be fully resident, but this may not
     // matter, since the GSG may have the necessary buffers already loaded.
     // We'll let the GSG ultimately decide whether to render it.
-    bin->add_object(object, current_thread);
-  } else {
-    delete object;
+    bin->add_object(alloc_object(std::move(object)), current_thread);
   }
 }
 
@@ -371,6 +366,33 @@ make_new_bin(int bin_index) {
   return bin_ptr;
 }
 
+/**
+ * Creates a new AllocationPage replacing the old one.
+ */
+CullResult::AllocationPage *CullResult::
+new_page() {
+  AllocationPage *page = new AllocationPage;
+  page->_next = _page;
+  _page = page;
+  return page;
+}
+
+/**
+ *
+ */
+void CullResult::
+delete_page(AllocationPage *page) {
+  size_t size = std::exchange(page->_size, 0);
+  for (size_t i = 0; i < size; ++i) {
+    ((CullableObject *)page->_memory)[i].~CullableObject();
+  }
+  AllocationPage *next = page->_next;
+  if (next != nullptr) {
+    delete_page(next);
+    delete page;
+  }
+}
+
 /**
  * Returns a RenderState containing the given rescale normal attribute.
  */

+ 17 - 1
panda/src/pgraph/cullResult.h

@@ -45,6 +45,7 @@ class EXPCL_PANDA_PGRAPH CullResult : public ReferenceCount {
 public:
   CullResult(GraphicsStateGuardianBase *gsg,
              const PStatCollector &draw_region_pcollector);
+  CullResult(const CullResult &copy) = delete;
   INLINE ~CullResult();
 
 PUBLISHED:
@@ -52,7 +53,7 @@ PUBLISHED:
 
   INLINE CullBin *get_bin(int bin_index);
 
-  void add_object(CullableObject *object, const CullTraverser *traverser);
+  void add_object(CullableObject &&object, const CullTraverser *traverser);
   void finish_cull(SceneSetup *scene_setup, Thread *current_thread);
   void draw(Thread *current_thread);
 
@@ -64,6 +65,11 @@ public:
 private:
   CullBin *make_new_bin(int bin_index);
 
+  struct AllocationPage;
+  AllocationPage *new_page();
+  void delete_page(AllocationPage *page);
+  INLINE CullableObject *alloc_object(CullableObject &&object);
+
   INLINE void check_flash_bin(CPT(RenderState) &state, CullBinManager *bin_manager, int bin_index);
   INLINE void check_flash_transparency(CPT(RenderState) &state, const LColor &color);
 
@@ -88,6 +94,16 @@ private:
 
   bool _show_transparency = false;
 
+  // Arena allocator for CullableObjects.
+  struct AllocationPage {
+    AllocationPage *_next = nullptr;
+    static const size_t _capacity = 64;
+    size_t _size = 0;
+    alignas(CullableObject) unsigned char _memory[sizeof(CullableObject) * _capacity];
+  };
+  AllocationPage *_page;
+  AllocationPage _first_page;
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 6 - 14
panda/src/pgraph/cullTraverser.cxx

@@ -259,15 +259,8 @@ draw_bounding_volume(const BoundingVolume *vol,
 
   if (bounds_viz != nullptr) {
     _geoms_pcollector.add_level(2);
-    CullableObject *outer_viz =
-      new CullableObject(bounds_viz, get_bounds_outer_viz_state(),
-                         internal_transform);
-    _cull_handler->record_object(outer_viz, this);
-
-    CullableObject *inner_viz =
-      new CullableObject(std::move(bounds_viz), get_bounds_inner_viz_state(),
-                         internal_transform);
-    _cull_handler->record_object(inner_viz, this);
+    _cull_handler->record_object(CullableObject(bounds_viz, get_bounds_outer_viz_state(), internal_transform), this);
+    _cull_handler->record_object(CullableObject(std::move(bounds_viz), get_bounds_inner_viz_state(), internal_transform), this);
   }
 }
 
@@ -309,11 +302,10 @@ show_bounds(CullTraverserData &data, bool tight) {
 
     if (bounds_viz != nullptr) {
       _geoms_pcollector.add_level(1);
-      CullableObject *outer_viz =
-        new CullableObject(std::move(bounds_viz), get_bounds_outer_viz_state(),
-                           internal_transform);
-      outer_viz->_instances = data._instances;
-      _cull_handler->record_object(outer_viz, this);
+      CullableObject outer_viz(std::move(bounds_viz), get_bounds_outer_viz_state(),
+                               internal_transform);
+      outer_viz._instances = data._instances;
+      _cull_handler->record_object(std::move(outer_viz), this);
     }
   } else if (data._instances == nullptr) {
     draw_bounding_volume(node->get_bounds(), internal_transform);

+ 3 - 3
panda/src/pgraph/cullableObject.h

@@ -24,7 +24,6 @@
 #include "geomNode.h"
 #include "cullTraverserData.h"
 #include "pStatCollector.h"
-#include "deletedChain.h"
 #include "graphicsStateGuardianBase.h"
 #include "sceneSetup.h"
 #include "lightMutex.h"
@@ -46,7 +45,10 @@ public:
                         CPT(TransformState) internal_transform);
 
   INLINE CullableObject(const CullableObject &copy);
+  INLINE CullableObject(CullableObject &&from) noexcept = default;
+
   INLINE void operator = (const CullableObject &copy);
+  INLINE CullableObject &operator = (CullableObject &&from) noexcept = default;
 
   bool munge_geom(GraphicsStateGuardianBase *gsg, GeomMunger *munger,
                   const CullTraverser *traverser, bool force);
@@ -64,8 +66,6 @@ public:
                             bool force, Thread *current_thread);
 
 public:
-  ALLOC_DELETED_CHAIN(CullableObject);
-
   void output(std::ostream &out) const;
 
 public:

+ 8 - 11
panda/src/pgraph/geomNode.cxx

@@ -538,10 +538,9 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
     if (!geom->is_empty()) {
       CPT(RenderState) state = data._state->compose(geoms.get_geom_state(0));
       if (!state->has_cull_callback() || state->cull_callback(trav, data)) {
-        CullableObject *object =
-          new CullableObject(std::move(geom), std::move(state), std::move(internal_transform));
-        object->_instances = data._instances;
-        trav->get_cull_handler()->record_object(object, trav);
+        CullableObject object(std::move(geom), std::move(state), std::move(internal_transform));
+        object._instances = data._instances;
+        trav->get_cull_handler()->record_object(std::move(object), trav);
       }
     }
   }
@@ -562,10 +561,9 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
       if (data._instances != nullptr) {
         // Draw each individual instance.  We don't bother culling each
         // individual Geom for each instance; that is probably way too slow.
-        CullableObject *object =
-          new CullableObject(std::move(geom), std::move(state), internal_transform);
-        object->_instances = data._instances;
-        trav->get_cull_handler()->record_object(object, trav);
+        CullableObject object(std::move(geom), std::move(state), internal_transform);
+        object._instances = data._instances;
+        trav->get_cull_handler()->record_object(std::move(object), trav);
         continue;
       }
 
@@ -588,9 +586,8 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
         }
       }
 
-      CullableObject *object =
-        new CullableObject(std::move(geom), std::move(state), internal_transform);
-      trav->get_cull_handler()->record_object(object, trav);
+      trav->get_cull_handler()->record_object(CullableObject(
+        std::move(geom), std::move(state), internal_transform), trav);
     }
   }
 }

+ 6 - 8
panda/src/pgraph/occluderNode.cxx

@@ -141,17 +141,15 @@ bool OccluderNode::
 cull_callback(CullTraverser *trav, CullTraverserData &data) {
   // Normally, an OccluderNode is invisible.  But if someone shows it, we will
   // draw a visualization, a checkerboard-textured polygon.
-  CullableObject *occluder_viz =
-    new CullableObject(get_occluder_viz(trav, data), get_occluder_viz_state(trav, data),
-                       data.get_internal_transform(trav));
-  trav->get_cull_handler()->record_object(occluder_viz, trav);
+  trav->get_cull_handler()->record_object(CullableObject(
+    get_occluder_viz(trav, data), get_occluder_viz_state(trav, data),
+    data.get_internal_transform(trav)), trav);
 
   // Also get the frame.
   nassertr(_frame_viz != nullptr, false);
-  CullableObject *frame_viz =
-    new CullableObject(_frame_viz, get_frame_viz_state(trav, data),
-                       data.get_internal_transform(trav));
-  trav->get_cull_handler()->record_object(frame_viz, trav);
+  trav->get_cull_handler()->record_object(CullableObject(
+    _frame_viz, get_frame_viz_state(trav, data),
+    data.get_internal_transform(trav)), trav);
 
   // Now carry on to render our child nodes.
   return true;

+ 2 - 4
panda/src/pgraph/planeNode.cxx

@@ -140,10 +140,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
   // Normally, a PlaneNode is invisible.  But if someone shows it, we will
   // draw a visualization, a nice yellow wireframe.
 
-  CullableObject *plane_viz =
-    new CullableObject(get_viz(trav, data), data._state,
-                       data.get_internal_transform(trav));
-  trav->get_cull_handler()->record_object(plane_viz, trav);
+  trav->get_cull_handler()->record_object(CullableObject(
+    get_viz(trav, data), data._state, data.get_internal_transform(trav)), trav);
 
   // Now carry on to render our child nodes.
   return true;

+ 4 - 5
panda/src/pgraphnodes/callbackNode.cxx

@@ -121,11 +121,10 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
   // Geoms, however.
   CallbackObject *cbobj = get_draw_callback();
   if (cbobj != nullptr) {
-    CullableObject *object =
-      new CullableObject(nullptr, data._state,
-                         data.get_internal_transform(trav));
-    object->set_draw_callback(cbobj);
-    trav->get_cull_handler()->record_object(object, trav);
+    CullableObject object(nullptr, data._state,
+                          data.get_internal_transform(trav));
+    object.set_draw_callback(cbobj);
+    trav->get_cull_handler()->record_object(std::move(object), trav);
   }
 }
 

+ 4 - 5
panda/src/pgraphnodes/computeNode.cxx

@@ -82,11 +82,10 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
   // OK, render this node.  Rendering this node means creating a
   // CullableObject for the Dispatcher.  We don't need to pass any Geoms,
   // however.
-  CullableObject *object =
-    new CullableObject(nullptr, data._state,
-                       data.get_internal_transform(trav));
-  object->set_draw_callback(_dispatcher);
-  trav->get_cull_handler()->record_object(object, trav);
+  CullableObject object(nullptr, data._state,
+                        data.get_internal_transform(trav));
+  object.set_draw_callback(_dispatcher);
+  trav->get_cull_handler()->record_object(std::move(object), trav);
 }
 
 /**

+ 4 - 5
panda/src/pgraphnodes/nodeCullCallbackData.cxx

@@ -48,11 +48,10 @@ upcall() {
     // any Geoms, however.
     CallbackObject *cbobj = cbnode->get_draw_callback();
     if (cbobj != nullptr) {
-      CullableObject *object =
-        new CullableObject(nullptr, _data._state,
-                           _data.get_internal_transform(_trav));
-      object->set_draw_callback(cbobj);
-      _trav->get_cull_handler()->record_object(object, _trav);
+      CullableObject object(nullptr, _data._state,
+                            _data.get_internal_transform(_trav));
+      object.set_draw_callback(cbobj);
+      _trav->get_cull_handler()->record_object(std::move(object), _trav);
     }
   }