1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 /*!
34   \class SoSeparator SoSeparator.h Inventor/nodes/SoSeparator.h
35   \brief The SoSeparator class is a state-preserving group node.
36 
37   \ingroup nodes
38 
39   Subgraphs parented by SoSeparator nodes will not affect the state,
40   as they push and pop the traversal state before and after traversal
41   of its children.
42 
43   SoSeparator nodes also provides options for traversal optimalization
44   through the use of caching.
45 
46   <b>FILE FORMAT/DEFAULTS:</b>
47   \code
48     Separator {
49         renderCaching AUTO
50         boundingBoxCaching AUTO
51         renderCulling AUTO
52         pickCulling AUTO
53     }
54   \endcode
55 
56   \sa SoTransformSeparator
57 */
58 
59 // *************************************************************************
60 
61 #include <Inventor/nodes/SoSeparator.h>
62 
63 #ifdef HAVE_CONFIG_H
64 #include "config.h"
65 #endif // HAVE_CONFIG_H
66 
67 #include <cstdlib> // strtol(), rand()
68 #include <climits> // LONG_MIN, LONG_MAX
69 
70 #include <Inventor/actions/SoCallbackAction.h>
71 #include <Inventor/actions/SoGLRenderAction.h>
72 #include <Inventor/actions/SoGetBoundingBoxAction.h>
73 #include <Inventor/actions/SoGetMatrixAction.h>
74 #include <Inventor/actions/SoHandleEventAction.h>
75 #include <Inventor/actions/SoRayPickAction.h>
76 #include <Inventor/actions/SoSearchAction.h>
77 #include <Inventor/actions/SoAudioRenderAction.h>
78 #include <Inventor/caches/SoBoundingBoxCache.h>
79 #include <Inventor/caches/SoGLCacheList.h>
80 #include <Inventor/elements/SoCacheElement.h>
81 #include <Inventor/elements/SoCullElement.h>
82 #include <Inventor/elements/SoLocalBBoxMatrixElement.h>
83 #include <Inventor/elements/SoSoundElement.h>
84 #include <Inventor/misc/SoChildList.h>
85 #include <Inventor/misc/SoState.h>
86 #include <Inventor/errors/SoDebugError.h>
87 #include <Inventor/system/gl.h>
88 #include <Inventor/C/tidbits.h> // coin_getenv()
89 #include <Inventor/threads/SbStorage.h>
90 
91 #ifdef COIN_THREADSAFE
92 #include <Inventor/threads/SbMutex.h>
93 #endif // COIN_THREADSAFE
94 
95 #include "coindefs.h" // COIN_OBSOLETED()
96 #include "nodes/SoSubNodeP.h"
97 #include "glue/glp.h"
98 #include "rendering/SoGL.h"
99 #include "misc/SoDBP.h"
100 
101 #include <Inventor/annex/Profiler/SoProfiler.h>
102 #include "profiler/SoNodeProfiling.h"
103 
104 // *************************************************************************
105 
106 #if COIN_DEBUG
107 #define GLCACHE_DEBUG 0 // set to 1 to debug caching
108 #endif
109 
110 // *************************************************************************
111 
112 // environment variable
113 static int COIN_RANDOMIZE_RENDER_CACHING = -1;
114 
115 // Maximum number of caches available for allocation for the
116 // rendercaching.
117 int SoSeparator::numrendercaches = 2;
118 
119 // *************************************************************************
120 
121 /*!
122   \enum SoSeparator::CacheEnabled
123 
124   Enumeration of flags for the fields deciding which optimalizations
125   to do in SoSeparator nodes. There are two types of settings
126   available: caching policies or culling policies. See doumentation of
127   fields.
128 */
129 /*!
130   \var SoSeparator::CacheEnabled SoSeparator::OFF
131   No caching.
132 */
133 /*!
134   \var SoSeparator::CacheEnabled SoSeparator::ON
135   Always try to cache state.
136 */
137 /*!
138   \var SoSeparator::CacheEnabled SoSeparator::AUTO
139   Use heuristics to try to figure out the optimal caching policy.
140 */
141 
142 
143 /*!
144   \var SoSFEnum SoSeparator::renderCaching
145 
146   Policy for caching of rendering instructions for faster
147   execution. This will typically use the OpenGL \e displaylist
148   mechanism.
149 
150   Default value is SoSeparator::AUTO.
151 
152   If you know that some parts of your scene will never change,
153   rendering might happen faster if you explicitly set this field to
154   SoSeparator::ON. If you on the other hand know that parts of the
155   scene will change a lot (like for every redraw), it will be
156   beneficial to set this field to SoSeparator::OFF for the top-level
157   separator node of this (sub)graph.
158 
159   Usually the default setting of \c AUTO will handle any scene very
160   well. The advantages that \e can be had from setting
161   SoSeparator::renderCaching to \c ON are:
162 
163   <ul>
164    <li> If you positively know that the geometry under the SoSeparator is
165      static, you get the cache set up right away.
166 
167      Otherwise, the code in Coin will do a bit of testing and decide
168      by some heuristics whether or not to enable it. That will make
169      the rendering be a tiny bit slower right after start-up than with
170      renderCaching set to \c ON.
171 
172      (The slow-down should hardly be noticable, though, so we don't
173      advice application programmers to do this.)
174    </li>
175 
176    <li> For many of the shape nodes that can contain many basic
177      primitives, like e.g. SoFaceSet, SoIndexedFaceSet, SoLineSet, etc
178      etc, there is an internal threshold for how many primitives a
179      node can contain before we don't do caching when
180      SoSeparator::renderCaching is set to \c AUTO.
181 
182      The reason we do this is because OpenGL renderlists can
183      potentially suck up a lot of memory resources on the graphics
184      card.
185 
186      But if you know that it will be advantageous on your particular
187      platform, you can override this by setting
188      SoSeparator::renderCaching equal to \c ON.
189 
190      (We don't advice application programmers to do this either. A
191      better solution in these cases would simply be to get in touch
192      with SIM and describe the platform and the problem, and we could
193      integrate a proper fix into Coin.)
194    </li>
195   </ul>
196 
197   There are good reasons for setting renderCaching to \c OFF, like
198   when you know the geometry will be changing a lot. Still, Coin
199   should work fairly well without even this optimization.  (If
200   renderCaching is \c AUTO over a sub-graph with changing geometry or
201   other cache smashing nodes, the caching heuristics will stop the
202   SoSeparator node from trying to make caches -- at least after a few
203   tries has been made and failed.)
204 
205 
206   The short story about how auto-caching works is as follows:
207 
208   <ul>
209 
210     <li>For vertex-based shapes with fewer than 100 triangles and
211         where the geometry is detected to be fairly static, caching is
212         enabled.</li>
213 
214     <li>For shapes with more than 1000 trangles, it is disabled, to
215         avoid spending too much of the on-board graphics card's memory
216         resources.</li>
217 
218     <li>For shapes with between 100 and 1000 shapes, displaylist
219         caching will be turned on if our heuristics decides that the
220         geometry can be considered static.</li>
221 
222   </ul>
223 
224   The maximum threshold (of 1000) is higher when doing remote
225   rendering (as when rendering from one X11-based system to another).
226 
227   Disabling the displaylist caching takes precedence over enabling, so
228   if you have an SoSeparator with a shape with more than 1000
229   triangles and a shape with fewer than 100 triangles, caching will be
230   disabled for the SoSeparator.
231 
232   It's possible to tune the limits using some environment variables:
233 
234   <ul>
235 
236     <li>\c COIN_AUTOCACHE_LOCAL_MIN can be used to change the
237         enable-caching limit, while \c COIN_AUTOCACHE_LOCAL_MAX
238         controls the disable-caching limit.</li>
239 
240     <li>The corresponding variables for remote rendering are \c
241         COIN_AUTOCACHE_REMOTE_MIN and \c
242         COIN_AUTOCACHE_REMOTE_MAX.</li>
243 
244   </ul>
245 */
246 
247 /*!
248   \var SoSFEnum SoSeparator::boundingBoxCaching
249 
250   Policy for caching bounding box calculations. Default value is
251   SoSeparator::AUTO.
252 
253   See also documentation for SoSeparator::renderCaching.
254 */
255 /*!
256   \var SoSFEnum SoSeparator::renderCulling
257 
258   Policy for doing viewport culling during rendering
259   traversals. Default value is SoSeparator::AUTO.
260 
261   When the render culling is turned off for Coin, it will be left to
262   be done for the underlying immediate mode rendering library. This
263   will often be faster than doing culling from within Coin, so be
264   careful to monitor the change in execution speed if setting this
265   field to SoSeparator::ON.
266 
267   See also documentation for SoSeparator::renderCaching.
268 */
269 /*!
270   \var SoSFEnum SoSeparator::pickCulling
271 
272   Policy for doing viewport culling during pick traversals. Default
273   value is SoSeparator::AUTO.
274 
275   See documentation for SoSeparator::renderCulling.
276 */
277 
278 // *************************************************************************
279 
280 // when doing threadsafe rendering, each thread needs its own
281 // glcachelist
282 typedef struct {
283   SoGLCacheList * glcachelist;
284 } soseparator_storage;
285 
286 static void
soseparator_storage_construct(void * data)287 soseparator_storage_construct(void * data)
288 {
289   soseparator_storage * ptr = (soseparator_storage*) data;
290   ptr->glcachelist = NULL;
291 }
292 
293 static void
soseparator_storage_destruct(void * data)294 soseparator_storage_destruct(void * data)
295 {
296   soseparator_storage * ptr = (soseparator_storage*) data;
297   delete ptr->glcachelist;
298 }
299 
300 // *************************************************************************
301 
302 class SoSeparatorP {
303 public:
SoSeparatorP(void)304   SoSeparatorP(void) {
305     this->glcachestorage =
306       new SbStorage(sizeof(soseparator_storage),
307                     soseparator_storage_construct,
308                     soseparator_storage_destruct);
309     this->pub = NULL;
310   }
~SoSeparatorP()311   ~SoSeparatorP() {
312     delete this->glcachestorage;
313   }
314 
315   SoSeparator * pub;
316 
317   SoBoundingBoxCache * bboxcache;
318   uint32_t bboxcache_usecount;
319   uint32_t bboxcache_destroycount;
320 
321 #ifdef COIN_THREADSAFE
322   // FIXME: a mutex for every SoSeparator instance seems a bit
323   // excessive, especially since MSWindows might have rather strict
324   // limits on the total amount of mutex resources a process (or even
325   // a user) can allocate. so consider making this a class-wide
326   // instance instead.  -mortene.
327   SbMutex mutex;
328 #endif // !COIN_THREADSAFE
329   SbStorage * glcachestorage;
invalidate_gl_cache(void * tls,void *)330   static void invalidate_gl_cache(void * tls, void *) {
331     soseparator_storage * ptr = (soseparator_storage*) tls;
332     if (ptr->glcachelist) {
333       ptr->glcachelist->invalidateAll();
334     }
335   }
336 
337   enum { YES, NO, MAYBE } hassoundchild;
338 
339   SoGLCacheList * getGLCacheList(SbBool createifnull);
340 
invalidateGLCaches(void)341   void invalidateGLCaches(void) {
342     glcachestorage->applyToAll(invalidate_gl_cache, NULL);
343   }
344 
lock(void)345   void lock(void) {
346 #ifdef COIN_THREADSAFE
347     this->mutex.lock();
348 #endif // COIN_THREADSAFE
349   }
unlock(void)350   void unlock(void) {
351 #ifdef COIN_THREADSAFE
352     this->mutex.unlock();
353 #endif // COIN_THREADSAFE
354   }
355 
356   static SbBool doCull(SoSeparatorP * thisp, SoState * state,
357                        SbBool (* cullfunc)(SoState *, const SbBox3f &, const SbBool));
358 };
359 
360 #define PRIVATE(obj) ((obj)->pimpl)
361 #define PUBLIC(obj) ((obj)->pub)
362 
363 // *************************************************************************
364 
365 SoGLCacheList *
getGLCacheList(SbBool createifnull)366 SoSeparatorP::getGLCacheList(SbBool createifnull)
367 {
368   soseparator_storage * ptr =
369     (soseparator_storage*) this->glcachestorage->get();
370   if (createifnull && ptr->glcachelist == NULL) {
371     ptr->glcachelist = new SoGLCacheList(SoSeparator::getNumRenderCaches());
372   }
373   return ptr->glcachelist;
374 }
375 
376 // *************************************************************************
377 
378 SO_NODE_SOURCE(SoSeparator);
379 
380 /*!
381   Default constructor.
382 */
SoSeparator(void)383 SoSeparator::SoSeparator(void)
384 {
385   this->commonConstructor();
386 }
387 
388 /*!
389   Constructor.
390 
391   The argument should be the approximate number of children which is
392   expected to be inserted below this node. The number need not be
393   exact, as it is only used as a hint for better memory resource
394   allocation.
395 */
SoSeparator(const int nchildren)396 SoSeparator::SoSeparator(const int nchildren)
397   : SoGroup(nchildren)
398 {
399   this->commonConstructor();
400 }
401 
402 // private common constructor helper function
403 void
commonConstructor(void)404 SoSeparator::commonConstructor(void)
405 {
406   PRIVATE(this)->pub = this;
407 
408   SO_NODE_INTERNAL_CONSTRUCTOR(SoSeparator);
409 
410   SO_NODE_ADD_FIELD(renderCaching, (SoSeparator::AUTO));
411   SO_NODE_ADD_FIELD(boundingBoxCaching, (SoSeparator::AUTO));
412   SO_NODE_ADD_FIELD(renderCulling, (SoSeparator::AUTO));
413   SO_NODE_ADD_FIELD(pickCulling, (SoSeparator::AUTO));
414 
415   SO_NODE_DEFINE_ENUM_VALUE(CacheEnabled, ON);
416   SO_NODE_DEFINE_ENUM_VALUE(CacheEnabled, OFF);
417   SO_NODE_DEFINE_ENUM_VALUE(CacheEnabled, AUTO);
418 
419   SO_NODE_SET_SF_ENUM_TYPE(renderCaching, CacheEnabled);
420   SO_NODE_SET_SF_ENUM_TYPE(boundingBoxCaching, CacheEnabled);
421   SO_NODE_SET_SF_ENUM_TYPE(renderCulling, CacheEnabled);
422   SO_NODE_SET_SF_ENUM_TYPE(pickCulling, CacheEnabled);
423 
424   static long int maxcaches = -1;
425   if (maxcaches == -1) {
426     maxcaches = -2; // so we don't request the envvar later if it is not set
427     const char * maxcachesstr = coin_getenv("IV_SEPARATOR_MAX_CACHES");
428     if (maxcachesstr) {
429       maxcaches = strtol(maxcachesstr, NULL, 10);
430       if ((maxcaches == LONG_MIN) || (maxcaches == LONG_MAX) || (maxcaches < 0)) {
431         SoDebugError::post("SoSeparator::commonConstructor",
432                            "Environment variable IV_SEPARATOR_MAX_CACHES "
433                            "has invalid setting \"%s\"", maxcachesstr);
434       }
435       else {
436         SoSeparator::setNumRenderCaches(maxcaches);
437       }
438     }
439   }
440 
441   PRIVATE(this)->bboxcache = NULL;
442   PRIVATE(this)->bboxcache_usecount = 0;
443   PRIVATE(this)->bboxcache_destroycount = 0;
444 
445   // This environment variable used for local stability / robustness /
446   // correctness testing of the render caching. If set >= 1,
447   // renderCaching will be set to "ON" with a probability of 0.5 for
448   // every SoSeparator instantiated.
449   if (COIN_RANDOMIZE_RENDER_CACHING < 0) {
450     const char * env = coin_getenv("COIN_RANDOMIZE_RENDER_CACHING");
451     if (env) COIN_RANDOMIZE_RENDER_CACHING = atoi(env);
452     else COIN_RANDOMIZE_RENDER_CACHING = 0;
453   }
454   if (COIN_RANDOMIZE_RENDER_CACHING > 0) {
455     if (rand() > (RAND_MAX/2)) { this->renderCaching = SoSeparator::ON; }
456   }
457 
458   PRIVATE(this)->hassoundchild = SoSeparatorP::MAYBE;
459 }
460 
461 /*!
462   Destructor. Frees resources used to implement caches.
463 */
~SoSeparator()464 SoSeparator::~SoSeparator()
465 {
466   if (PRIVATE(this)->bboxcache) {
467     PRIVATE(this)->bboxcache->unref();
468   }
469 }
470 
471 // Doc from superclass.
472 void
initClass(void)473 SoSeparator::initClass(void)
474 {
475   SO_NODE_INTERNAL_INIT_CLASS(SoSeparator, SO_FROM_INVENTOR_1|SoNode::VRML1);
476 
477   SO_ENABLE(SoGetBoundingBoxAction, SoCacheElement);
478   SO_ENABLE(SoGLRenderAction, SoCacheElement);
479   SoSeparator::numrendercaches = 2;
480 }
481 
482 // Doc from superclass.
483 void
doAction(SoAction * action)484 SoSeparator::doAction(SoAction * action)
485 {
486   action->getState()->push();
487   inherited::doAction(action);
488   action->getState()->pop();
489 }
490 
491 // Doc from superclass.
492 void
getBoundingBox(SoGetBoundingBoxAction * action)493 SoSeparator::getBoundingBox(SoGetBoundingBoxAction * action)
494 {
495   SoState * state = action->getState();
496 
497   SbXfBox3f childrenbbox;
498   SbBool childrencenterset;
499   SbVec3f childrencenter;
500 
501   // FIXME: AUTO is interpreted as ON for the boundingBoxCaching
502   // field, but we should trigger some heuristics based on scene graph
503   // "behavior" in the children subgraphs if the value is set to
504   // AUTO. 19990513 mortene.
505   SbBool iscaching = this->boundingBoxCaching.getValue() != OFF;
506 
507   switch (action->getCurPathCode()) {
508   case SoAction::IN_PATH:
509     // can't cache if we're not traversing all children
510     iscaching = FALSE;
511     break;
512   case SoAction::OFF_PATH:
513     return; // no need to do any more work
514   case SoAction::BELOW_PATH:
515   case SoAction::NO_PATH:
516     // check if this is a normal traversal
517     if (action->isInCameraSpace() || action->isResetPath()) iscaching = FALSE;
518     break;
519   default:
520     iscaching = FALSE;
521     assert(0 && "unknown path code");
522     break;
523   }
524 
525   SbBool validcache = iscaching && PRIVATE(this)->bboxcache && PRIVATE(this)->bboxcache->isValid(state);
526 
527   if (iscaching && validcache) {
528     SoCacheElement::addCacheDependency(state, PRIVATE(this)->bboxcache);
529     PRIVATE(this)->bboxcache_usecount++;
530     childrenbbox = PRIVATE(this)->bboxcache->getBox();
531     childrencenterset = PRIVATE(this)->bboxcache->isCenterSet();
532     childrencenter = PRIVATE(this)->bboxcache->getCenter();
533     if (PRIVATE(this)->bboxcache->hasLinesOrPoints()) {
534       SoBoundingBoxCache::setHasLinesOrPoints(state);
535     }
536   }
537   else {
538     SbXfBox3f abox = action->getXfBoundingBox();
539 
540     SbBool storedinvalid = FALSE;
541 
542     // check if we should disable auto caching
543     if (PRIVATE(this)->bboxcache_destroycount > 10 && this->boundingBoxCaching.getValue() == AUTO) {
544       if (float(PRIVATE(this)->bboxcache_usecount) / float(PRIVATE(this)->bboxcache_destroycount) < 5.0f) {
545         iscaching = FALSE;
546       }
547     }
548 
549     if (iscaching) {
550       storedinvalid = SoCacheElement::setInvalid(FALSE);
551     }
552     state->push();
553 
554     if (iscaching) {
555       // lock before changing the bboxcache pointer so that the notify()
556       // function can be used by another thread.
557       PRIVATE(this)->lock();
558       // if we get here, we know bbox cache is not created or is invalid
559       if (PRIVATE(this)->bboxcache) {
560         PRIVATE(this)->bboxcache_destroycount++;
561         PRIVATE(this)->bboxcache->unref();
562       }
563       PRIVATE(this)->bboxcache = new SoBoundingBoxCache(state);
564       PRIVATE(this)->bboxcache->ref();
565       PRIVATE(this)->unlock();
566       // set active cache to record cache dependencies
567       SoCacheElement::set(state, PRIVATE(this)->bboxcache);
568     }
569 
570     SoLocalBBoxMatrixElement::makeIdentity(state);
571     action->getXfBoundingBox().makeEmpty();
572     inherited::getBoundingBox(action);
573 
574     childrenbbox = action->getXfBoundingBox();
575     childrencenterset = action->isCenterSet();
576     if (childrencenterset) childrencenter = action->getCenter();
577 
578     action->getXfBoundingBox() = abox; // reset action bbox
579 
580     if (iscaching) {
581       PRIVATE(this)->bboxcache->set(childrenbbox, childrencenterset, childrencenter);
582     }
583     state->pop();
584     if (iscaching) SoCacheElement::setInvalid(storedinvalid);
585   }
586 
587   if (!childrenbbox.isEmpty()) {
588     action->extendBy(childrenbbox);
589     if (childrencenterset) {
590       // FIXME: shouldn't this assert() hold up? Investigate. 19990422 mortene.
591 #if 0 // disabled
592       assert(!action->isCenterSet());
593 #else
594       action->resetCenter();
595 #endif
596       action->setCenter(childrencenter, TRUE);
597     }
598   }
599 }
600 
601 // Doc from superclass.
602 void
callback(SoCallbackAction * action)603 SoSeparator::callback(SoCallbackAction * action)
604 {
605   SoState * state = action->getState();
606   state->push();
607   // culling planes should normally not be set, but can be set
608   // manually by the application programmer to optimize callback
609   // action traversal.
610 
611   if (!this->cullTest(state)) {
612     SoGroup::callback(action);
613   }
614   state->pop();
615 }
616 
617 // *************************************************************************
618 
619 // Doc from superclass.
620 void
GLRender(SoGLRenderAction * action)621 SoSeparator::GLRender(SoGLRenderAction * action)
622 {
623   switch (action->getCurPathCode()) {
624   case SoAction::NO_PATH:
625   case SoAction::BELOW_PATH:
626     this->GLRenderBelowPath(action);
627     break;
628   case SoAction::OFF_PATH:
629     // do nothing. Separator will reset state.
630     break;
631   case SoAction::IN_PATH:
632     this->GLRenderInPath(action);
633     break;
634   }
635 }
636 
637 /*!
638   SGI Open Inventor v2.1 obsoleted support for
639   SoGLRenderAction::addMethod().  Instead, GLRender() might be called
640   directly, and to optimize traversal, the SoSeparator node calls
641   GLRenderBelowPath whenever the path code is BELOW_PATH or NO_PATH
642   (path code is guaranteed not to change). To be compatible with SGI's
643   Inventor (and thereby also TGS') we have chosen to follow their
644   implementation in this respect.
645 
646   SoSeparator::GLRenderBelowPath() do not traverse its children using
647   SoChildList::traverse(), but calls GLRenderBelowPath() directly
648   for all its children.
649 */
650 void
GLRenderBelowPath(SoGLRenderAction * action)651 SoSeparator::GLRenderBelowPath(SoGLRenderAction * action)
652 {
653   SoState * state = action->getState();
654   state->push();
655   SbBool didcull = FALSE;
656 
657   SoGLCacheList * createcache = NULL;
658   if ((this->renderCaching.getValue() != OFF) &&
659       (SoSeparator::getNumRenderCaches() > 0)) {
660 
661     // test if bbox is outside view-volume
662     if (!state->isCacheOpen()) {
663       didcull = TRUE;
664       if (this->cullTest(state)) {
665         state->pop();
666         return;
667       }
668     }
669     PRIVATE(this)->lock();
670     SoGLCacheList * glcachelist = PRIVATE(this)->getGLCacheList(TRUE);
671     PRIVATE(this)->unlock();
672     if (glcachelist->call(action)) {
673 #if GLCACHE_DEBUG // debug
674       SoDebugError::postInfo("SoSeparator::GLRenderBelowPath",
675                              "%p executed GL cache", this);
676 #endif // debug
677       state->pop();
678 
679       if (SoProfiler::isEnabled()) {
680         SoProfilerElement * e = SoProfilerElement::get(state);
681         if (e) {
682           e->getProfilingData().setNodeFlag(action->getCurPath(), SbProfilingData::GL_CACHED_FLAG, TRUE);
683         }
684       }
685 
686       return;
687     }
688 
689     if (!SoCacheElement::anyOpen(state)) {
690 #if GLCACHE_DEBUG // debug
691       SoDebugError::postInfo("SoSeparator::GLRenderBelowPath",
692                              "%p creating GL cache", this);
693 #endif // debug
694       createcache = glcachelist;
695     }
696   }
697 
698   if (createcache) {
699     createcache->open(action, this->renderCaching.getValue() == AUTO);
700   }
701 
702   SbBool outsidefrustum =
703     (createcache || state->isCacheOpen() || didcull) ?
704     FALSE : this->cullTest(state);
705   if (createcache || !outsidefrustum) {
706     int n = this->children->getLength();
707     SoNode ** childarray = (n!=0)? reinterpret_cast<SoNode**>(this->children->getArrayPtr()) : NULL;
708     action->pushCurPath();
709     for (int i = 0; i < n && !action->hasTerminated(); i++) {
710       action->popPushCurPath(i, childarray[i]);
711       if (action->abortNow()) {
712         // only cache if we do a full traversal
713         SoCacheElement::invalidate(state);
714         break;
715       }
716 
717       {
718         SoNodeProfiling profiling;
719         profiling.preTraversal(action);
720         childarray[i]->GLRenderBelowPath(action); // traversal call
721         profiling.postTraversal(action);
722       }
723 
724 #if COIN_DEBUG
725       // The GL error test is default disabled for this optimized
726       // path.  If you get a GL error reporting an error in the
727       // Separator node, enable this code by setting the environment
728       // variable COIN_GLERROR_DEBUGGING to "1" to see exactly which
729       // node caused the error.
730       static SbBool chkglerr = sogl_glerror_debugging();
731       if (chkglerr) {
732         cc_string str;
733         cc_string_construct(&str);
734         const unsigned int errs = coin_catch_gl_errors(&str);
735         if (errs > 0) {
736           SoDebugError::post("SoSeparator::GLRenderBelowPath",
737                              "GL error: '%s', nodetype: %s",
738                              cc_string_get_text(&str),
739                              (*this->children)[i]->getTypeId().getName().getString());
740         }
741         cc_string_clean(&str);
742       }
743 #endif // COIN_DEBUG
744     }
745     action->popCurPath();
746   }
747   state->pop();
748   if (createcache) {
749     createcache->close(action);
750   }
751 }
752 
753 // Doc from superclass.
754 void
GLRenderInPath(SoGLRenderAction * action)755 SoSeparator::GLRenderInPath(SoGLRenderAction * action)
756 {
757   int numindices;
758   const int * indices;
759 
760   SoAction::PathCode pathcode = action->getPathCode(numindices, indices);
761 
762   if (pathcode == SoAction::IN_PATH) {
763     SoState * state = action->getState();
764     SoNode ** childarray = (SoNode**) this->children->getArrayPtr();
765     state->push();
766     int childidx = 0;
767     for (int i = 0; i < numindices; i++) {
768       for (; childidx < indices[i] && !action->hasTerminated(); childidx++) {
769         SoNode * offpath = childarray[childidx];
770         if (offpath->affectsState()) {
771           action->pushCurPath(childidx, offpath);
772           if (!action->abortNow()) {
773             SoNodeProfiling profiling;
774             profiling.preTraversal(action);
775             offpath->GLRenderOffPath(action); // traversal call
776             profiling.postTraversal(action);
777           }
778           else {
779             SoCacheElement::invalidate(state);
780           }
781           action->popCurPath(pathcode);
782         }
783       }
784       SoNode * inpath = childarray[childidx];
785       action->pushCurPath(childidx, inpath);
786       if (!action->abortNow()) {
787         SoNodeProfiling profiling;
788         profiling.preTraversal(action);
789         inpath->GLRenderInPath(action); // traversal call
790         profiling.postTraversal(action);
791       }
792       else {
793         SoCacheElement::invalidate(state);
794       }
795       action->popCurPath(pathcode);
796       childidx++;
797     }
798     state->pop();
799   }
800   else if (pathcode == SoAction::BELOW_PATH) {
801     this->GLRenderBelowPath(action);
802   }
803 }
804 
805 // Doc from superclass.
806 void
GLRenderOffPath(SoGLRenderAction *)807 SoSeparator::GLRenderOffPath(SoGLRenderAction *)
808 {
809   // do nothing, since all state changes will be reset by the separator
810 }
811 
812 // Doc from superclass.
813 void
handleEvent(SoHandleEventAction * action)814 SoSeparator::handleEvent(SoHandleEventAction * action)
815 {
816   SoSeparator::doAction(action);
817 }
818 
819 // Doc from superclass.
820 void
audioRender(SoAudioRenderAction * action)821 SoSeparator::audioRender(SoAudioRenderAction * action)
822 {
823   // Note: This function is similar to SoVRMLGroup::audioRender().
824   /* FIXME: how should we handle termination of an action? We should
825      probably reset PRIVATE(this)->hassoundchild to MAYBE. Investigate
826      2003-01-31 thammer. */
827 
828   int numindices;
829   const int * indices;
830   SoState * state = action->getState();
831   if (PRIVATE(this)->hassoundchild != SoSeparatorP::NO) {
832     if (action->getPathCode(numindices, indices) != SoAction::IN_PATH) {
833       action->getState()->push();
834       SoSoundElement::setSceneGraphHasSoundNode(state, this, FALSE);
835       inherited::doAction(action);
836       PRIVATE(this)->hassoundchild = SoSoundElement::sceneGraphHasSoundNode(state) ?
837         SoSeparatorP::YES : SoSeparatorP::NO;
838       action->getState()->pop();
839     } else {
840       SoSeparator::doAction((SoAction*)action);
841     }
842   }
843 }
844 
845 // compute object space ray and test for intersection
846 static SbBool
ray_intersect(SoRayPickAction * action,const SbBox3f & box)847 ray_intersect(SoRayPickAction * action, const SbBox3f &box)
848 {
849   if (box.isEmpty()) return FALSE;
850   action->setObjectSpace();
851   return action->intersect(box, TRUE);
852 }
853 
854 // Doc from superclass.
855 void
rayPick(SoRayPickAction * action)856 SoSeparator::rayPick(SoRayPickAction * action)
857 {
858   if (this->pickCulling.getValue() == OFF ||
859       !PRIVATE(this)->bboxcache || !PRIVATE(this)->bboxcache->isValid(action->getState()) ||
860       !action->hasWorldSpaceRay() ||
861       ray_intersect(action, PRIVATE(this)->bboxcache->getProjectedBox())) {
862     SoSeparator::doAction(action);
863   }
864 }
865 
866 // Doc from superclass.
867 void
search(SoSearchAction * action)868 SoSeparator::search(SoSearchAction * action)
869 {
870   // Include this node in the search. (_Don't_ use the
871   // SoGroup::search(), as it will also traverse all children.)
872   SoNode::search(action);
873 
874   if (action->isFound()) return;
875 
876   SoSeparator::doAction(action);
877 }
878 
879 // Doc from superclass.
880 void
getMatrix(SoGetMatrixAction * action)881 SoSeparator::getMatrix(SoGetMatrixAction * action)
882 {
883   int numindices;
884   const int * indices;
885   if (action->getPathCode(numindices, indices) == SoAction::IN_PATH) {
886     // need to push/pop to handle SoUnitsElement correctly
887     action->getState()->push();
888     this->children->traverseInPath(action, numindices, indices);
889     action->getState()->pop();
890   }
891 }
892 
893 /*!
894   Set the maximum number of caches that SoSeparator nodes may allocate
895   for render caching.
896 
897   This is a global value which will be used for all SoSeparator nodes,
898   but the value indicate the maximum number \e per SoSeparator node.
899 
900   More caches might give better performance, but will use more memory.
901   The built-in default value is 2.
902 
903   The value can also be changed globally by setting the host system's
904   environment variable IV_SEPARATOR_MAX_CACHES to the wanted
905   number. This is primarily meant as an aid during debugging, to make
906   it easy to turn off rendercaching completely (by setting
907   "IV_SEPARATOR_MAX_CACHES=0") without having to change any
908   application code.
909 */
910 void
setNumRenderCaches(const int howmany)911 SoSeparator::setNumRenderCaches(const int howmany)
912 {
913   SoSeparator::numrendercaches = howmany;
914 }
915 
916 /*!
917   Returns maximum number of caches SoSeparator nodes are allowed to
918   use for render caching.
919 
920   \sa setNumRenderCaches()
921 */
922 int
getNumRenderCaches(void)923 SoSeparator::getNumRenderCaches(void)
924 {
925   return SoSeparator::numrendercaches;
926 }
927 
928 // Doc from superclass.
929 SbBool
affectsState(void) const930 SoSeparator::affectsState(void) const
931 {
932   // Subgraphs parented by SoSeparator nodes will not affect the
933   // state, as they push and pop the traversal state before and after
934   // traversal of its children.
935   return FALSE;
936 }
937 
938 // Doc from superclass.
939 void
getPrimitiveCount(SoGetPrimitiveCountAction * action)940 SoSeparator::getPrimitiveCount(SoGetPrimitiveCountAction * action)
941 {
942   SoSeparator::doAction((SoAction *)action);
943 }
944 
945 // Doc from superclass.
946 void
notify(SoNotList * nl)947 SoSeparator::notify(SoNotList * nl)
948 {
949   inherited::notify(nl);
950 
951   // lock before using the cache pointers so that we know the pointers
952   // are valid while reading them
953   PRIVATE(this)->lock();
954   if (PRIVATE(this)->bboxcache) PRIVATE(this)->bboxcache->invalidate();
955   PRIVATE(this)->invalidateGLCaches();
956   PRIVATE(this)->hassoundchild = SoSeparatorP::MAYBE;
957   PRIVATE(this)->unlock();
958 }
959 
960 /*!
961   This is an internal Open Inventor method. We've implemented
962   view frustum culling in a different manner. Let us know if
963   you need this function, and we'll consider implementing it.
964 */
965 SbBool
cullTest(SoGLRenderAction * COIN_UNUSED_ARG (action),int & COIN_UNUSED_ARG (cullresults))966 SoSeparator::cullTest(SoGLRenderAction * COIN_UNUSED_ARG(action), int & COIN_UNUSED_ARG(cullresults))
967 {
968   COIN_OBSOLETED();
969   return FALSE;
970 }
971 
972 // Doc from superclass.
973 SbBool
readInstance(SoInput * in,unsigned short flags)974 SoSeparator::readInstance(SoInput * in, unsigned short flags)
975 {
976   return inherited::readInstance(in, flags);
977 }
978 
979 // *************************************************************************
980 
981 SbBool
doCull(SoSeparatorP * thisp,SoState * state,SbBool (* cullfunc)(SoState *,const SbBox3f &,const SbBool))982 SoSeparatorP::doCull(SoSeparatorP * thisp, SoState * state,
983                      SbBool (* cullfunc)(SoState *, const SbBox3f &, const SbBool))
984 {
985   if (PUBLIC(thisp)->renderCulling.getValue() == SoSeparator::OFF) return FALSE;
986   if (SoCullElement::completelyInside(state)) return FALSE;
987 
988   SbBool outside = FALSE;
989   if (thisp->bboxcache &&
990       thisp->bboxcache->isValid(state)) {
991     const SbBox3f & bbox = thisp->bboxcache->getProjectedBox();
992     if (!bbox.isEmpty()) {
993       outside = (*cullfunc)(state, bbox, TRUE);
994     }
995   }
996 
997 #if 0
998 // temporarily disabled. setNodeFlag() needs current path, which is
999 // unavailable here
1000   if (outside && SoProfiler::isEnabled()) {
1001     SoProfilerElement * elt = SoProfilerElement::get(state);
1002     if (elt) {
1003       // FIXME: need current path to set this flag. move outside cullTest()?
1004       // elt->getProfilingData().setNodeFlag(PUBLIC(thisp)->getCurPath(), SbProfilingData::CULLED_FLAG, TRUE);
1005     }
1006   }
1007 #endif
1008 
1009   return outside;
1010 }
1011 
1012 /*!
1013   Internal method which do view frustum culling. For now, view frustum
1014   culling is performed if the renderCulling field is \c AUTO or \c ON,
1015   and the bounding box cache is valid.
1016 
1017   Returns \c TRUE if this separator is outside view frustum, \c FALSE
1018   if inside.
1019 */
1020 SbBool
cullTest(SoState * state)1021 SoSeparator::cullTest(SoState * state)
1022 {
1023   return SoSeparatorP::doCull(&PRIVATE(this).get(), state, SoCullElement::cullBox);
1024 }
1025 
1026 //
1027 //  Performs a cull test on this node. This call will not update
1028 //  the SoCullElement, so it can be called without calling
1029 //  state->push() first.
1030 //
1031 SbBool
cullTestNoPush(SoState * state)1032 SoSeparator::cullTestNoPush(SoState * state)
1033 {
1034   return SoSeparatorP::doCull(&PRIVATE(this).get(), state, SoCullElement::cullTest);
1035 }
1036 
1037 // *************************************************************************
1038 
1039 #undef PRIVATE
1040 #undef PUBLIC
1041 #undef GLCACHE_DEBUG
1042