The performance of a Visualize application depends on a number of factors. Obviously, scenes with less geometry will generally perform faster than those with more. The available hardware, including GPU and memory, are also frequent bottlenecks. While it can be difficult to anticipate all the configurations that your application will run on, there are steps you can take to ensure that Visualize runs as well as possible.
Good segment organization is critical, and the most common cause of rendering performance problems is an inefficient scene graph structure. The segment tree is designed to group graphical primitives based on similar graphical attributes. It is a common mistake for programmers to structure their segments to directly match the structure/hierarchy of their application data.
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).
Organizing the scene-graph based on graphical attributes helps maintain attribute coherence and minimizes context-switching, which refers to a need for Visualize to reset attributes in 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.
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 similar to 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 summary, the scene graph should be organized to group geometry with like attributes together, and utilize as few segments as possible.
A display list is a set of pre-compiled drawing commands and data that are stored in GPU memory. Use of display lists for retained geometry that is being traversed during each update is key to maximum GPU throughput. Display lists usage is controlled via the HPS::PerformanceControl.
Visualize supports geometry-level and segment-level display lists. Generally, segment-level display lists are the best choice as all geometry and attributes within a segment are grouped and can potentially be defined in a single list. However, if a segment contains a large number of primitives and you are editing or moving some of them frequently, segment-level display lists may not be a good choice as changes will invalidate the list, and the entire list will need to be rebuilt after each edit. There is some amount of overhead associated with compiling the list.
If you are doing many edits in a short amount of time, a reasonable approach would be to move the shells into another segment, and then perform the edits on the moved shells. The new segment should not have the display-list setting at all, since you are changing the geometry every update and thus display lists would provide no benefit. There will be a small hit due to moving the shells out, thus causing the segment-level display list to get invalidated and rebuilt. But the continual editing operation will be much faster with this plan.
Geometry-level display lists will group geometry by type into more numerous lists. This may be a better choice if your scene makes periodic small edits to geometry. When this type of display list is invalidated, only that single, smaller list will need to be rebuilt.
Display lists utilize GPU memory. To avoid low GPU memory conditions, Visualize automatically monitors the amount of video memory it uses and only creates display lists as long as video memory is available.
Segment-level display lists are enabled by default. If you are utilizing the OpenGL driver, display-lists are only activated if you've first defined the polygon handedness for the model as described in section 7.3.4. However, a polygon handedness should generally ALWAYS be defined since that allows Visualize to also perform back face culling, covered below.
When the static model attribute is set, Visualize will combine the geometry and attributes from multiple segments into a more optimized, internal segment tree. At render time, Visualize renders from this internal segment tree instead of the external scene graph that you have defined. When used in conjunction with segment-level display lists, it can significantly improve rendering performance, because graphical objects will be grouped into larger segment-level display lists and result in better GPU throughput.
Static model, as its name implies, should be used when you expect your model to not change between updates. For example, you may have view/query logic that is changing a camera in a 'view' segment (where that view segment includes the model tree), or is selecting/qeurying the model. In this case, there are no changes being made to the actual model subtree.
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. However, editing specific pieces of geometry will not cause the static model to be regenerated. Additionally, if you are using Visualize highlighting support, you can make items change color or effectively disappear, and the static model will not be regenreated. When using static model, you can still make changes (edits/deletions, etc...) to segments that are not affected by the static model setting, and the static model will not be regenerated. For example, you could have other segment(s) in the scene graph where you are continually flushing/editing temporary geometry, in perhaps in order to dynamically create/edit a piece of new geoemtry, or create/edit overlayed annotations/markups.
Performance Expectations
You should always attempt to organize your scene graph according to the recommendations in section 7.3.1. As noted in that section, one of the main guidelines is to group your geometry into as few segments as possible, based on similar attributes. However, sometimes it may be difficult to organize the scene in this way due to the structure of the model itself, and the static model attribute can greatly improve performance. However, if your scene graph is already fairly optimally organized, using static model may not have any noticeable effect.
There are three main factors that determine how much of a performance gain can be realized: the size of your model, its original segment organization, and the usage patterns of your application. For instance, if your application has a viewing mode that allows the end-user to examine (zooming, rotating, and panning the camera) a model without altering it, static model can frequently increase performance.
Static model does not have to be enabled at all times, and can also just be enabled for certain parts of your scene graph that you know may not be changing. Additionally, 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". In general, we recommend that you try this option with 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.
Setting the static model mode to AttributeSpatialStaticModel will not result in as large a potential performance gain as AttributeStaticModel, since additional spatial-based internal segments will be created. However, if fixed framerate is also being utilized (see below), the additional spatial segments will ensure that the most visually important items will be drawn first during each time-bound update.
In computer graphics, culling is the procedure used to identify geometry that is not important enough to be drawn and subsequently prevent it from being rendered. The effect of culling is to speed up the rendering process. Visualize uses a few different types of culling to achieve these aims, as discussed in the next subsections.
Back face culling is a rendering optimization used to determine whether the back faces of a polygon should be drawn. The determination is made based on whether the polygon's vertices are oriented in a clockwise or counter-clockwise direction with respect to the active camera. The terms "clockwise" and "counter-clockwise" are merely for convenience - Visualize understands the terms as "left" and "right". For example, if the user has specified that front-facing polygons are those that are wound counter-clockwise (a handedness of "right"), then any polygons oriented clockwise would be considered to be facing away from the camera and thus will not be rendered. This operation improves performance because the number of polygons Visualize must draw is reduced.
Polygons do not have an inherent front or back face. You make the determination by setting the handedness for faces. Back face culling will not work without a handedness setting.
The code below demonstrates how to enable back face culling and how to set the front-face handedness.
This diagram shows how inserting vertices in a specific order affects the handedness. The left polygon has a left-handed orientation because the vertices have been inserted counter-clockwise [0, 1, 2, 3] with respect to the camera. The right polygon has a right-handed orientation with the order [0, 3, 2, 1].
In the images below, the two shells are identical, except for the orientation of their faces. The vertices of the first polygon have been inserted in a clockwise, or "left-handed", orientation. The second polygon's vertices are counter-clockwise, or "right-handed". Back-face culling is not enabled in the first image, and both faces are drawn regardless of their orientation. In the second image, the polygon handedness has been set to Left, indicating left-handed polygon faces will not be drawn. Culling has been enabled in the second image to demonstrate this.
Polygon handedness also indicates which way the normals should by directed when assembling shells from a face list. Using the incorrect handedness will result in abnormal lighting effects.
NOTE: Polygon handedness does not take into account the coordinate system handedness and instead assumes that the coordinate system is left-handed. This means that for users of right-handed coordinate systems, the front and back faces of your polygons may appear to be reversed. The problem is solved by setting the polygon handedness to opposite of what it actually is. The reason Visualize assumes a left-handed coordinate system is so that developers can change the coordinate system handedness without having to also change the polygon handedness.
Some scenes contain many smaller but geometrically detailed parts that are not important to the user's workflow when their extents fall elow a certain size on screen (for example, when zoomed out). In these cases, the user may not care to see every part of the scene, and would rather rendering be completed quickly. Culling extents deals with culling geometry that would be rendered smaller than a certain number of pixels.
Deferral extents is similar to culling extents in that small geometry is marked for culling. However, deferred geometry is still drawn, but only after all other geometry is drawn. This way, non-important geometry still appears, but only when the renderer is otherwise idle. This option is only useful for timed updates, when the scene is drawn piece-by-piece instead of all at once. For more information about timed updates, see the section on fixed framerate mode below.
To enable culling extents or deferral extents:
Another culling algorithm is the 'view frustum' setting which means that Visualize does a bounding-box check for each segment and shell/mesh primitive. The check determines if the segment/primitive can be viewed by the current camera or lies within the selection region before trying to draw or select it. This is a common culling technique and is on by default; it should only be disabled for debugging purposes. The following code snippet shows how to set this suboption.
Vector culling is another setting which allows you to set a vector and tolerance for a given segment. Visualize determines if the segment should be drawn by finding the angular difference between the culling vector and the view vector. If the difference is less than the tolerance angle, then Visualize terminates the tree walk at that segment and it will not be drawn. If you organize your segment tree spatially by faces, then setting these two options could improve the rendering performance significantly.
When working with scenes composed of a large amount of geometry or effects relative to the GPU capabilities, rendering at an interactive speed is not possible. Rather than wait for Visualize to fully redraw the entire scene on each update, it is usually much more preferable to ensure a fixed framerate.
When fixed framerate mode is enabled, Visualize will draw as much of the scene as possible, interrupt the rendering process to ensure that the user-specified framerate value is met, and begin the next update. If another update is not requested (for example, the user stops orbiting the camera), Visualize will continue to render the undrawn parts of the scene until another update request. Even though Visualize will not draw the entire scene, the user will typically prefer to get a smooth framerate regardless of model size, instead of drawing every detail and encountering an unpredictable and slower framerate as model size increases.
Fixed framerate mode is supported via the HPS::Canvas object and is activated by calling HPS::Canvas::SetFrameRate:
In the code of snippet 7.3.5.a, the parameter to HPS::Canvas::SetFrameRate indicates the the number of frames per second that you want to maintain. Culling and deferral extents are also important considerations. In this example, the culling extent value '10' means any entity that would be rendered at 10 pixels or less is not drawn. The deferral size of '100' indicates anything that is rendered at 100 pixels or less is drawn after all the larger objects are drawn. To disable fixed framerate, set the frames-per-second value to 0.
The assumption with the culling extents and the deferral size is that smaller objects are less important in a framerate-limited situation and are sacrificed for the more prominent geometry. The nature of this technique means geometry is drawn as soon as the GPU is ready to display it - Visualize does not wait to compose a full scene before drawing.
Because fixed framerate will instruct Visualize to only draw what it can within the specified time, it is important to ensure that overall rendering performance is as optimized as possible. For example, if the scene-graph is poorly organized and rendering throughput is poor, that could result in very little geometry being drawn in each update. To help ensure maximum throughput when using fixed framerate, you should consider utilizing the static model capabilities discussed above. Additionally, the static model AttributeSpatialStaticModel mode will ensure that larger and most visually important items are drawn first.