OSPRay v2.0 Porting Guide

OSPRay v2.0.0 introduces a number of new features and updates, as well as some API changes. This guide is intended as an introduction to the new features and the concepts behind them, and as a guide to porting applications using OSPRay v1.8.x to v2.0.0.

Parameters

Setting parameters

OSPRay traded having a limited number of parameter types (with a unique function signature for each type) by instead having a single generic function that takes any type found in the OSPDataType enum. The new function signature is as follows:

void ospSetParam(OSPObject, const char *name, OSPDataType type, const void *mem)

This function takes the address of the value being set, where care should be taken when setting pointer-based types. For example, consider the following C-array:

float myValues[] = {0.f, 1.f, 2.f}

If the application wants to set these values as an OSP_VEC3F, note that the const void *mem parameter to ospSetParam should point to what is the address of what would be a vec3f. Thus either of the following are valid:

void ospSetParam(object, "some_parameter", OSP_VEC3F, &myValues[0]);

or

void ospSetParam(object, "some_parameter", OSP_VEC3F, myValues);

The second variant relies on implicit casting from float[] to float *, which will point to the address of the first value. This then gets casted to a vec3f to be set on the target object.

Some of the ospSet* functions were preserved as utility wrapper functions outlined later in this document (and can be found in ospray_util.h).

OSPDataType type checking

Where it was previously accepted to create data of generic type OSP_OBJECT to represent a list of any object type, the OSPDataType must now match the object(s) it contains.

Objects

New Object Hierarchy

OSPRay objects, such as OSPGeometry and OSPVolume, have a new hierarchy that affords more control over geometry and volume transformation and instancing in the scene.

Previously, the workflow was to create an object, fill it with the necessary parameters, and then place the object into an OSPModel via, for example, ospAddGeometry. An example is shown below using the C API.

OSPGeometry mesh = ospNewGeometry("triangles");
// set parameters on mesh
ospCommit(mesh);

OSPModel world = ospNewModel();
ospAddGeometry(world, mesh);
ospRelease(mesh);
ospCommit(world);

In OSPRay v2.0.0, there is now an OSPWorld, which effectively replaces the old OSPModel. In addition, there are now 3 new objects that exist “in between” the geometry and the world: OSPGeometricModel, OSPGroup, and OSPInstance. There is also an OSPVolumetricModel equivalent for working with OSPVolume objects.

The new workflow is shown below. Note that calls to ospRelease() have been removed for brevity.

// create a geometry
OSPGeometry mesh = ospNewGeometry("triangles");
// set parameters on mesh
ospCommit(mesh);

// put the geometry in a model (a geometry can exist in more than one model)
OSPGeometricModel model = ospNewGeometricModel(mesh);
ospCommit(model);

// put the geometric model(s) in a group
OSPGroup group = ospNewGroup();
OSPData geometricModels = ospNewSharedData1D(&model, OSP_GEOMETRIC_MODEL, 1);
ospSetObject(group, "geometry", geometricModels);
ospCommit(group);

// put the group in an instance (a group can be instanced more than once)
OSPInstance = ospNewInstance(group);
ospCommit(instance);

// put the instance in the world
OSPWorld world = ospNewWorld();
OSPData instances = ospNewSharedData1D(&instance, OSP_INSTANCE, 1);
ospSetObject(world, "instance", instances);
ospCommit(world);

While this looks more complex at first, the new hierarchy structure provides more fine control over appearance information and instance transformations.

In OSPRay v1.x, geometries and volumes contained both structural and appearance information which limited their reuse in other objects. For example, the volume’s transfer function can now be different between an isosurface, slice, and rendered volume all in the same scene without duplicating the actual volume itself.

OSPGeometry and OSPVolume

OSPGeometry and OSPVolume contain the physical data represented by the object. For geometries, this is the intersectable surface. For volumes, it is the scalar field to be sampled.

OSPGeometricModel and OSPVolumetricModel

OSPGeometricModel and OSPVolumetricModel contain appearance information about the geometry or volume that they hold. They have a one-to-N relationship with OSPGeometry and OSPVolume objects (i.e. a geometry or volume can exist in more than one model), but commonly exist as one-to-one. This could be used to create multiple copies of a geometry with different materials, for example.

OSPGeometricModels can hold primitive color information (e.g. the color used for each sphere in a Spheres geometry), a material or list of materials, and primitive material IDs (i.e. indexes into the list of materials if it is used).

OSPVolumetricModels can hold transfer functions.

OSPGroup

OSPGroup objects contain zero or more OSPGeometricModel and OSPVolumetricModel objects. They can hold geometries and volumes simultaneously.

This is useful to collect together any objects that are logically grouped together in the scene.

OSPInstance

OSPInstance contains transformation information on an OSPGroup object. It has a one-to-one relationship with OSPGroup.

This means that each OSPInstance contains exactly one OSPGroup. Similar to models and groups, a single OSPGroup may be placed into multiple OSPInstace objects. This allows for instancing of multiple objects throughout the scene, each with different transformations.

OSPInstance objects holds an affine transformation matrix that is applied to all objects in its group.

OSPWorld

