25.1. Device, context and memory abstraction

Buffer objects mechanism

Main concept

The buffer object classes provide a powerful and convenient abstraction for managing data objects stored in various kinds of memory on various computing (or rendering) devices. The main behavior of the buffer objects is to abstract access to the memory using a generic API. This API manages the calls to the hardware driver and provides useful methods to manage the allocated memory. All the buffer object classes must provide methods to copy data to a CPU buffer and from a CPU buffer. This allows basic data interchange with custom buffer object types. If a custom SoBufferObject SoBufferObject SoBufferObject is defined it should always be possible to read and write data in this type of buffer.

Open Inventor 8.0 provides three kinds of Buffer Objects:

CPU buffer objects

The CPU buffer objects provide methods to manage data located in the system memory. This type of buffer doesn’t need a “valid context”, although SoCpuContext SoCpuContext SoCpuContext exists for consistency. If there is no valid SoCpuContext SoCpuContext SoCpuContext a default one is used. SoCpuBufferObject SoCpuBufferObject SoCpuBufferObject objects are valid in any context.

OpenGL buffer objects

The OpenGL buffer objects are new OpenGL extensions mainly used for high performance and easier modification of data located in the GPU’s “texture” memory. They are nothing but arrays of bytes located in the graphic card main memory. Each SoGLBufferObject SoGLBufferObject SoGLBufferObject can be one of the following OpenGL buffer object types:

  • VBO: Vertex Buffer Object: This buffer can contain vertices, normals and texture coordinates.

  • PBO: Pixel Buffer Object: This buffer improves performance for texture data upload or download between the CPU and the GPU. It provides useful mechanisms to speed up the download and the upload by removing one data copy during the TexImage and the ReadPixels methods calls. It can also be used to do asynchronous ReadPixels to avoid the lock usually introduced by the call to ReadPixels.

  • TBO: Texture Buffer Object: This buffer provides data to shader programs. It can be regular texture data or any vertex coordinates, normal coordinates, vertex attributes, etc.

The main difference with the base SoBufferObject SoBufferObject SoBufferObject class is the addition of the methods bind() and unbind() which are useful for some OpenGL functions which require the binding of those buffers in the OpenGL state.

OpenGL buffers allow the transfer of data, during rendering, between each step of the rendering pipeline without transferring the data back to system memory.

CUDA buffer objects

The CUDA buffer objects allow the management of memory located on the video card. The main purpose of this class is to provide buffer objects for CUDA computing. All the CUDA algorithms use CUDA buffer objects for computing.

Understanding the mapping API

All the buffer object types are context dependent. This allows a generic API for buffers which need device contexts (e.g. an SoGLBufferObject SoGLBufferObject SoGLBufferObject needs an OpenGL context).

Global architecture

Figure 25.1. Global architecture



When a buffer object is created it needs a valid context. It ensures that the buffer is valid in this context. All the methods in the buffer object classes assume that the current context is the one used when the buffer object was created.

The map() and unmap() methods can be used to map a buffer object allocated in a specific context to a buffer object in a different context or even to a different buffer object type. They take care of the contexts. If the buffers have the same type but use different contexts, the map and unmap methods will do the copy using the appropriate driver API.

Data input/output

The SoBufferObject SoBufferObject SoBufferObject classes provide methods for data input and output. All the buffer classes must provide a method to copy data from or to an SoCpuBufferObject SoCpuBufferObject SoCpuBufferObject . Those methods are named memcpy() and copyToCpuBuffer().

This mechanism provides a reliable way to copy data to any type of buffer, because memcpy and copyToCpuBuffer are mandatory.

Follow these general steps to put data in a buffer of type X:

  • Create a SoCpuBufferObject SoCpuBufferObject SoCpuBufferObject (assume it is called cpuBuffer)

  • Set the size of the buffer: cpuBuffer.setSize( sizeInBytes )

  • Map the buffer to a valid pointer using the method cpuBuffer.map(). It returns a void* pointer usable like any C pointer.

  • Unmap the buffer: cpuBuffer.unmap()

  • Bind the destination buffer context: destBuffer->getContext()->bind();

  • Do the actual copy of the data: destBuffer->memcpy( &cpuBuffer );

  • Unbind the context: destBuffer->getContext()->unbind();

Integration with standard classes

Some standard Open Inventor classes have been enhanced to allow direct use of buffer objects to improve performance and/or integrate with computation:

  • Images interface

    The field classes SoSFImage and SoSFImage3 have been modified to support SoBufferObject SoBufferObject SoBufferObject . They use SoBufferObject internally and use the mapping mechanism for the pointer based methods. It’s also possible to read and write the content of those fields through the read and write actions.

  • Textures interface

    The nodes SoTexture2 and SoTexture3 support buffer objects directly in their internal mechanism in order to provide them directly to OpenGL. For this feature the texture element was also modified to support SoBufferObject objects. The support for buffer objects in these nodes is through the modification of SoSFImage and SoSFImage3.

  • Shapes interface

    The new node SoBufferedShape SoBufferedShape SoBufferedShape supports buffer objects directly for providing vertices, normals, colors, etc. Accordingly the shape data may be stored either on the CPU or GPU. The input buffer objects are mapped to the right kind of buffers directly in the method which uploads the texture to OpenGL.

