Framework Interfaces
Initialization
In order to use APEX, the first thing that must be done by the application is to create a single instance of the ApexSDK class:
#include "Apex.h"
extern PxFoundation* gFoundation;
extern PxPhysics* gPhysicsSDK;
extern PxCookingInterface* gCooking;
extern PxUserOutputStream* gOutputStream;
ApexSDK* gApexSDK;
...
ApexSDKDesc apexDesc;
ApexCreateError errorCode;
apexDesc.outputStream = gOutputStream;
apexDesc.foundation = gFoundation;
apexDesc.physXSDK = gPhysicsSDK;
apexDesc.cooking = gCooking;
apexDesc.renderResourceManager = new UserRenderResourceManager();
gApexSDK = CreateApexSDK(apexDesc, &errorCode);
The PhysX SDK must have already been initialized, so you that you have a PxPhysics*. Most functionality of APEX is provided directly or indirectly through the ApexSDK class. For example, scene creation and APEX module initialization is performed by calling methods of the ApexSDK class.
Loading Modules
The ApexSDK class provides a single generic public API for loading modules, called createModule(). Providing a single generic API makes it possible for third-parties to develop modules and ship module binaries (libs and DLL’s) that are compatible with a particular version of the APEX SDK and of the PhysX SDK.
The module is identified by its filename, without path and without suffix/extension, which is the first argument to createModule(). The second argument to createModule() is a pointer to an error result. The APEX SDK will ensure that the loaded module matches the APEX and PhysX SDK versions. Module specific parameters, if any, must be passed to the module using a subsequent call to the module’s init() method.
Here is an example:
#include "ApexSDK.h"
#include "ModuleDestructible.h"
#include "NvParamUtils.h"
ModuleDestructible* gModuleDestructible;
...
ApexCreateError errorCode;
NvParameterized::Interface* moduleDesc;
#if MODULE_STATIC_LINKED
instantiateModuleDestructible();
#endif
gModuleDestructible = static_cast<ModuleDestructible*> ( GetApexSDK()->createModule("Destructible", &errorCode) );
PX_ASSERT(gModuleDestructible);
moduleDesc = gModuleDestructible->getDefaultModuleDesc();
NvParameterized::setU16( *moduleDesc, "maxActorCreatesPerFrame", 50 ); // 50 is just a random number for demonstration purposes
gModuleDestructible->init( *moduleDesc );
Creating Scenes
The APEX scene (class Scene) represents a unique physics world in which various types of objects can be instantiated. It mirrors the PhysX SDK PxScene class. An instance of the Scene class should be created for each PxScene in which APEX will be used.
The APEX scene provides a centralized interface for advancing the simulation and accessing the rendering data output from APEX.
The Scene derives from Context, and therefore can contain (or be the context for) APEX actors. In fact, all *asset::create*Actor() methods must take an Scene argument.
The Scene operates in similar fashion to the PhysX SDK scene, however, the APEX scene can exist, and even contain APEX actors, without having an associated PhysX SDK scene. But in order to actually perform simulation in the PhysX SDK, an PhysX scene must be associated with the APEX scene, either by providing a pointer to it in the SceneDesc descriptor structure, or by using the setPhysXScene() method.
Using CUDA
An APEX scene will use CUDA if the associated PhysX scene is given a GpuDispatcher. See the PhysX 3.x documentation on GPU Resource Management for more information.
Loading Assets
At run-time, asset creation is typically performed by the game engine’s “level loader” using the Named Resource Provider’s user defined ResourceCallback::requestResource() method. The level loader typically processes a set of asset instances that have been placed in the level. At a minimum, the game engine must be capable of storing the following information for each asset instance in the level: name of the asset, the type of the asset, and position and orientation of the instance. For each asset instance found in a level, the level loader should query the NRP to determine whether or not the named asset has been already loaded. It should use the ResourceProvider::getResource() method to do so. If the asset has not been loaded, a callback to ResourceCallback::requestResource() will be immediately triggered to request that the asset be loaded. Either way, getResource() will return a void pointer to the loaded asset that can subsequently be used to create the instance of the asset: the Actor.
Assets use NvParameterized classes to store all of their serialized data. APEX provides a serializer which will serialize and deserialize to either an ASCII XML format or a binary format.
To load APEX assets, the ResourceCallback::requestResource() callback should first create a PxFileBuf data stream using ApexSDK::createStream() for reading the asset from non-volatile storage. In the APEX samples the assets are mapped to a single file and not bundled into packages, though a game engine can interpret an asset name from ::requestResource() however it wishes.
If the APEX default stream implementation is being used (e.g.: the APEX samples), a stream can be created with the ApexSDK::createStream() method:
physx::PxFileBuf* stream = GetApexSDK()->createStream( "asset.xml", physx::PxFileBuf::OPEN_READ_ONLY );
Otherwise, an instance of a user defined stream class (derived from the pure virtual PxFileBuf base class) should be created.
Create the NvParameterized::Serializer, specifying the serialization format (this may or may not be part of the asset name in the callback):
// NvParameterized::Serializer * ApexSDK::createSerializer(NvParameterized::Serializer::SerializeType type)
NvParameterized::Serializer * ser = GetApexSDK()->createSerializer(serType); // serType is either NST_XML or NST_BINARY
You may also specify custom Traits object if you want to change some aspects of serializer behavior (see NvParameterized Guide for more details):
// NvParameterized::Serializer * ApexSDK::createSerializer(NvParameterized::Serializer::SerializeType type, NvParameterized::Traits *traits)
NvParameterized::Serializer * ser = GetApexSDK()->createSerializer(serType, customTraits);
You may determine stream format using ApexSdk::getSerializeType() method:
NvParameterized::Serializer::SerializeType serType = GetApexSDK()->getSerializeType(*stream);
PX_ASSERT( serType != NvParameterized::SerializeType::NST_LAST );
You may also check target platform for your binary file (e.g. before you do inplace deserialization):
NvParameterized::SerializePlatform platform;
GetApexSDK()->getSerializePlatform(*stream, platform);
if( platform == NvParameterized::SerializePlatform::GetCurrentPlatform() )
...
The NvParameterized asset classes must be deserialized using the NvParameterized::Serializer::deserialize() method. The parameters required are the data stream and an instance of the NvParameterized::Serializer::DeserializedData class. All assets which are serialized in the stream will be deserialized and stored as NvParameterized classes which can be accessed through the DeserializedData object.
The NvParameterized class(es) serialized in the PxFileBuf may be accessed from the DeserializedData object and created using the ApexSDK::createAsset(NvParameterized::Interface parameters, const char * name)* method:
const char *assetName; // input parameter in ::requestResource()
Asset *asset;
NvParameterized::Serializer::DeserializedData data;
serializer->deserialize(*stream, data); // assume there is one asset in the stream for this case
NvParameterized::Interface *params = data[0];
asset = GetApexSDK()->createAsset( params, assetName );
The NvParameterized object(s) retrieved from the serializer should not be released or destroyed by the application. The act of creating an asset from an NvParameterized object passes ownership of the object to the asset.
The asset can be released using ApexSDK::releaseAsset() or Asset::release(). If the asset was loaded as a result of manually calling the ResourceCallback::requestResource() callback, then it should be released using ResourceProvider::releaseResource().
Asset Authoring
Generic asset authoring creation methods are used to create an asset authoring class:
AssetAuthoring * ApexSDK::createAssetAuthoring( const char *authorableTypeName );
AssetAuthoring * ApexSDK::createAssetAuthoring( const char *authorableTypeName, const char *name );
Asset authoring type names are retrieved from the individual APEX asset header files:
// DestructibleAsset.h
#define DESTRUCTIBLE_AUTHORING_TYPE_NAME "DestructibleAsset"
Asset authoring classes should be released using:
void ApexSDK::releaseAssetAuthoring( AssetAuthoring& );
Since all asset data is stored in NvParameterized classes, it is all available to be edited from the NvParameterized object retrieved from the asset author using the AssetAuthoring::getNvParameterized() method.
Creating an Asset from an Authoring Class
An APEX Asset and Asset Authoring basically contain the same NvParameterized class, but ownership must be considered.
If one wanted to keep the Asset Authoring class and create a new Asset, the ApexSDK::createAsset( AssetAuthoring&, const char *name ) method may be used. This will perform a copy of the NvParameterized class data:
AssetAuthoring *author; // initialized elsewhere
Asset *asset = GetApexSDK()->createAsset( *author, "uniqueAssetName" );
If one no longer needed the Asset Authoring class and wanted to create a new Asset, a more efficient process may be used (no NvParameterized data copy required):
AssetAuthoring *author; // initialized elsewhere
NvParameterized::Interface* oldInterface = author->releaseAndReturnNvParameterizedInterface();
Asset *asset = GetApexSDK()->createAsset( oldInterface, "uniqueAssetName" );
Creating an Authoring from an Asset Class
An APEX Asset and Asset Authoring basically contain the same NvParameterized class, but ownership must be considered.
If one wanted to keep the Asset class and create a new Asset Authoring, a copy of the NvParameterized class data must be made:
Asset *asset; // initialized elsewhere
NvParameterized::Interface* oldInterface = asset->getAssetNvParameterized();
// make the copy
NvParameterized::Interface* newInterface = GetApexSDK()->getParameterizedTraits()->createNvParameterized(oldInterface->className());
newInterface->copy( oldInterface );
AssetAuthoring *author = GetApexSDK()->createAssetAuthoring( newInterface, "uniqueAssetAuthoringName" );
If one no longer needed the Asset class and wanted to create a new Asset Authoring, a more efficient process may be used (no NvParameterized data copy required):
Asset *asset; // initialized elsewhere
NvParameterized::Interface* oldInterface = asset->releaseAndReturnNvParameterizedInterface();
AssetAuthoring *author = GetApexSDK()->createAssetAuthoring( oldInterface, "uniqueAssetAuthoringName" );
Loading All Assets at Once
There are several scenarios where a game may require all assets to be loaded at once:
- Some games may require that APEX loads all of its assets at level load time (possibly in a deterministic order) to avoid asset loading during game play.
- Some games may require that APEX load all of its assets at one time so that they have information about which asset files to bundle into a “cooked build” of a level.
- Some games may require assets to be loaded asynchronously (e.g.: over a slow network connection). They need to be able to start streaming all their assets during the “level loading” time.
Since APEX assets can reference other APEX assets by name, it is not normally possible for the level loading code to know, up front, about all assets that are going to be needed. In order to solve this problem, APEX provides an API that can be used to force the immediate loading of all referenced, but not yet loaded assets.
The game engine is responsible for calling ApexSDK::forceLoadAssets() as many times as it returns a number greater than 0. Each time it calls forceLoadAssets() it should expect calls to its registered ResourceCallback::requestResource() callback to load the requested asset:
PxU32 ApexSDK::forceLoadAssets()
This method returns the number of assets force loaded by all of the existing APEX modules. It will also force load Render Mesh assets, which are not contained in a module.
Instantiating Actors
Once an asset has been created, de-serialized, and registered with the Named Resource Provider, instantiating it in a scene is done by calling the Asset::createApexActor() method. Additional information, such as its global pose (position and orientation in the scene), that is commonly available to the level loader can be provided in an NvParameterized descriptor data structure that is passed to the createApexActor() method along with a pointer to the APEX Scene:
NvParameterized::Interface * destructibleDesc = asset->getDefaultActorDesc();
/* Set the actor's pose */
NvParameterized::setParamMat44( *destructibleDesc, "globalPose", pose );
/* Create the destructible actor in the APEX scene */
DestructibleActor* actor = asset->createApexActor( *destructibleDesc, *gApexScene );
The Asset classes own the NvParameterized::Interface object that is retrieved from Asset::getDefaultActorDesc(), so there is no need for the application to destroy it.
Named Resource Provider
The Named Resource Provider (NRP) is a public interface class in the APEX Framework (class ApexResourceProvider). It’s purpose is to provide name to address mapping for APEX assets (Asset and derived classes) and for miscellaneous game engine resources that APEX authorable objects refer to by name, such as materials/textures. It also provides a callback interface to allow the application to load the named asset and thus resolve the mapping.
The NRP maintains mappings from a namespace/name pair to a resource pointer. Namespaces and names are both null terminated character strings (char*), and resource pointers are void pointers (void*).
Internally to APEX, a unique integer is associated with every namespace/name pair. This integer is used to efficiently access the current value of a resource pointer, without requiring the NRP to perform string operations everytime the resource pointer is queried.
Every module, when created, creates a new namespace for each type of authorable object (asset) that it implements. The namespace for an authorable object will not be changed, because it is through this name that game developers, as well as other modules, will reference assets of this type.
Whenever any asset is referenced by name (e.g.: when some other asset is loaded), APEX uses the createResource() method to create a mapping from the namespace/name pair to a unique resource identifier. This only needs to be done once, as resource identifiers are permanently (for the lifetime of the APEX SDK) associated with the namespace/name pair. The referencing object should save the assigned resource identifier for subsequent use with the getResource() method to query the current value of the resource pointer. APEX modules must, however, not save the resource pointer for future use. Rather, they must call getResource() every time they need to access the resource pointer. This allows the application to call setResource() at any time to change the value of the resource pointer. When the referencing object is done referencing the resource (e.g.: when the referencing object is deleted), it must call releaseResource(), which will call the ResourceCallback::releaseResource() callback if the number of references is now zero.
The public interface consists of a single method, setResource(), and two callback functions, requestResource() and releaseResource(), that are implemented in a user defined subclass of the pure virtual base class, ResourceCallback. The application may call setResource() at any time, to create a mapping between a namespace/name pair and a resource pointer. This can be safely done before the createNameSpace() or createResource() calls are made by the APEX modules. If the application has not yet called setResource() to create a mapping (for a particular namespace/name pair) when getResource() is called for the first time from within APEX, then the NRP will call the requestResource() callback to request the resource pointer to use for the mapping. The callback must return a value, however, the value may be 0 (NULL). The callback will only be called once for a given namespace/name pair. If the application wants to subsequently change the mapping, it must call setResource(). This may be done as often as the application wishes, and getResource() will always return the correct (most recent) resource pointer. The user class implementing ResourceCallback must be provided in the descriptor when the APEX SDK is initialized, in the call to CreateApexSDK().
NRP Well Known Namespaces
NSCollisionGroup
A namespace for names of collision groups (CollisionGroup). Each name in this namespace must map to a 5-bit integer in the range [0..31] (stored in a void*). The NRP will not automatically generate release requests for names in this namespace:
#define APEX_COLLISION_GROUP_NAME_SPACE "NSCollisionGroup"
NSCollisionGroup128
A namespace for names of GroupsMasks. Each name in this namespace must map to a pointer to a persistent 128-bit GroupsMask type. The NRP will not automatically generate release requests for names in this namespace:
#define APEX_COLLISION_GROUP_128_NAME_SPACE "NSCollisionGroup128"
NSCollisionGroupMask
A namespace for names of collision group masks. Each name in this namespace must map to a 32-bit integer (stored in a void*), wherein each bit represents a collision group (CollisionGroup). The NRP will not automatically generate release requests for names in this namespace:
#define APEX_COLLISION_GROUP_MASK_NAME_SPACE "NSCollisionGroupMask"
ApexMaterials
A namespace for graphical material names. Each name in this namespace maps to a pointer to a game-engine defined graphical material data structure. APEX does not interpret or dereference this pointer in any way. APEX provides this pointer to the UserRenderResource::setMaterial(void *material) callback to the rendering engine. This mapping allows APEX assets to refer to game engine materials (e.g.: texture maps and shader programs) without imposing any limitations on what a game engine graphical material can contain. The NRP will not automatically generate release requests for names in this namespace:
#define APEX_MATERIALS_NAME_SPACE "ApexMaterials"
NSPhysicalMaterial
A namespace for physical material names. Each name in this namespace maps to MaterialID, which is a data type defined by the PhysX SDK. The NRP will not automatically generate release requests for names in this namespace:
#define APEX_PHYSICS_MATERIAL_NAME_SPACE "NSPhysicalMaterial"
NSCustomVBNames
A namespace for custom vertex buffer semantics names. Each name in this namespace maps to a pointer to a game-engine defined data structure identifying a custom vertex buffer semantic. APEX does not interpret or dereference this pointer in any way. APEX provides an array of these pointers in UserRenderVertexBufferDesc::customBuffersIdents, which is passed the rendering engine when requesting allocation of vertex buffers:
#define APEX_CUSTOM_VB_NAME_SPACE "NSCustomVBNames"
Namespaces for Asset names
A new namespace is created for each type of Authorable Object (i.e. Asset).
The names in this namespace will be the names of assets of the specified type. Each entry maps to a pointer to the asset class for the named asset.
The NRP will automatically generate release requests for names in this namespace when the APEX SDK is released.
- ApexRenderMesh
- ClothingAsset
- DestructibleAsset
- ApexEmitterAsset
- GroundEmitterAsset
- ImpactEmitterAsset
- IofxAsset
Render Mesh Asset
The RenderMeshAsset class, and the associated RenderMeshAssetAuthoring and RenderMeshActor classes, implement a general purpose triangle mesh asset. These classes are provided by the APEX Framework, and can be used, by named reference, or by inclusion by assets in other modules.
The RenderMeshAsset stores a vertex buffer accessible through an VertexBuffer interface. This vertex buffer supports a wide variety of attributes: position, normal, tangent, binormal, color, bone indices, bone weights, and up to 4 sets of [U,V] coordinates. It also supports a variety of formats for each attribute. See Render Mesh Asset Interfaces.
The mesh can be subdivided in two orthogonal ways: by “part” and by “sub-mesh”. Furthermore, each vertex of the mesh can be associated with one or more “bones” (transformation matrices).
Each “part” is a unique subset of the triangles of the mesh for which rendering can be individually enabled or disabled. This is called “visibility”, and is controlled, per instance, using the RenderMeshActor::setVisibility(..) method. A part can span several sub-meshes (see below), such that it can use different materials for different triangles.
Each “sub-mesh” is a unique subset of the vertices and triangles of the mesh that is assigned a particular material name. At run-time, the material name is mapped to a user-defined pointer, using the Named Resource Provider. This pointer is subsequently passed to the rendering engine through the Render Resources Interface. A sub-mesh can contain triangles from many different parts.
Each “bone” is a transform matrix (position, rotation, and scale) that can be used at run-time to transform the position of the vertices of the mesh. Each vertex can be associated with one or more bones using the “bone index” vertex semantic. If a vertex is associated with more than one bone, the “bone weight” semantic is also used to provide a weighting factor that determines the proportion in which the vertex position is transformed by each bone. If all the vertices of a part are associated with a single common bone, the part will be rigid. If they are associated with different bones, the part will be “skinned”.
Render Resources Interface
The APEX Framework provides a standard interface for modules to interface with the game’s rendering engine. The purpose of the interface is to pass data (e.g.: vertex buffers, index buffers, transform buffers) from APEX to the rendering engine for rendering. APEX does not directly render anything (i.e.: no calls to D3D or OpenGL), nor does specify or control how or when things are rendered. It simply seeks to pass data that it generates or updates to the game’s rendering engine in a flexible yet efficient manner.
Renderable Actor Interface
All transactions with the “Interface to Rendering” are initiated by the game calling an APEX API. This interface is thread-safe, and can be called at any time during the frame, including during simulation, and while calls are being made from other game threads to other APEX API’s (assuming the locking semantics described below are respected). These calls are all methods of the Renderable class, which all renderable APEX actors derive from:
class Renderable
{
public:
const PxBounds3& getBounds() const;
void updateRenderResources();
void dispatchRenderResources( UserRenderer &renderer, PxReal lod );
}
Calling updateRenderResources() on an APEX actor allows APEX to issue callbacks to allocate any buffers that need to be allocated, and to update (fill-in) any buffers that need to be updated. Other than issuing these callbacks, updateRenderResources() will not perform any additional computations, so as not to adversely impact the performance of the thread that called it.
Note
By performing these tasks in callbacks from updateRenderResources(), APEX allows the game to control when buffers are allocated and written to, as well as the thread context in which this occurs. These buffers can therefore be directly allocated from the graphics subsystem (e.g.: from D3D), even when the graphics API is not thread-safe, thus avoiding additional buffer copies.
updateRenderResources() must be called before dispatchRenderResources(), but can be skipped entirely if the actor will not be rendered that frame.
Calling dispatchRenderResources() on an APEX actor triggers APEX to call the provided renderer’s renderResource() method for each of the actor’s UserRenderResources. It will not update or allocate any new rendering resources. It can be called as many times as the game wishes to generate the same set of RenderContexts.
Warning
APEX is completely unaware of D3D device resets and will not replay writeBuffer() calls into static vertex or bone buffers. It is the user’s responsibility to request for buffers to be rewritten. For this, updateRenderResources() accepts an optional boolean flag (rewriteBuffers). When set to true, updateRenderResources() will make the necessary writeBuffer() calls to rewrite affected buffer data.
Examples of scenarios that might lead to a device reset include: resizing the application window, switching to full screen mode or changing the screen resolution while the application is running.
There are two important things to note when using the rewriteBuffers flag.
- Do not set rewireBuffers to true the first time UpdateRenderResources() is called.
2. UpdateRenderResources should be called twice when the buffers need to be rewritten. First with rewriteBuffers=true, then with rewriteBuffers=false.
User Implemented Classes
APEX interfaces with the game engine via several classes which are implemented by the user. The first class manages the creation and deletion of render resources. A user defined instance of UserRenderResourceManager is provided to the ApexSDK at startup, and is used globally by APEX to request/release render resources from the game.
class UserRenderResourceManager
{
public:
virtual UserRenderVertexBuffer* createVertexBuffer( const UserRenderVertexBufferDesc& desc );
virtual void releaseVertexBuffer( UserRenderVertexBuffer& buffer );
virtual UserRenderIndexBuffer* createIndexBuffer( const UserRenderIndexBufferDesc& desc );
virtual void releaseIndexBuffer( UserRenderIndexBuffer& buffer );
virtual UserRenderBoneBuffer* createBoneBuffer( const UserRenderBoneBufferDesc& desc );
virtual void releaseBoneBuffer( UserRenderBoneBuffer& buffer );
virtual UserRenderInstanceBuffer* createInstanceBuffer( const UserRenderInstanceBufferDesc& desc );
virtual void releaseInstanceBuffer( UserRenderInstanceBuffer& buffer );
virtual UserRenderSpriteBuffer* createSpriteBuffer( const UserRenderSpriteBufferDesc& desc );
virtual void releaseSpriteBuffer( UserRenderSpriteBuffer& buffer );
virtual UserRenderSurfaceBuffer* createSurfaceBuffer( const UserRenderSurfaceBufferDesc& desc );
virtual void releaseSurfaceBuffer( UserRenderSurfaceBuffer& buffer );
virtual UserRenderResource* createResource( const UserRenderResourceDesc& desc );
virtual void releaseResource( UserRenderResource& resource );
};
APEX only requests new resources from this interface in the context of calls to updateRenderResources(), so those functions generally do not need to be thread safe.
Warning
Any APEX API call that results in actor deletion can result in an immediate callback to UserRenderResourceManager (in the context of the calling thread) to release the associated render resources. Therefore, the release methods of this user implemented class MUST be thread safe.
Each of the UserRender*Buffer classes are also abstract and must be implemented by the user. The buffer descriptor passed to the create methods will describe the data format of each semantic (or member) of that buffer, so the user class has the ability to pack or interleave that data into as many or as few GPU buffers as necessary. The descriptor will also contain a hint from APEX on the usage of that buffer; whether it is static or dynamic. Each render buffer class has a user-implemented write buffer method that can be called by APEX (only from within the context of updateRenderResources()) to update the data in that buffer. The write methods are defined as follows:
// new implementation so the renderer receives all semantics at once
void writeBuffer(const RenderVertexBufferData &data, physx::PxU32 firstVertex, physx::PxU32 numVertices)
APEX provides a pointer to the new source data (srcData) and it’s stride (srcStride), plus the ranges of values in the target buffer to be updated (firstDestElement, numElements). The user is expected to know the size of the APEX data from the descriptor used to create the buffer. If locking and unlocking of buffers is expensive for the game, they can cache the writeBuffer() calls made during updateRenderResources() and then play them back all in a single lock/write/unlock pass. Caching multiple writeBuffer() calls is guaranteed to generate coherent data so long as the actor remains locked until all of the writes are completed.
Next, the user must implement an UserRenderResource class for managing a renderable entity. The descriptor for the UserRenderResource contains pointer to all the various user buffers allocated for this renderable actor. Some of those buffers may be shared with other actors. The public interface of the UserRenderResource has methods to update indices in the various buffers, associate a material, and to retrieve those user buffers. APEX will only call these methods from within updateRenderResources() calls.
Note that when an UserRenderResource instance is released to the UserRenderResourceManager, the resource manager should not implicitly release the render buffers used by that render resource. Instead, APEX will explicitly release those resources as required. Only APEX understands all of the sharing semantics, so the the game should not call any of those release methods itself.
The last class that the user must implement is UserRenderer:
class UserRenderer
{
public:
virtual void renderResource( const RenderContext &context ) = 0;
};
An instance of this class must be passed to dispatchRenderResources() (described below). From the context of dispatchRenderResources(), APEX will call this renderResource() method a number of times to pass RenderContexts back to the game engine. RenderContext is not user-defined, but it is a simple structure:
class RenderContext
{
public:
UserRenderResource *renderResource;
PxMat44 local2world;
PxMat44 world2local;
};
It essentially informs the game engine that a particular UserRenderResource instance should be rendered at a particular world pose. These render contexts could be rendered immediately by your UserRenderer, or it may cache them for later use. The render contexts are guaranteed to be valid until the next updateRenderResources() call is made on that actor.
APEX Scene Based Render Resource Updating
The ApexScene has an API for creating an iterator for the scene’s renderable actors. It is used in the following manner:
MyCachedRenderer renderer;
RenderableIterator *iter = gApexScene->createRenderableIterator();
for( Renderable *r = iter->getFirst() ; r ; r = iter->getNext() )
{
if( earlyCullCheck( r->getBounds() ) )
continue;
r->updateRenderResources();
r->dispatchRenderResources( renderer, 0 );
}
iter->release();
renderer.render_pass_1();
renderer.render_pass_2();
When the game is ready to begin rendering, it can acquire an iterator from the ApexScene and use it to call updateRenderResources() and dispatchRenderResources() on all renderables in the scene it wishes to render. The iterator implicitly handles all of the actor locking and will in fact temporarily skip over actors locked by other threads so that the loop can be completed as efficiently as possible. If the same iterator is used to make multiple passes through a scene’s actors, it will also safely deal with actor deletions.
With this method, the game can store all of the RenderContexts passed to it’s renderer and use them safely in as many rendering passes as it needs without any worry of conflicting with the ApexScene or other game threads making calls to the APEX API.
Ad-Hoc Render Resource Updating
Commonly, the game rendering thread will already have a scene graph for it’s rendering purposes and this graph will contain pointers to all the Renderable actors. In this case, the game will not want to use an iterators to access the actors. Instead, it will want to access them in an ad-hoc method.
Renderable *r = getApexRenderable( gameActor );
MyCachedRenderer gRenderer;
r->lockRenderResources();
if ( !earlyCullCheck( r->getBounds() ) )
{
r->updateRenderResources();
r->dispatchRenderResources( gRenderer, 0 );
}
r->unlockRenderResources();
With this method, APEX is unable to ensure the actor has not been deleted by another game thread, so the game engine must ensure the actor still exists. While the actor is locked, it cannot be deleted or modified in any way by APEX. It’s rendering data is guaranteed to stay in a consistent state until it is unlocked.
Example Overview
From 1000 feet up, the situation looks like this:
- The game allocates an UserRenderResourceManager instance and provides it to the ApexSDK at creation
- The main game thread calls Scene::simulate() and Scene::fetchResults() each frame (probably not in that order)
- When Scene::fetchResults() is called, the APEX actor states are updated to their new world positions and world bounds.
- In the rendering thread, using either an Scene iterator or the ad-hoc locking method, the game finds actors which are close enough to the view frustrum to be renderable and calls their updateRenderResources() methods
- Inside updateRenderResources(), APEX (may) call back to the UserRenderResourceManager to allocate new render resources. APEX then calls buffer write methods to pass the latest ApexActor state to the game engine.
- Subsequent calls to updateRenderResources() before the next invocation of Scene::fetchResults() should equate to NOOPs
- The rendering thread can now call dispatchRenderResources() on any of the ApexActors which have been updated, and it can make this call as many times as it wishes. Each time, dispatchRenderResources() will generate the same list of RenderContexts.
Materials
APEX does not “know” anything about render materials. The details of materials and shaders vary so much one renderer to the next, as well as from one game engine to the next. It would be very difficult, if not impossible, to abstract material handling in an useful way.
However, APEX must know something about materials, to the extent that it groups render vertices according to different materials, and informs the application as to which material is to be used with a vertex buffer when render resources are dispatched (see Render Resources Interface). It accomplishes this via the Named Resource Provider, allowing the application to register all materials (as void* pointers or integer handles) by their names. APEX render mesh assets (RenderMeshAsset) group vertices by material, and refer to the materials by name. Before the rendering resources are dispatched to the user for a draw call, those material names are matched to the user’s material handles (void* or integer) using the Named Resource Provider. In this way APEX has an efficient and meaningful way to refer to the material at runtime.
This would be the end of the section on materials, except that APEX has cases where it is its “own customer,” that is, with applications that need to render. One case is for authoring tools, another is for sample or demo applications.
Default Material Library Implementation
As a utility for tools, samples, and demos, APEX has a common generic material library class, ApexDefaultMaterialLibrary. This class can create simple materials with texture maps (currently a diffuse, bump, and normal map are supported). The texture maps may be read in from a variety of file format (to support authoring), and stores the texture maps in its own generic internal format.
The ApexDefaultMaterialLibrary is used to store material information for fractured mesh authoring in the DestructionTool, and to load materials for rendering in the Destruction module sample applications. Our SimpleRenderer code, used by our tools and demos, interfaces with ApexDefaultMaterialLibrary.
A ApexDefaultMaterialLibrary stores ApexDefaultMaterials, which in turn can refer to ApexDefaultTextureMaps. These three classes are defined in samples/common/include, and are based upon abstract base classes.
Asynchronous Simulation Interface
Simulation is controlled by the APEX scene. The Scene class implements simulate(), checkResults(), and fetchResults() methods that operate in similar fashion to the corresponding methods of the PhysX SDK PxScene class.
Just as with scene creation, these three simulation methods automatically call the corresponding PhysX SDK methods, eliminating the need for the game to make duplicate calls. Taking control of stepping the PhysX SDK simulations allows APEX modules to perform tasks before, during, and after the PhysX SDK simulation.
In order to do all this, each Scene spawns a new simulation thread, the “APEX scene thread”.
When the game thread calls Scene::simulate(), a signal is sent which unblocks the APEX scene thread. The APEX scene thread then passes through three phases. In each phase, each APEX ModuleScene (see above) is allowed to perform any asynchronous processing that it desires. Furthermore, during the second phase, the PhysX SDK simulation is allowed to proceed, by means of a call to PxScene::simulate(), followed by a call to PxScene::checkResults( block=true ).
The APEX scene in turns passes the execution context to each module in turn.
Buffered API
Warning
The buffering feature is NOT AVAILABLE in this release of APEX. Calls to the API to change the simulation state must NOT be made between the calls to simulate() and fetchResults().
Changes to the simulation state (calls to “set” methods of the API) must be made before the call to the simulate() method for the timestep(s) in which the changes are to be effective. Changes that are made after the call to simulate() are buffered until simulation completes (i.e.: until fetchResults()). Changes are applied in the order in which the API calls were originally made. This functionality is similar to what is provided by the PxD wrapper for the PhysX SDK, except that the functionality is built-in to APEX rather than being implemented as a wrapper.
Deletion of scenes is also buffered. Deleting a scene during simulation will not take effect until the simulation completes. Even though the deletion is buffered, the application must not make any more API calls that reference the scene, e.g.: don’t call fetchResults().
Calling fetchResults() before a corresponding simulate() call is allowed but does nothing. This avoids the need for the application to do a first-time check when calling fetchResults() for the previous frame immediately before simulate() for the next frame.
View Matrix and the Projection Matrix
The APEX framework provides the following interfaces to allocate, set and get the View Matrix and the Projection Matrix. Setting the View Matrix and the Projection Matrix is manditory since various APEX features such as the Level of Detail and Debug rendering require the data. This interface has been designed to support multiple sets of matrices in order to provide future APEX capabilities for multi-frame games on consoles. The following API are in the APEX scene and may be found in Scene.h.:
// first space must be allocated for the View Matrix and the Projection Matrix
// These calls must be made one time only.
const physx::PxU32 allocViewMatrix(ViewMatrixType::Enum);
const physx::PxU32 allocProjMatrix(ProjMatrixType::Enum);
// Next the transforms must be updated every time their values change
void setViewMatrix(const physx::PxMat44 & viewTransform, const physx::PxU32 viewID = 0);
void setProjMatrix(const physx::PxMat44 & projTransform, const physx::PxU32 projID = 0);
// If the View Matrix type is USER_CUSTOMIZED the following call must be made every time the View Matrix is set.
void setViewParams(const physx::PxVec3 & eyePosition, const physx::PxVec3 & eyeDirection,
const physx::PxVec3 & worldUpDirection = PxVec3(0,1,0),
const physx::PxU32 viewID = 0);
// SetProjParams must be called every time the Projection Matrix is updated.
void setProjParams(physx::PxF32 nearPlaneDistance, physx::PxF32 farPlaneDistance,
physx::PxF32 fieldOfViewDegree, physx::PxU32 viewportWidth,
physx::PxU32 viewportHeight, const physx::PxU32 projID = 0)
// setUseViewProjMatrix must be called once. Presently since only one of each matrix is supported
// this function only needs to be called once.
void setUseViewProjMatrix(const physx::PxU32 viewID = 0, const physx::PxU32 projID = 0)
NOTE: Presently only one View Matrix and one Projection matrix may be set in each APEX scene. Consequently the projID values for both of the above calls must presently be set to 0 which is the only handle that the alloc functions will return.
When a view matrix is alocated the type must be specified. It may be one of the following:
struct ViewMatrixType
{
enum Enum
{
USER_CUSTOMIZED = 0,
LOOK_AT_RH,
LOOK_AT_LH,
};
};
struct ProjMatrixType
{
enum Enum
{
USER_CUSTOMIZED = 0,
};
};
If the matrix type is set to LOOK_AT_RH or LOOK_AT_LH the eye position, direction, and world up direction are automatically computed based on a left handed or right handed world. If the type is set to USER_CUSTOMIZED then the eye position must be set using the setProjMatrix API.
If the above calls are not made or are made incorrectly the following error messages will be output to the error stream.
ERROR CODE | MESSAGE | Explanation |
---|---|---|
APEX_INVALID_PARAMETER | view matrix for viewID %d is not initialized! see allocViewMatrix() | A view matrix is required but has not yet been set. |
APEX_INVALID_PARAMETER | projection matrix for projID %d is not initialized! see allocProjMatrix() | A projection matrix is required but has not yet been set. |
APEX_INVALID_PARAMETER | Invalid ViewMatrixType! | View Matrix types must be set to one of the ViewMatrixType enumeration values. |
APEX_INVALID_PARAMETER | Invalid ProjMatrixType! | Projection Matrix types must be set to one of the ViewMatrixType enumeration values. |
APEX_INVALID_PARAMETER | instantiating more than %d view matrices is not allowed! | Presently only 1 view matrix may be allocated. |
APEX_INVALID_PARAMETER | instantiating more than %d projection matrices is not allowed! | Presently only 1 projection matrix may be allocated. |
Level of Detail
APEX Modules will frequently implement Level of Detail (LoD) systems. LoD systems are simply algorithms that determine the best way to distribute limited resources over a set of objects in a scene. They typically use criteria such as distance the object from the camera, size of the object, age of the object, or relevance to game-play, to determine the optimal distribution of resources.
APEX LOD defines resources roughly as computational load required to accomplish a task to achieve a certain level of benefit. A high fidelity APEX asset that is near the eye position has greater benefit than one that is located far from the eye position. The APEX modules that participate in the LOD resource distribution calculate sum of the benefits of all of their actors. The LOD system distributes the available resources to the various APEX module scenes based on each modules total benefits and a user supplied weighting for each of the modules. The module weighting feature is provided to allow the LOD system to be tuned based upon the priority that the game developer places on the benefit of the actors that the various modules provide.
The following APIs allows the total resource budget to be set and provides a query to determine the actual resources consumed in the last simulation frame. These functions are in APEX scene and can be found in Scene.h.:
void setLODResourceBudget(physx::PxF32 totalResource);
physx::PxF32 getLODResourceConsumed()
The LOD resource budget should be adjusted based on the system that on which the game is running. On platforms such as Microsoft Windows that support a wide range of CPU and GPU computational capabilities the budget could be set using a slider in a configuration window or perhaps by a benchmark. Since the computational capabilities of any one console type is the same the game developer could set the budget to a fixed value to achieve the desired balance between the fidelity of the simulation and frame rate.
Every APEX module that participates in the APEX LOD resource distribution system computes the benefit of its actors in a module specific way. Setting the module weights both sets the priority and normalizes the benefits. It should be noted that resources and benefit are unique quantities. The resources required to implent an actor at a certain level of fidelity is the same no matter where the actor is located relative to the eye position. However the benefit of the actor will be much higher if it is located directly in front of the user than if the actor was far away and not visible. The resource weight can also be normalized between APEX modules using the same API call. The following APIs allows the benefit and resource weights of a module to be set and retrieved and are defined in Module.h.:
// Sets scaling factors that determine how the units of benefit and resource used by
// this module relate to those of other modules.
void setLODWeights(physx::PxF32 benefitWeight, physx::PxF32 resourceWeight);
// Retrieves the currently used LOD Weights.
void getLODWeights(physx::PxF32 & benefitWeight, physx::PxF32 & resourceWeight);
Further details of how the various APEX modules implement LOD is included in the documentation for each module. Clothing LOD is described here Clothing Physical LOD, and here Clothing Actor Benefit. Destruction LOD features are described in Destruction LOD Scalable Parameters, and Destruction Debug Visualization LOD Features. APEX Particles LOD is documented in APEX Particles LOD.
Scalable Parameters
Each APEX feature has one or more Scalable Parameters. Due to the hierarchical relationship of many Authorable Objects not all Authorable Objects implement a scalable parameter. A Scalable Parameter is the specification of a range of values that is authored and saved in asset. Scalable Parameters may be found in any kind of asset, including those that are only instantiated once per-module or per-module-scene. In other words, a module may define Scalable Parameters that are “global” to the module, or that are authored on a per-scene basis.
Scalable Parameters do not inherently have anything to do with Level of Detail(LoD), but they can be used to configure various LoD settings. For example, a simple LoD system might simulate the N most important widgets in the scene, while leaving the remaining widgets static. In such a scenario N is a Scalable Parameter.
Ownership of PhysX SDK Objects
The APEX SDK provides a mechanism for the application to make queries about PhysX SDK objects, such as Actors, that have been created by APEX. For example:
void myOnContactNotify(ContactPair & pair, PxU32 events)
{
const PhysXObjectDesc *desc0 = GetApexSDK()->getPhysXObjectInfo(pair[0]);
const PhysXObjectDesc *desc1 = GetApexSDK()->getPhysXObjectInfo(pair[1]);
if (desc0)
{
if (desc0->getActor()->getOwner()->getObjectTypeID() == gModuleDestructible->getModuleID())
{
DestructibleActor *da = static_cast<DestructibleActor*> (desc0->getActor());
//...
}
}
}
Serialization
Serialization is the process by which APEX assets are saved (serialized) by an authoring tool and loaded (de-serialized) by the APEX run-time.
Every APEX Authorable Object defines a set of data that makes up the serialized asset in an NvParameterized class. The NvParameterized::Serializer class serializes and deserializes using the PxFileBuf interface for I/O. APEX provides both a file and memory buffer based implementation of PxFileBuf, retrieved with ApexSDK::createStream() and ApexSDK::createMemory[Read, Write]Stream(), respectively.
The NvParameterized::Serializer can output XML formatted assets, which are useful for smaller assets where a user might want to do hand editing or tweaking. The serializer can also output platform-specific binary assets, which are significantly smaller and deserialize much more quickly. Binary assets can be serialized for any platform and deserialized on any platform, but if the target platform matches the current platform, the Serializer::deserializeInplace() method may be used to perform in-place deserialization, which is significantly faster than the traditional method of processing an asset one parameter at a time.
Asset Preview
Before creating an asset preview, the NvParameterized preview descriptors have to be set. Specific details on descriptor parameters can be found in their respective module’s programmers guides. The following code illustrates the general steps in creating an asset preview.
Note
To modify asset preview parameters after creation, member set functions of the asset preview will have to be used, instead of modifying the NvParameterized descriptor values. Refer to individual modules programmers guides for more details.
Asset Preview Colors
Preview rendering colors are fixed, and cannot be modified by the application.
Errors and Warnings
The APEX Framework outputs the following error and warning messages using the standard APEX error stream.
ERROR CODE | MESSAGE | Explanation |
---|---|---|
APEX_INTERNAL_ERROR | Cuda Error %d | A CUDA driver API call returned the error number %d. |
APEX_INTERNAL_ERROR | Internal Error: GPU device memory allocation failure of %s in file ‘%s’ in line %i. | Memory allocation on the GPU accelerating PhysX & APEX failed. |
APEX_INTERNAL_ERROR | Internal Error: CPU host memory allocation failure of %s in file ‘%s’ in line %i. | Memory allocation on the CPU failed. |
APEX_INTERNAL_ERROR | Stream version (%d) is newer than this library (%d) | The APEX asset being deserialized has a more recent version number than the corresponding APEX module. |
APEX_INTERNAL_ERROR | Asset type name mismatch. File <%s> != asset name <%s> | The named file does not contain the expected asset type |
APEX_INTERNAL_ERROR | Stream version (%d) does not contain a universal named asset header, use a specific asset creation method to load this asset | Loading a legacy asset without the universal named asset header is not supported using the APEX generic asset loading. The module specific asset load must be used for this asset. |
APEX_INTERNAL_ERROR | Unknown authorable type: %s, please load all required modules. | Attempting to create an asset that is not supported by currently loaded modules. |
APEX_INTERNAL_ERROR | Cannot create IOS asset <%s>, its module is not loaded. | The specified IOS module must be loaded before creating an IOS asset. |
APEX_INTERNAL_ERROR | Invalid abortionRatio when doing infinite steps (%f) | The abortion ratio for iso meshes must be greater than or equal to 0 and less than 1. |
APEX_INTERNAL_ERROR | Several parts and multiple bones per vertex are conflicting! | |
APEX_INTERNAL_ERROR | several submeshes have different custom buffers | |
APEX_INTERNAL_ERROR | Conflicting parameterized objects! | |
APEX_INTERNAL_ERROR | simulation is still running | Unable to query debug visualization while simulation is running, not thread safe. |
APEX_INVALID_OPERATION | MeshDesc is not valid! | The mesh data indicates that there are submeshes but there are no submeshes. |
APEX_INVALID_OPERATION | instantiating more than %d view matrices is not allowed! | |
APEX_INVALID_OPERATION | instantiating more than %d projection matrices is not allowed! | |
APEX_INVALID_OPERATION | view-projection matrix is not yet set! see setUseViewProjMatrix() | |
APEX_INVALID_OPERATION | no vertices available | |
APEX_INVALID_PARAMETER | isoGridSubdivision must be bigger than 0, setting it to 10 | |
APEX_INVALID_PARAMETER | Wrong input stream. The provided stream has to contain %s. | |
APEX_INVALID_PARAMETER | value is out of range (%d not in [%d, %d]) | The specified parameter is out of range. |
APEX_INVALID_PARAMETER | Cannot add (%p), another vertex buffer (%p) is already assigned! | |
APEX_INVALID_PARAMETER | Cannot remove (%p), another vertex buffer(%p) is assigned | |
APEX_INVALID_PARAMETER | Invalid ViewMatrixType! | |
APEX_INVALID_PARAMETER | Invalid ProjMatrixType! | |
APEX_INVALID_PARAMETER | view matrix for viewID %d is not initialized! see allocViewMatrix() | |
APEX_INVALID_PARAMETER | projection matrix for projID %d is not initialized! see allocProjMatrix() | |
APEX_INVALID_PARAMETER | view matrix for viewID %d is not initialized! see allocViewMatrix() | |
APEX_INVALID_PARAMETER | view matrix for viewID %d is not a LookAt type! see allocViewMatrix() | |
APEX_INVALID_PARAMETER | view matrix for viewID %d is not a user-customized type! see allocViewMatrix() | |
APEX_INVALID_PARAMETER | projection matrix for projID %d is not initialized! see allocProjMatrix() | |
APEX_INVALID_PARAMETER | projection matrix for projID %d is not a user-customized type! see allocProjMatrix() | |
APEX_INVALID_PARAMETER | projection matrix for projID %d is a not a perspective FOV type! see allocProjMatrix() | |
APEX_DEBUG_INFO | ApexAssetHelper::getAssetFromName: Could not create resource ID asset: %s | |
APEX_DEBUG_WARNING | Asset <%s> already exists | The authorable object specified by <%s> has already been loaded |
APEX_DEBUG_WARNING | Deprecated interface, use ApexSDK::createAsset( physx::PxFileBuf&, const char * ) | |
APEX_DEBUG_WARNING | Deprecated interface, use ApexSDK::createAsset( AssetAuthoring&, const char * ) | |
APEX_DEBUG_WARNING | Deprecated interface, use ApexSDK::releaseAsset( Asset& ) | |
APEX_DEBUG_WARNING | Deprecated interface, use ApexSDK::createAssetAuthoring( ) | |
APEX_DEBUG_WARNING | Deprecated interface, use ApexSDK::releaseAssetAuthoring( AssetAuthoring& ) | |
APEX_DEBUG_WARNING | Removed %d physX actor descriptor(s) still remaining in destroyed ApexScene. | |
APEX_DEBUG_WARNING | Removed %d physX softbody descriptor(s) still remaining in destroyed ApexScene. | |
APEX_DEBUG_WARNING | createObjectDesc: Px Object already registered | |
APEX_DEBUG_WARNING | createObjectDesc: Px Object already registered with the given Actor | |
APEX_DEBUG_WARNING | releaseObjectDesc: Unable to release object descriptor | |
APEX_DEBUG_WARNING | Asset <%s> already exists | |
APEX_DEBUG_WARNING | No NvParameterized::Factory is registered for %s | |
APEX_DEBUG_WARNING | Some conversions for class %s were not released | |
APEX_DEBUG_WARNING | Some factories for class %s were not released | |
APEX_DEBUG_WARNING | NvParameterized::Factory does not have a valid name | |
APEX_DEBUG_WARNING | Schema checksum is different, results may be invalid | |
APEX_DEBUG_WARNING | NvParameterized ENUM value not correct: %s, substituting: %s |