Good segment organization is critical, and is a primary differentiator of good HOOPS Visualize applications from bad ones. The most common cause of rendering performance problems is poor HOOPS/3dGS database structure.
HOOPS Visualize segments primarily organize attributes, rather than geometry. It is a common mistake for programmers to structure their segments to mimic the organization of the geometry in a scene. Although this structure works, it can slow rendering and can even make your program more difficult to write.
For example, imagine that you are drawing a checkerboard with white and black squares (ignore for a moment that the best way to draw a checkerboard would be with a mesh). The squares cannot all be in a single segment, because they do not all share the same color attribute value. One way to organize the database is to put each square of the board in its own segment, but that requires 64 different segments. A more efficient organization is to use two segments, and to put all the black squares in one segment, and all the white squares in the other. This structure uses fewer segments, is faster to render, and can make it easier to change attributes (for example, to change all the white squares to red).
The best way to design a HOOPS Visualize database is to structure it based on like attributes. This helps maintain attribute coherence and minimizes 'context-switching', which refers to resetting attributes on the underlying device driver. Organizing the database is a two-step process. The first step is to put into the same segment all geometry that has the same color, transformation, and other attributes. Some attributes, such as transformations, will naturally tend to follow the organization of the geometry in a scene; if they do, then your segment organization might follow the organization of your geometry, but it will not be dictated by that organization. In addition, if you have a group of geometries that will be deleted together, you can put them in their own segment to make deleting them easier.
The second step is to build an inheritance hierarchy for segments with similar attribute settings. For example, if, in the resulting database, there are two segments with nearly identical attribute values, then you should give them a common parent segment and should set the common attributes in the parent. Notice that organizing an attribute hierarchy is just like designing a good class hierarchy in C++. Continue until your attributes are well organized - the goal is to structure your database so that you can change any attribute that affects a group of geometry by changing it in only one place. For example, if a group of objects will normally move together, give the group a common parent so that you can change the transformation in one place. If two attributes conflict on how they want the database to be organized, consider putting one of the attributes into a style segment.
In some cases, it may be challenging to create an efficient segment tree due to the complexities of attribute matching/hashing, or a pre-existing HOOPS scene graph created elsewhere may have been read in. Alternately, your scene graph may have been reasonably organized but was not as optimal as it could have been, because of a desire to facilitate object selection, editing, or to meet other unrelated application requirements. While we always encourage developers to follow the guidelines above to create a reasonable graphics organization of the data, the ::Optimize_Segment_Tree method provides support for automatic scene graph optimization. It provides a wide variety of sub options to remove modelling matrices, merge items based their attributes, merge shells, and more. Depending on the original scene graph organization and the selected options, substantial performance gains are possible after optimization.
To create a more efficient segment tree, ::Optimize_Segment_Tree will likely alter the structure and organization of your scene graph significantly. For example, if the "merge shells" option has been set, then individual shells may have migrated to regions within a larger shell. If the "instance shells" option is on, ::Optimize_Segment_Tree will take any shells which are identical except for a translation and delete all but one copy from the segment hierarchy and replace the deleted ones with a geometry reference and a modelling matrix. As a result of these changes, existing functionality such as selection/query that relied on the original segment/geometry keys might not be valid anymore. HOOPS/3DGS provides the function ::Show_Optimized_Mapping to address this issue by allowing you to map a piece of geometry between an old segment tree and a new segment tree (or vice-versa). Another consequence of the collapse and/or removal of segments during the optimization process is the loss of information in user options. If you wish to keep segments with user options in tact, set the "preserver user options" option to on. Note, when this option is on, the effectiveness of ::Optimize_Segment_Tree is significantly reduced especially for models with a large number of user options. By default, "preserve user options" is off.
In some applications, it may be necessary to preserve the model data in its original form. For these situations, using ::Optimize_Segment_Tree is not a realistic option. HOOPS Visualize provides another way to optimize rendering via the static model option in Set_Heuristics. Instead of replacing that segment and its children with an optimized tree, this option instructs Visualize to create a parallel optimized segment tree in another part of the database. At render time, Visualize uses the optimized segment tree instead of the original data. When the static model option is used in conjunction with segment level display lists, it can improve rendering performance significantly.
After making a change that invalidates the static model, there are several options for updating the window and rendering the scene. For optimal performance, the window should be updated using Update_Display or Update_One_Display. Using those update functions, the static model will not be regenerated on the first update immediately following any scene changes that have invalidated the static model subtree. Instead, the static model will be regenerated on the second update as long as no further changes occurred between the two updates which would have invalidated the static model subtree. In other words, HOOPS waits until it is reasonably sure that no more breaking changes will occur; this is a performance optimization to avoid generating a static model unnecessarily. Alternatively, if you'd like to force a static model update, you can call Control_Update with the compile only or redraw everything option.
Deleting, editing and/or creating segments, geometry, or attributes in the portion of the scene graph that is affected by the static model setting will generally result in a regeneration of the 'internal' segment tree associated with that portion of the scene graph, and the regeneration process can take up significant computing time for large models. You can make any number of static breaking changes prior to an update, and you will only have to regenerate the static model once for all the changes. For good performance, it is desirable to avoid such regeneration where possible. Below are the operations that do not invalidate the static model.
- Highlighting: Designed specifically to cooperate well with the static model, highlighting is by far the best way to make temporary attribute changes to the scene.
Certain scene edits when the model type heuristic is set to 'default':
- Inserting Geometry
- Creating an empty Segment
- Deleting Geometry
- Deleting Segments
- Deleting Includes
- Moving geometry into a subsegment of its original segment
Setting certain segment-level attributes:
- Color (except when there are geometry references with color)
- Face Pattern
- Edge Pattern or Weight
- Line Pattern or Weight
- Marker Symbol or Size
- Text Font, Alignment, Path or Spacing
- Clip Region
- Setting geometry-level attributes
For the edits listed in #2 above, the static model is not fully regenerated. Instead, it is patched. Each patch will make the internal static model segment tree slightly less optimal than the original. Each of these patches by itself will have a negligible impact on performance, but the cumulative impact of many patches may cause performance to decline. If the number of edits cannot be reduced, and a performance decline begins to be noticeable, you can force regeneration of the static model (creating a fresh, fully optimized segment tree) with a compile only or redraw everything Control_Update.
Example workflow: There are three pieces of geometry in a segment where the color is set to blue, and you want to make just one piece of geometry green. You can create a new subsegment, move the geometry to that new subsegment, and then set the color to green. This edit workflow will not cause a static model regeneration.
Static model can be enabled for just the parts of your scene graph that you know will remain relatively unchanged. The more of your scene graph that is using static model, the better the performance benefit will be. So it is best to have static model enabled model-wide (or as much as possible), and then turned off for the portions of your scene that will be subject to changes.
However, static model does not have to be enabled at all times. If you know your model will be subject to a large number of edits, you could disable static model temporarily, and then re-enable it when the end user exits "edit mode".
If you have many logical conditions in your model tree which exclude large parts of the model, consider taking advantage of the static model option "condition analysis", for example, Set_Heuristics("static model=(on, condition analysis=view independent)"). This call will cause the static tree algorithm to completely exclude those branches during its calculations.
We recommend that you try a number of representative models from your data set along with common usage patterns to determine if using the static model option will benefit your application.
To see information about static tree regenerations that may have occurred during an update, call Show_Device_Info with a query parameter such as "statics created" or "static time".
It is much more efficient to refer to the HOOPS/3dGS segments by key (using, for example, ::Open_Segment_By_Key) than to do so by name. This is discussed in Section 18.104.22.168. Accessing a single segment or just a few segments by name after an operation may not seem very costly, but performing numerous segment accesses by name within a loop (while building up the database after loading a model, for example) will be extremely costly.
There are a few commands that take a segment name, and that cannot be used with a key. In these few cases, you have two alternatives: you can open the segment using ::Open_Segment_By_Key and then refer to the current segment as ".", or you can manufacture a segment name from the key. To manufacture a segment name from a key, you create a string of the form "@hex-value" - an at sign followed by the ASCII representation of the hexadecimal value of the key.
::Update_Display_Timed, a variant of the HOOPS/3dGS function ::Update_Display, instructs HOOPS to perform a 'time-bounded' update. In this case, HOOPS/3dGS will redraw as much of the scene possible within the specified amount of time.
This function is especially useful when you want to maintain a constant frame rate. Used in conjunction with the culling heuristic, this is a powerful way to control the responsiveness of your application while still maintaining the visual integrity of your scene. When the culling heuristic is enabled, you can determine what objects in the scene will be rendered within the time frame given. To learn more about how to set proper values for the culling heuristic, please see the programming guide section on culling.
Note that ::Update_Display_Timed has different behavior than ::Update_Display especially for shadow map generation. During a call to ::Update_Display_Timed, if a new light is introduced and the shadow map rendering option is enabled, HOOPS will generate the shadow map regardless of the time. In subsequent calls to ::Update_Display_Timed, shadow maps will not be regenerated. If your scene has camera relative lights and/or the shadow map supoption 'view dependent' is enabled, this will affect your scene visually as no "new" shadow maps will be generated. To force regeneration of shadow maps, you must call ::Update_Display.
HOOPS/3dGS is not thread safe by default. This means that HOOPS/3dGS functions cannot safely be called from different threads. Furthermore, ::Update_Display is a blocking function. The user must wait for it to finish before further editing the database, or performing additional updates of other drivers.
However, you have the option to make HOOPS/3dGS thread-safe and use it in a multi-threaded application by performing the following:
The multi-threading System Option must be enabled with a called to ::Define_System_Options. Now, HOOPS/3dGS is completely thread-safe. This allows different threads to call any HOOPS/3dGS functions at any time. Its biggest benefit is that shared parts of the segment tree can be modified by any thread at any time.
Depending on the function call, HOOPS/3dGS may set read or read/write locks for the entire segment tree.
All driver instances should have the isolated Driver Option enabled via ::Set_Driver_Options. From then on, you should call ::Update_One_Display for each driver instance instead of ::Update_Display.
When ::Update_Display is called, HOOPS traverses the entire segment tree and updates all driver instances, while ::Update_One_Display instructs HOOPS/3dGS to perform an update on a single driver instance. Setting the isolated option on a driver instance removes the driver instance from the full list of drivers that gets updated in a call to ::Update_Display. This is important, because if we've already commenced the update process for a hardcopy driver on our separate thread by calling ::Update_One_Display, we don't want to re-update after calling ::Update_Display to perform our display update. As a result, this option should be set before any updates are called in your system.