The CUDA API in Open Inventor

The CUDA technology introduced by NVIDIA has been integrated in Open Inventor as a separate module. The main class for this interface is SoCuda SoCuda (Inventor/cuda/SoCuda.h). It provides useful methods to register and unregister modules and to resolve CUDA functions.

The module provides the following features:

The CUDA module is a separate module which directly uses the nvcuda.dll provided by the NVIDIA driver. There is no need to use or deploy any other toolkit or SDK to use CUDA kernels with an Open Inventor application. Of course when you need to build your CUDA kernel binary file you must install the CUDA toolkit from the NVIDIA website.

Device information

Each device type supported by Open Inventor provides a class that allows you to get information for each such device present in the system. All these classes are derived from the class SoDevice SoDevice SoDevice .

The supported devices are CPU, OpenGL, CUDA and OpenCL. Each of them inherits from the SoDevice SoDevice SoDevice class and implement the following common methods:

getTotalMemory()
getAvailableMemory()
getLogicalUnits()
getDriverVersion()
getDeviceName()
getDevicesCount()
getDevice()

Some device type dependent information can also be retrieved, like SSE support level for a CPU and number of SPU for a CUDA or OpenCL device. See the reference manual for a complete list.

Example 25.1. Retrieving information from the CPU

// Init devices class
SoCpuDevice::initClass();
SoOpenGLDevice::initClass();
SoCudaDevice::initClass();
SoOpenCLDevice::initClass();
// loop over detected devices
int numDevices = SoDevice::getDevicesCount();
for ( int i = 0; i < numDevices; i++ )
{
  SoDevice* dev = SoDevice::getDevice( i );
  SoCpuDevice* cpuDevice = dynamic_cast<SoCpuDevice *>(dev);
  if ( cpuDevice != NULL )
    std::cout << *cpu << std::endl;
}
// loop over detected devices
int numDevices = SoDevice.GetDevicesCount();
for ( int i = 0; i < numDevices; i++ )
{
  SoCpuDevice cpuDevice = SoDevice.GetDevice( i ) as SoCpuDevice;
  if ( cpuDevice != null )
    Console.WriteLine( cpuDevice.ToString() );
}
            
          


A complete example illustrating this functionality is provided in

$OIHOME/src/Inventor/examples/Features/DevicesProperties/main.cxx

Limitations:

Device contexts

Main concept

In general, buffer objects are only valid within a specific context. A context stores all the status, configuration, memory buffer locations, etc. Many contexts can be created and used but generally each context can be used in one thread at a time. That means you must release a context from a thread before using it in a different one.

CPU device contexts

Currently CPU devices don’t require a context. The whole memory is accessible from the program in all the threads. The SoCpuContext SoCpuContext SoCpuContext class is provided in order to keep a generic API. During the first allocation of an SoCpuBufferObject SoCpuBufferObject SoCpuBufferObject a CPU device context is created for convenience.

OpenGL device contexts

The OpenGL contexts store all the information about the OpenGL state, buffer objects, textures, etc. In most cases they can be shared in order to use one single copy of the data in two threads. The class SoGLContext SoGLContext SoGLContext provides methods to create, manage and destroy OpenGL contexts.

Starting with Open Inventor 8.1 the SoGLContext SoGLContext SoGLContext class is used to manage all the OpenGL objects. Any OpenGL object created in Open Inventor is linked to an SoGLContext SoGLContext SoGLContext . This means that if an SoGLContext SoGLContext SoGLContext is released, all the OpenGL objects associated with this context will be released. It also means that the viewer classes use SoGLContext SoGLContext SoGLContext to setup rendering and to perform the actual rendering. The viewer classes now provide methods to bind and unbind the SoGLContext SoGLContext SoGLContext associated with the viewer. In order to call OpenGL functions the viewer must bind and unbind the viewer context. Basically it means that there is no active context outside the rendering traversal and if the application needs a context it must explicitly bind it.

SoGLContext SoGLContext SoGLContext objects are created according to specific formats that describe the configuration of the OpenGL rendering format like size of the depth buffer, color format, anti-alisaing configuration, etc.

The new SoGLFormat SoGLFormat SoGLFormat class manages this configuration. It is possible to change the current configuration of a viewer by setting a new SoGLFormat SoGLFormat SoGLFormat .

The CUDA contexts

The CUDA contexts are very similar to OpenGL contexts, meaning that all the buffers and all the modules loaded in a context are specific to that context. To create a buffer object or perform computation on a buffer object the CUDA module assumes that there is a valid context.

The class SoCudaContext SoCudaContext provides all the methods to create, bind, unbind, and destroy a CUDA context.

Note: A CUDA context can only be used in one thread at a time. That means if one thread tries to bind the context it will be locked until the actual owner of the context unbinds it.

CUDA context and CUDA buffer objects

All the methods in the class SoCudaBufferObject SoCudaBufferObject need a valid active CUDA context, except the map and unmap methods which are able to automatically manage all the context issues. If a buffer object is created in a CUDA context, all the methods are usable only if the current context is the context which was active when the buffer was created. To use a buffer in a different context the user must use the mapping mechanism to map the buffer into a separate buffer created in a different context (See SoCudaBufferObject SoCudaBufferObject in the Open Inventor documentation for more information).