Panda3D
renderAttrib.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file renderAttrib.cxx
10  * @author drose
11  * @date 2002-02-21
12  */
13 
14 #include "renderAttrib.h"
15 #include "bamReader.h"
16 #include "indent.h"
17 #include "config_pgraph.h"
18 #include "lightReMutexHolder.h"
19 #include "pStatTimer.h"
20 
21 using std::ostream;
22 
23 LightReMutex *RenderAttrib::_attribs_lock = nullptr;
24 RenderAttrib::Attribs *RenderAttrib::_attribs = nullptr;
25 TypeHandle RenderAttrib::_type_handle;
26 
27 size_t RenderAttrib::_garbage_index = 0;
28 
29 PStatCollector RenderAttrib::_garbage_collect_pcollector("*:State Cache:Garbage Collect");
30 
31 /**
32  *
33  */
34 RenderAttrib::
35 RenderAttrib() {
36  if (_attribs == nullptr) {
37  init_attribs();
38  }
39  _saved_entry = -1;
40 }
41 
42 /**
43  * The destructor is responsible for removing the RenderAttrib from the global
44  * set if it is there.
45  */
48  LightReMutexHolder holder(*_attribs_lock);
49 
50  // unref() should have cleared this.
51  nassertv(_saved_entry == -1);
52 }
53 
54 /**
55  * Intended to be overridden by derived RenderAttrib types to specify how two
56  * consecutive RenderAttrib objects of the same type interact.
57  *
58  * This should return false if a RenderAttrib on a higher node will compose
59  * into a RenderAttrib on a lower node that has a higher override value, or
60  * true if the lower RenderAttrib will completely replace the state.
61  *
62  * The default behavior is false: normally, a RenderAttrib in the graph cannot
63  * completely override a RenderAttrib above it, regardless of its override
64  * value--instead, the two attribs are composed. But for some kinds of
65  * RenderAttribs, it is useful to allow this kind of override.
66  *
67  * This method only handles the one special case of a lower RenderAttrib with
68  * a higher override value. If the higher RenderAttrib has a higher override
69  * value, it always completely overrides. And if both RenderAttribs have the
70  * same override value, they are always composed.
71  */
72 bool RenderAttrib::
74  return false;
75 }
76 
77 /**
78  * Should be overridden by derived classes to return true if cull_callback()
79  * has been defined. Otherwise, returns false to indicate cull_callback()
80  * does not need to be called for this node during the cull traversal.
81  */
82 bool RenderAttrib::
84  return false;
85 }
86 
87 /**
88  * If has_cull_callback() returns true, this function will be called during
89  * the cull traversal to perform any additional operations that should be
90  * performed at cull time.
91  *
92  * This is called each time the RenderAttrib is discovered applied to a Geom
93  * in the traversal. It should return true if the Geom is visible, false if
94  * it should be omitted.
95  */
96 bool RenderAttrib::
98  return true;
99 }
100 
101 /**
102  * This method overrides ReferenceCount::unref() to clear the pointer from the
103  * global object pool when its reference count goes to zero.
104  */
105 bool RenderAttrib::
106 unref() const {
107  if (!state_cache || garbage_collect_states) {
108  // If we're not using the cache at all, or if we're relying on garbage
109  // collection, just allow the pointer to unref normally.
110  return ReferenceCount::unref();
111  }
112 
113  // Here is the normal refcounting case, with a normal cache, and without
114  // garbage collection in effect. In this case we will pull the object out
115  // of the cache when its reference count goes to 0.
116 
117  // We always have to grab the lock, since we will definitely need to be
118  // holding it if we happen to drop the reference count to 0. Having to grab
119  // the lock at every call to unref() is a big limiting factor on
120  // parallelization.
121  LightReMutexHolder holder(*_attribs_lock);
122 
123  if (ReferenceCount::unref()) {
124  // The reference count is still nonzero.
125  return true;
126  }
127 
128  // The reference count has just reached zero. Make sure the object is
129  // removed from the global object pool, before anyone else finds it and
130  // tries to ref it.
131  ((RenderAttrib *)this)->release_new();
132 
133  return false;
134 }
135 
136 /**
137  *
138  */
139 void RenderAttrib::
140 output(ostream &out) const {
141  out << get_type();
142 }
143 
144 /**
145  *
146  */
147 void RenderAttrib::
148 write(ostream &out, int indent_level) const {
149  indent(out, indent_level) << *this << "\n";
150 }
151 
152 /**
153  * Returns the total number of unique RenderAttrib objects allocated in the
154  * world. This will go up and down during normal operations.
155  */
156 int RenderAttrib::
158  LightReMutexHolder holder(*_attribs_lock);
159 
160  if (_attribs == nullptr) {
161  return 0;
162  }
163  return _attribs->get_num_entries();
164 }
165 
166 /**
167  * Lists all of the RenderAttribs in the cache to the output stream, one per
168  * line. This can be quite a lot of output if the cache is large, so be
169  * prepared.
170  */
171 void RenderAttrib::
172 list_attribs(ostream &out) {
173  LightReMutexHolder holder(*_attribs_lock);
174 
175  size_t size = _attribs->get_num_entries();
176  out << size << " attribs:\n";
177  for (size_t si = 0; si < size; ++si) {
178  const RenderAttrib *attrib = _attribs->get_key(si);
179  attrib->write(out, 2);
180  }
181 }
182 
183 /**
184  * Performs a garbage-collection cycle. This is called automatically from
185  * RenderState::garbage_collect(); see that method for more information.
186  */
187 int RenderAttrib::
189  if (_attribs == nullptr || !garbage_collect_states) {
190  return 0;
191  }
192  LightReMutexHolder holder(*_attribs_lock);
193 
194  PStatTimer timer(_garbage_collect_pcollector);
195  size_t orig_size = _attribs->get_num_entries();
196 
197 #ifdef _DEBUG
198  nassertr(_attribs->validate(), 0);
199 #endif
200 
201  // How many elements to process this pass?
202  size_t size = orig_size;
203  size_t num_this_pass = std::max(0, int(size * garbage_collect_states_rate));
204  if (num_this_pass <= 0) {
205  return 0;
206  }
207 
208  size_t si = _garbage_index;
209  if (si >= size) {
210  si = 0;
211  }
212 
213  num_this_pass = std::min(num_this_pass, size);
214  size_t stop_at_element = (si + num_this_pass) % size;
215 
216  do {
217  RenderAttrib *attrib = (RenderAttrib *)_attribs->get_key(si);
218  if (attrib->get_ref_count() == 1) {
219  // This attrib has recently been unreffed to 1 (the one we added when
220  // we stored it in the cache). Now it's time to delete it. This is
221  // safe, because we're holding the _attribs_lock, so it's not possible
222  // for some other thread to find the attrib in the cache and ref it
223  // while we're doing this.
224  attrib->release_new();
225  unref_delete(attrib);
226 
227  // When we removed it from the hash map, it swapped the last element
228  // with the one we just removed. So the current index contains one we
229  // still need to visit.
230  --size;
231  --si;
232  if (stop_at_element > 0) {
233  --stop_at_element;
234  }
235  }
236 
237  si = (si + 1) % size;
238  } while (si != stop_at_element);
239  _garbage_index = si;
240 
241  nassertr(_attribs->get_num_entries() == size, 0);
242 
243 #ifdef _DEBUG
244  nassertr(_attribs->validate(), 0);
245 #endif
246 
247  // If we just cleaned up a lot of attribs, see if we can reduce the table in
248  // size. This will help reduce iteration overhead in the future.
249  _attribs->consider_shrink_table();
250 
251  return (int)orig_size - (int)size;
252 }
253 
254 /**
255  * Ensures that the cache is still stored in sorted order. Returns true if
256  * so, false if there is a problem (which implies someone has modified one of
257  * the supposedly-const RenderAttrib objects).
258  */
259 bool RenderAttrib::
261  LightReMutexHolder holder(*_attribs_lock);
262  if (_attribs->is_empty()) {
263  return true;
264  }
265 
266  if (!_attribs->validate()) {
267  pgraph_cat.error()
268  << "RenderAttrib::_attribs cache is invalid!\n";
269 
270  size_t size = _attribs->get_num_entries();
271  for (size_t si = 0; si < size; ++si) {
272  const RenderAttrib *attrib = _attribs->get_key(si);
273  //cerr << si << ": " << attrib << "\n";
274  attrib->write(std::cerr, 2);
275  }
276 
277  return false;
278  }
279 
280  size_t size = _attribs->get_num_entries();
281  size_t si = 0;
282  nassertr(si < size, false);
283  nassertr(_attribs->get_key(si)->get_ref_count() >= 0, false);
284  size_t snext = si;
285  ++snext;
286  while (snext < size) {
287  nassertr(_attribs->get_key(snext)->get_ref_count() >= 0, false);
288  const RenderAttrib *ssi = _attribs->get_key(si);
289  const RenderAttrib *ssnext = _attribs->get_key(snext);
290  int c = ssi->compare_to(*ssnext);
291  int ci = ssnext->compare_to(*ssi);
292  if ((ci < 0) != (c > 0) ||
293  (ci > 0) != (c < 0) ||
294  (ci == 0) != (c == 0)) {
295  pgraph_cat.error()
296  << "RenderAttrib::compare_to() not defined properly!\n";
297  pgraph_cat.error(false)
298  << "(a, b): " << c << "\n";
299  pgraph_cat.error(false)
300  << "(b, a): " << ci << "\n";
301  ssi->write(pgraph_cat.error(false), 2);
302  ssnext->write(pgraph_cat.error(false), 2);
303  return false;
304  }
305  si = snext;
306  ++snext;
307  }
308 
309  return true;
310 }
311 
312 /**
313  * This function is used by derived RenderAttrib types to share a common
314  * RenderAttrib pointer for all equivalent RenderAttrib objects.
315  *
316  * This is different from return_unique() in that it does not actually
317  * guarantee a unique pointer, unless uniquify-attribs is set.
318  */
319 CPT(RenderAttrib) RenderAttrib::
320 return_new(RenderAttrib *attrib) {
321  nassertr(attrib != nullptr, attrib);
322  if (!uniquify_attribs) {
323  attrib->calc_hash();
324  return attrib;
325  }
326 
327  return return_unique(attrib);
328 }
329 
330 /**
331  * This function is used by derived RenderAttrib types to share a common
332  * RenderAttrib pointer for all equivalent RenderAttrib objects.
333  *
334  * The make() function of the derived type should create a new RenderAttrib
335  * and pass it through return_new(), which will either save the pointer and
336  * return it unchanged (if this is the first similar such object) or delete it
337  * and return an equivalent pointer (if there was already a similar object
338  * saved).
339  */
340 CPT(RenderAttrib) RenderAttrib::
341 return_unique(RenderAttrib *attrib) {
342  nassertr(attrib != nullptr, attrib);
343 
344  attrib->calc_hash();
345 
346  if (!state_cache) {
347  return attrib;
348  }
349 
350 #ifndef NDEBUG
351  if (paranoid_const) {
352  nassertr(validate_attribs(), attrib);
353  }
354 #endif
355 
356  LightReMutexHolder holder(*_attribs_lock);
357 
358  if (attrib->_saved_entry != -1) {
359  // This attrib is already in the cache. nassertr(_attribs->find(attrib)
360  // == attrib->_saved_entry, attrib);
361  return attrib;
362  }
363 
364  int si = _attribs->find(attrib);
365  if (si != -1) {
366  // There's an equivalent attrib already in the set. Return it. If this
367  // is a newly created RenderAttrib, though, be sure to delete it.
368  if (attrib->get_ref_count() == 0) {
369  delete attrib;
370  }
371  return _attribs->get_key(si);
372  }
373 
374  // Not already in the set; add it.
375  if (garbage_collect_states) {
376  // If we'll be garbage collecting attribs explicitly, we'll increment the
377  // reference count when we store it in the cache, so that it won't be
378  // deleted while it's in it.
379  attrib->ref();
380  }
381  si = _attribs->store(attrib, nullptr);
382 
383  // Save the index and return the input attrib.
384  attrib->_saved_entry = si;
385  return attrib;
386 }
387 
388 /**
389  * Intended to be overridden by derived RenderAttrib types to return a unique
390  * number indicating whether this RenderAttrib is equivalent to the other one.
391  *
392  * This should return 0 if the two RenderAttrib objects are equivalent, a
393  * number less than zero if this one should be sorted before the other one,
394  * and a number greater than zero otherwise.
395  *
396  * This will only be called with two RenderAttrib objects whose get_type()
397  * functions return the same.
398  */
399 int RenderAttrib::
400 compare_to_impl(const RenderAttrib *other) const {
401  return 0;
402 }
403 
404 /**
405  * Intended to be overridden by derived RenderAttrib types to return a unique
406  * hash for these particular properties. RenderAttribs that compare the same
407  * with compare_to_impl(), above, should return the same hash; RenderAttribs
408  * that compare differently should return a different hash.
409  */
410 size_t RenderAttrib::
411 get_hash_impl() const {
412  return 0;
413 }
414 
415 /**
416  * Intended to be overridden by derived RenderAttrib types to specify how two
417  * consecutive RenderAttrib objects of the same type interact.
418  *
419  * This should return the result of applying the other RenderAttrib to a node
420  * in the scene graph below this RenderAttrib, which was already applied. In
421  * most cases, the result is the same as the other RenderAttrib (that is, a
422  * subsequent RenderAttrib completely replaces the preceding one). On the
423  * other hand, some kinds of RenderAttrib (for instance, ColorTransformAttrib)
424  * might combine in meaningful ways.
425  */
426 CPT(RenderAttrib) RenderAttrib::
427 compose_impl(const RenderAttrib *other) const {
428  return other;
429 }
430 
431 /**
432  * Intended to be overridden by derived RenderAttrib types to specify how two
433  * consecutive RenderAttrib objects of the same type interact.
434  *
435  * See invert_compose() and compose_impl().
436  */
437 CPT(RenderAttrib) RenderAttrib::
438 invert_compose_impl(const RenderAttrib *other) const {
439  return other;
440 }
441 
442 /**
443  * Outputs a string representation of the given PandaCompareFunc object.
444  */
445 void RenderAttrib::
446 output_comparefunc(ostream &out, PandaCompareFunc fn) const {
447  switch (fn) {
448  case M_none:
449  out << "none";
450  break;
451 
452  case M_never:
453  out << "never";
454  break;
455 
456  case M_less:
457  out << "less";
458  break;
459 
460  case M_equal:
461  out << "equal";
462  break;
463 
464  case M_less_equal:
465  out << "less_equal";
466  break;
467 
468  case M_greater:
469  out << "greater";
470  break;
471 
472  case M_not_equal:
473  out << "not_equal";
474  break;
475 
476  case M_greater_equal:
477  out << "greater_equal";
478  break;
479 
480  case M_always:
481  out << "always";
482  break;
483  }
484 }
485 
486 /**
487  * This inverse of return_new, this releases this object from the global
488  * RenderAttrib table.
489  *
490  * You must already be holding _attribs_lock before you call this method.
491  */
492 void RenderAttrib::
493 release_new() {
494  nassertv(_attribs_lock->debug_is_locked());
495 
496  if (_saved_entry != -1) {
497  _saved_entry = -1;
498  nassertv_always(_attribs->remove(this));
499  }
500 }
501 
502 /**
503  * Make sure the global _attribs map is allocated. This only has to be done
504  * once. We could make this map static, but then we run into problems if
505  * anyone creates a RenderAttrib object at static init time; it also seems to
506  * cause problems when the Panda shared library is unloaded at application
507  * exit time.
508  */
509 void RenderAttrib::
511  _attribs = new Attribs;
512 
513  // TODO: we should have a global Panda mutex to allow us to safely create
514  // _attribs_lock without a startup race condition. For the meantime, this
515  // is OK because we guarantee that this method is called at static init
516  // time, presumably when there is still only one thread in the world.
517  _attribs_lock = new LightReMutex("RenderAttrib::_attribs_lock");
518  nassertv(Thread::get_current_thread() == Thread::get_main_thread());
519 }
520 
521 /**
522  * Writes the contents of this object to the datagram for shipping out to a
523  * Bam file.
524  */
525 void RenderAttrib::
527  TypedWritable::write_datagram(manager, dg);
528 }
529 
530 /**
531  * Called immediately after complete_pointers(), this gives the object a
532  * chance to adjust its own pointer if desired. Most objects don't change
533  * pointers after completion, but some need to.
534  *
535  * Once this function has been called, the old pointer will no longer be
536  * accessed.
537  */
539 change_this(TypedWritable *old_ptr, BamReader *manager) {
540  // First, uniquify the pointer.
541  RenderAttrib *attrib = DCAST(RenderAttrib, old_ptr);
542  CPT(RenderAttrib) pointer = return_unique(attrib);
543 
544  // But now we have a problem, since we have to hold the reference count and
545  // there's no way to return a TypedWritable while still holding the
546  // reference count! We work around this by explicitly upping the count, and
547  // also setting a finalize() callback to down it later.
548  if (pointer == attrib) {
549  pointer->ref();
550  manager->register_finalize(attrib);
551  }
552 
553  // We have to cast the pointer back to non-const, because the bam reader
554  // expects that.
555  return (RenderAttrib *)pointer.p();
556 }
557 
558 /**
559  * Called by the BamReader to perform any final actions needed for setting up
560  * the object after all objects have been read and all pointers have been
561  * completed.
562  */
563 void RenderAttrib::
565  // Unref the pointer that we explicitly reffed in change_this().
566  unref();
567 
568  // We should never get back to zero after unreffing our own count, because
569  // we expect to have been stored in a pointer somewhere. If we do get to
570  // zero, it's a memory leak; the way to avoid this is to call unref_delete()
571  // above instead of unref(), but this is dangerous to do from within a
572  // virtual function.
573  nassertv(get_ref_count() != 0);
574 }
575 
576 /**
577  * This internal function is called by make_from_bam to read in all of the
578  * relevant data from the BamFile for the new RenderAttrib.
579  */
580 void RenderAttrib::
581 fillin(DatagramIterator &scan, BamReader *manager) {
582  TypedWritable::fillin(scan, manager);
583  manager->register_change_this(change_this, this);
584 }
static void list_attribs(std::ostream &out)
Lists all of the RenderAttribs in the cache to the output stream, one per line.
get_ref_count
Returns the current reference count.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the base class for a number of render attributes (other than transform) that may be set on sc...
Definition: renderAttrib.h:51
static int get_num_attribs()
Returns the total number of unique RenderAttrib objects allocated in the world.
CPT(RenderAttrib) RenderAttrib
This function is used by derived RenderAttrib types to share a common RenderAttrib pointer for all eq...
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition: bamReader.h:110
virtual bool has_cull_callback() const
Should be overridden by derived classes to return true if cull_callback() has been defined.
virtual bool unref() const final
This method overrides ReferenceCount::unref() to clear the pointer from the global object pool when i...
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
bool debug_is_locked() const
Returns true if the current thread has locked the LightReMutex, false otherwise.
A lightweight reentrant mutex.
Definition: lightReMutex.h:30
This collects together the pieces of data that are accumulated for each node while walking the scene ...
int store(const Key &key, const Value &data)
Records the indicated key/data pair in the map.
static void init_attribs()
Make sure the global _attribs map is allocated.
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition: pStatTimer.h:30
This template class implements an unordered map of keys to data, implemented as a hashtable.
Definition: simpleHashMap.h:81
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition: bamWriter.h:63
static int garbage_collect()
Performs a garbage-collection cycle.
virtual ~RenderAttrib()
The destructor is responsible for removing the RenderAttrib from the global set if it is there.
virtual void fillin(DatagramIterator &scan, BamReader *manager)
This internal function is intended to be called by each class's make_from_bam() method to read in all...
void register_change_this(ChangeThisFunc func, TypedWritable *whom)
Called by an object reading itself from the bam file to indicate that the object pointer that will be...
Definition: bamReader.cxx:835
virtual void write_datagram(BamWriter *manager, Datagram &dg)
Writes the contents of this object to the datagram for shipping out to a Bam file.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
size_t get_num_entries() const
Returns the number of active entries in the table.
A lightweight class that represents a single element that may be timed and/or counted via stats.
virtual void write_datagram(BamWriter *manager, Datagram &dg)
Writes the contents of this object to the datagram for shipping out to a Bam file.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
const Key & get_key(size_t n) const
Returns the key in the nth entry of the table.
void register_finalize(TypedWritable *whom)
Should be called by an object reading itself from the Bam file to indicate that this particular objec...
Definition: bamReader.cxx:808
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool validate() const
Returns true if the internal table appears to be consistent, false if there are some internal errors.
virtual bool cull_callback(CullTraverser *trav, const CullTraverserData &data) const
If has_cull_callback() returns true, this function will be called during the cull traversal to perfor...
Similar to MutexHolder, but for a light reentrant mutex.
void ref() const
Explicitly increments the reference count.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool consider_shrink_table()
Shrinks the table if the allocated storage is significantly larger than the number of elements in it.
static TypedWritable * change_this(TypedWritable *old_ptr, BamReader *manager)
Called immediately after complete_pointers(), this gives the object a chance to adjust its own pointe...
static bool validate_attribs()
Ensures that the cache is still stored in sorted order.
bool is_empty() const
Returns true if the table is empty; i.e.
virtual void finalize(BamReader *manager)
Called by the BamReader to perform any final actions needed for setting up the object after all objec...
A class to retrieve the individual data elements previously stored in a Datagram.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:38
virtual bool lower_attrib_can_override() const
Intended to be overridden by derived RenderAttrib types to specify how two consecutive RenderAttrib o...
void unref_delete(RefCountType *ptr)
This global helper function will unref the given ReferenceCount object, and if the reference count re...
bool remove(const Key &key)
Removes the indicated key and its associated data from the table.
This object performs a depth-first traversal of the scene graph, with optional view-frustum culling,...
Definition: cullTraverser.h:45
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual bool unref() const
Explicitly decrements the reference count.
int find(const Key &key) const
Searches for the indicated key in the table.