OSPWorld is the final container for all OSPInstance and OSPLight objects. It can contain one or more instances and lights. The world is passed along with a renderer, camera, and framebuffer to ospRenderFrame to generate an image.

void ospRetain(OSPObject)

To allow the user greater control over the lifetime of objects, a new API ospRetain has been introduced. This call increments an object’s reference count, and can delay automatic deletion.

Updated Public Parameter Names

OSPRay v2.0.0 has updated public parameter names (the strings used in ospSetParam) to a more consistent naming convention. OSPRay now will print a warning (visible if debug logs are enabled) if a parameter provided by the user is not used by an object. This can help catch cases where applications are using parameter names from OSPRay v1.8.5 or mistyped names. Some objects have required parameters. In these cases, OSPRay will invoke the error callback indicating which object and which parameter.

OSPRay now universally uses the camelCase naming convention for all object types and parameter names. Furthermore, parameter names which are data arrays now use singular names to indicate what the elements are, and parameters which form a logical “struture-of-arrays” are communicated via the "[structure_name].[member_name]" naming convention.

For example, the "opacities" array for linear transfer functions is now named "opacity".

Finally, some parameters have changed from being string-based to enum-based. All enums can be found in OSPEnums.h, where their usage can be found in the main API documentation.

ospRenderFrame

ospRenderFrame has changed in two ways. The signature has changed from:

float ospRenderFrame(OSPFrameBuffer, OSPRenderer,
                     const uint32_t frameBufferChannels = OSP_FB_COLOR);

to

OSPFuture ospRenderFrame(OSPFrameBuffer, OSPRenderer, OSPCamera, OSPWorld);

And, notably, it is no longer blocking. Two new calls ospIsReady, and ospWait are available to manage synchronization.

Utility Library

As a convenience, a lightweight utility library has been provided to help users port from the previous versions and reduce boilerplate code. This set of additional API calls are all implemented in terms of the core API found in ospray.h, where they can be found in ospray_util.h. Their definitions are compiled into libospray (the ospray::ospray CMake target) and are compatible with any valid device implementation.

OSPData and Parameter helpers

The core OSPRay API has been simplified by removing many of the type specializations from the data and parameter set calls. The utility library provides wrappers to the familiar calls listed below:

ospSetString(OSPObject, const char *n, const char *s);
ospSetObject(OSPObject, const char *n, OSPObject obj);

ospSetBool(OSPObject, const char *n, int x);
ospSetFloat(OSPObject, const char *n, float x);
ospSetInt(OSPObject, const char *n, int x);

ospSetVec2f(OSPObject, const char *n, float x, float y);
ospSetVec3f(OSPObject, const char *n, float x, float y, float z);
ospSetVec4f(OSPObject, const char *n, float x, float y, float z, float w);

ospSetVec2i(OSPObject, const char *n, int x, int y);
ospSetVec3i(OSPObject, const char *n, int x, int y, int z);
ospSetVec4i(OSPObject, const char *n, int x, int y, int z, int w);

ospSetObjectAsData(OSPObject, const char *n, OSPDataType type, OSPObject obj);

OSPRay v1.x calls to ospSetData have been replaced with ospSetObject. Convenience wrappers have also been provided to specialize ospNewData, and the new ospNewSharedData and ospCopyData APIs.

ospNewSharedData1D(const void *sharedData, OSPDataType type, uint32_t numItems);
ospNewSharedData1DStride(const void *sharedData, OSPDataType type, uint32_t numItems, int64_t byteStride);
ospNewSharedData2D(const void *sharedData, OSPDataType type, uint32_t numItems1, uint32_t numItems2);
ospNewSharedData2DStride(const void *sharedData, OSPDataType type, uint32_t numItems1, int64_t byteStride1, uint32_t numItems2, int64_t byteStride2);
ospNewSharedData3D(const void *sharedData, OSPDataType type, uint32_t numItems1, uint32_t numItems2, uint32_t numItems3);

ospNewData1D(OSPDataType type, uint32_t numItems);
ospNewData2D(OSPDataType type, uint32_t numItems1, uint32_t numItems2);

ospCopyData1D(const OSPData source, OSPData destination, uint32_t destinationIndex);
ospCopyData2D(const OSPData source, OSPData destination, uint32_t destinationIndex1, uint32_t destinationIndex2);

Object Usage Simplification

To simplify setting data onto an object, if there is only a single data item, the wrapper ospSetObjectAsData permits the user to assign this item without first creating a data object containing that item.

For example:

OSPData geometricModels = ospNewSharedData1D(&model, OSP_GEOMETRIC_MODEL, 1);
ospSetObject(group, "geometry", geometricModels);
ospRelease(geometricModels);

simply becomes:

ospSetObjectAsData(group, "geometry", OSP_GEOMETRIC_MODEL, model);

Rendering helpers

While ospRenderFrame is now asynchronous, some users will prefer the original blocking behavior that returns the frame variance. The utility library provides a wrapper to this functionality:

float ospRenderFrameBlocking(OSPFrameBuffer fb,
                             OSPRenderer renderer,
                             OSPCamera camera,
                             OSPWorld world)