HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Packed Primitive Rendering

Overview

The viewport treats packed primitives as geometry containers, much like objects. A packed primitive can have some basic attributes, a transform, and a level-of-detail attribute, along with some geometry which may be unpacked for display. This section outlines some strategies for drawing, caching, and instancing packed primitives.

Creating Instances with GT

One of the easier ways to instance packed geometry is by creating a GT_PrimInstance through a custom GT collector (see GT_PrimTetra.C example in toolkit/samples/tetprim). Once you have collected all the primitives that the hook is interested in, bucket them based on their packed geometry and create a GT_PrimInstance for each bucket.

A GT_PrimInstance, at minimum, takes a GT_Primitive and a set of GT_Transforms. If you are storing your geometry data in a GT_Primitive subclass, you can assign it directly to the instance. If you are storing it in a GU_Detail, create a new GT_GEODetail primitive to wrap the detail and assign that to the instance.

In order for instances to be picked as primitives, the GT_GEOOffsetList should also be set on the GT_PrimInstance. This contains a list of primitive GA_Offsets, one per transform, of each of the packed primitives this instance is representing.

The viewport also supports a Cd attribute (fp32 vector3), which will be multiplied by the geometry and material colors. There are also several special attributes which can be added:

  • __topology (detail only): an integer representing a unique topology configuration. If this changes (number of points, primitives, edges, vertices change), then the viewport will regenerate the geometry from scratch instead of incrementally updating the attribute values. If this isn't present the geometry will be regenerated from scratch every time the geometry changes, so it's a good idea to have this attribute.
  • __bboxmin, __bboxmax (detail only): 2 attributes with 3 floats each representing the bounding box: (Xmin, Ymin, Zmin), (Xmax, Ymax, Zmax). If this is missing, the bounding box will be computed from the source geometry. Both must exist for this to be used.
  • __filename (detail only): A string containing the full path to the file that this geometry was loaded from. This aids in caching. If the geometry did not come from a file but another named source, this can be used as an indentifier (the actual filepath is never accessed). If the source has no name, don't include this attribute.
  • __version (detail only): An int64 version serial which can be bumped whenever the file source has changed. This is used with __filename, and ignored otherwise.
  • __primitive_id (uniform only): An integer array of GA_Offsets for each packed primitive in the instance. This should be the same length as the number of transforms. This allows the instances to be selected in Primitive Selection mode.
  • __vertex_id (uniform only): An integer array of GA_Offsets for each packed primitives' vertex. This should be the same length as the number of transforms. This allows the instances to be selected in Point and Vertex Selection modes.

These are all attributes on the packed primitive, not the contained packed geometry.

Deferred Instance with GR

"Deferred Instancing" is a display optimization that allows packed instances to be replaced by bounding boxes if the scene it too heavy. All instances in the scene in the current view frustum are considered before rendering any of them. This is a two step process where you notify the GR_InstanceManager of your intent to draw instances, and then query what you're allowed to draw from it during the render.

If you have a GR primitive and would like it to participate in the deferred instancing scheme, you can post a draw request in the new virtual method checkForDeferredDraw() and later on query the results of that draw request in render().

void
GR_PrimCustom::checkForDeferredDraw(const RE_Render *r,
GR_RenderMode render_mode,
GR_RenderFlags render_flags,
const GR_DrawParms &dp,
const UT_Matrix4D &proj_view,
const UT_Matrix4D &object,
bool &visible,
bool &defer)
{
defer = true;
visible = true; // may want to use inViewFrustum() to conditionally set
if(!visible)
return;
int num_gl_prims_per_instance = // approx # of GL triangles, lines, etc.
bool is_wire = (mode == GR_RENDER_WIREFRAME ||
bool selected = // is the primitive or parent object selected?
int primtiive_id = // GA_Offset of packed primitive
myPackedColor = // color of packed prim, if any. Must exist until the render,
// cannot be a local as it is just referenced.
myBBox = // bounding box of packed geometry. Must exist until the render,
// cannot be a local as it is just referenced.
auto &&imgr = GR_InstanceManager::get();
myDrawID = imgr.queueDrawRequest(r, num_gl_prims_per_instance, myBBox,
render_mode, render_flags, dp, is_wire,
primitive_id, is_selected, &myPackedColor);
}
void
GR_RenderMode render_mode,
{
if(myDrawID >= 0)
{
getDrawRequestResult(myDrawID, render_mode, flags,dp);
return;
}
// Do normal rendering of the packed geometry
}

There are also methods to queue a bunch of instances, either fully (queueInstanceDrawRequest) or using a subset (queuePartialInstanceDrawRequest). The latter is useful because frustum culling can be done on the instances first building an index list of visble instances, then a draw can be queued for only visible instances. When queuing a bunch of instances, PARTIAL_DRAW may be returned, along with a boolean array for each queued instance indicating which instances are to be drawn.

It's also possible to queue bounding boxes in the case of a packed primitive with reduced LOD (queueBBoxDraw). This is more efficient than drawing your own bounding box as they are drawn in one pass at the end.

Finally, if you don't want the primitive to participate in the deferred scheme it is still a good idea to let the instancer know how many GL primitives you will be drawing with queueUncullableDraw(). And it's always a good idea to override checkForDeferredDraw() and assign 'visible' properly for frustum culling, even if you do nothing at all with GR_InstanceManager.

Caching GT Instances

If you have a GR_Primitive that handles the rendering for packed geometry and some way to reference that geometry (id, name, etc), then you can cache the GT geometry in the GT_PackedGeoCache (see GT_PackedGeoCache.h).

If you are generating a GT_PrimInstance, you can add the __filename as detail attributes on the GT_PrimInstance to describe the source file and the object name (if any). This will automatically cache all GL buffers with the new path, so that when the containing detail is changed, it will still reference the same packed geometry.