Overview of the Architecture
The default renderer in MotionBuilder requires manual shading setup for each model, as well as material and lighting tweaking to achieve good visual quality.
The contents in the MotionBuilder viewport include three parts: scene, locator, and heads up display (HUD). The custom renderer API focuses on scene shading, but you can extend the locator and HUD using the other components in the SDK. Additionally, you can customize the texture, material, geometry, model, light, and camera, if needed.
Concurrent Pipeline and Multiple Buffer Mechanism
MotionBuilder uses the multiple concurrent pipeline and double data buffer mechanism for high performance evaluation and rendering as shown in the following figure.
Adapting the multiple data buffer mechanism reduces the explicit need of lock and improves the pipeline’s throughput as shown in the figure. In this architecture, the evaluation pipeline can evaluate the scene for the next frame in the evaluation data buffer, while the rendering pipeline can display the scene for the current frame using the data in the display buffer. On swapping data, the indexes of the two buffers are exchanged. After a short time interval for event processing (for example, mouse, keyboard, or other user or system events), the next frame is processed with each pipeline working on alternative data buffers.
The tasks in the evaluation pipeline are distributed to multiple threads after the directed acyclic graph (DAG) dependency sorting. The geometry deformation tasks can be either executed in parallel on multiple CPUs in the evaluation pipeline after the depended transformation evaluation tasks, or collectively in the graphics processing unit (GPU) at the beginning of the rendering pipeline for better performance, if a CUDA™ enabled video card is available.
Currently, a single CPU core and a single GPU device is used for rendering by default. However, you can use the multi-GPU rendering approach as well.
Callbacks to Access the Pipeline's Critical Timing
The concurrent pipeline and multiple buffer architecture improves the runtime performance, but it can also complicate plug-in development. You need to take the design decisions carefully to maintain real-time performance without compromising the data integrity in multiple pipeline buffers. You can use a set of global callbacks to gain access to the critical timing stages of various pipelines.
Following is a set of global callback timings in the concurrent evaluation and rendering pipeline:
enum FBGlobalEvalCallbackTiming
{
/** Invoked before any DAG (Transformation and Deformation) evaluation tasks start
* in the evaluation pipeline/thread.
*/
kFBGlobalEvalCallbackBeforeDAG,
/** Invoked after all DAG (Transformation and Deformation) evaluation tasks are finished
* in the evaluation pipeline/thread.
*/
kFBGlobalEvalCallbackAfterDAG,
/** Invoked after all the deformation tasks are finished in the evaluation pipeline/thread.
*/
kFBGlobalEvalCallbackAfterDeform,
/** Invoked when both the evaluation and rendering pipelines/threads are stopped.
* Useful in certain complicated scene change tasks to avoid race condition.
*/
kFBGlobalEvalCallbackSyn,
/** Invoked in the rendering pipeline, before any rendering tasks start (immediately
* after clearing the GL back buffer).
*/
kFBGlobalEvalCallbackBeforeRender,
/** Invoked in the rendering pipeline, after any rendering tasks finish (just
* before swapping the GL back/front buffer).
*/
kFBGlobalEvalCallbackAfterRender
};
kFBGlobalEvalCallbackBeforeRender
and kFBGlobalEvalCallbackAfterRender
in the same rendering pipeline (same thread), although it might be called multiple times depending on the viewport settings such as multiple view panes, stereo camera, and others.Following are a set of functions and event properties to manage the registration of the global callback on a pipeline's critical timing:
void RegisterEvaluationGlobalFunction (kFBEvaluationGlobalFunctionCallback pCallback,
FBGlobalEvalCallbackTiming pTiming);
void UnregisterEvaluationGlobalFunction(kFBEvaluationGlobalFunctionCallback pCallback,
FBGlobalEvalCallbackTiming pTiming);
FBPropertyEventCallbackEvalPipeline OnEvaluationPipelineEvent;
FBPropertyEventCallbackRenderPipeline OnRenderingPipelineEvent;
FBPropertyEventCallbackSynPoint OnSynchronizationEvent;
Listening to the Scene Change Events
Most of the SDK object classes are inherited from the FBPlug
class, which provides a set of low level functions to manage the source (child) and destination (parent) connections between the plug instances. It also provides three important virtual functions: FBPlug::PlugNotify()
, FBPlug::PlugDataNotify()
, and FBPlug::PlugStateNotify()
. These functions can be overridden in the subclasses to receive notifications when there are changes in the internal state of the instances.
The following figure shows the plug based object model in MotionBuilder:
The most important subclasses are FBComponent
and FBProperty
. An instance of FBComponent
owns a list of properties (FBComponent::PropertyList
). The connection between the two object instances and the various value (or state) changing events on owned properties trigger callback events in the PlugNotify()
(or PlugDataNotify()
and PlugStateNotify()
) function. The FBProperty
subclass can contain various type of raw data (FBPropertyBool
, FBPropertyInt
, FBPropertyColor
, and others), and it also provides a uniform mechanism to organize the object’s attributes either using scalar or time varied (FBAnimatableProperty
) values.
Most objects in the ORSDK (including model, locator, geometry, material, textures, and others) are inherited from the FBComponent
class. The following figure shows the model, locator, geometry, material, and texture class hierarchy.
You can use a set of callback events (type of FBPropertyEvent
and its subclasses) in some global singleton objects to write code that can listen to the following:
- Certain high level scene change events. For example,
FileNew
,FileOpen
, andFileExit
inFBApplication
. - Certain events for all instances of targeted types. For example,
FBSystem::OnConnectionNotify
.
You can use wildcard searching (FBPropertyEvent*
) in the ORSDK header files to collect the complete list of event properties. The following figure shows the frequently used event properties in singleton objects.