DMbuffers for Video and OpenGL

Brian Beach

DMbuffers are used to store images in various formats, which can be passed from video to graphics, graphics to video, video to programs,

and programs to video.

For a general introduction to DMbuffers, see Chapter 5 of the Digital Media Programming Guide. The short summary is that an application wanting to use DMbuffers for video and graphics first creates a pool of buffers, specifying the number of buffers in the pool. Then, buffers can be

  • used to hold video input frames (in which case they are allocated by the video library),
  • used to hold rendered images (in which case they are allocated by the application and given to OpenGL),
  • used to hold decompressed JPEG images (in which case they are allocated by dmIC), or
  • used to hold images generated programmatically (in which case they are allocated and filled by the application).

The trick to using DMbuffers effectively is making sure that the format of the images in them is compatible with both the image producer and the image consumer.

Image Layouts

Before getting into the details of DMbuffers and how they are used with the digital media libraries, let's take a look at image formats. There are two basic image layouts in the O2 machine: linear and graphics.

The linear layout is familiar one. It comes in two varieties: top-to-bottom and bottom-to-top. With top-to-bottom (the natural order for video), the first pixel in a image buffer is the upper-left pixel. Following it are the rest of the pixels on the top row. After the top row is the next-to-the-top row, and so on, finally ending at the bottom-right pixel. This is the scan order for a linear image:

 

OpenGL more naturally supports the bottom-to-top order. More about this later.

The other layout is called the graphics layout. It is the internal image layout used by the CRM graphics engine. The details of the layout are not published; SGI reserves the right to change this layout at any time. The basic idea is that the image is divided into rectangular tiles, which allow the rendering engine to be more efficient. The scan order looks something like this:

 

Video on O2 supports both linear and graphics layouts. glDrawPixels and glReadPixels support only the linear layout, but images in pbuffers (see below) are always in the graphics layout.

Pixel Packing

There are three pixel formats that can be used both by video and graphics. The video library (libvl), the digital media library (libdmedia), and the graphics library (libGL) all have different names for the same things. The names are:

libvl libdmedia libGL
rgba-8888 VL_PACKING_ABGR8 DM_IMAGE_PACKING_RGBA GL_RGBA
rgba-5551 VL_PACKING_ARGB_1555 DM_IMAGE_PACKING_XRGB5551 GL_RGB5_A1_EXT
rgb-332 VL_PACKING_X444_332 DM_IMAGE_PACKING_RGB332 GL_RGB with
GL_UNSIGNED_BYTE_3_3_2_EXT

DMbuffers

The man page, DMbuffer(4), has an overview of DMbuffers, what they are, and how they are used. For our purposes here, suffice it to say that a DMbuffer is a chunk of memory, obtained from dmBufferAllocate(), vlEventToDMBuffer(), or dmICReceive(), and is used to store a single image.

Before you can use DMbuffers with OpenGL or dmIC, you must first create a DMparams that describes the format of the image. To do this, you need to know the size of the image (width and height), the pixel packing, and the image layout (graphics or linear). Here is code that creates a DMparams for linear, top-to-bottom, 640x480, RGBA-8888 images:

    DMparams* imageFormat;
    DMstatus s;

    s = dmParamsCreate( &imageFormat );
    if ( s != DM_SUCCESS )  error();

    s = dmSetImageDefaults( imageFormat,
            640, 480, DM_IMAGE_PACKING_RGBA );
    if ( s != DM_SUCCESS )  error();

    s = dmParamsSetEnum( imageFormat,
            DM_IMAGE_ORIENTATION,
            DM_TOP_TO_BOTTOM );
    if ( s != DM_SUCCESS )  error();

    s = dmParamsSetEnum( imageFormat,
            DM_IMAGE_LAYOUT,
            DM_IMAGE_LAYOUT_LINEAR );
    if ( s != DM_SUCCESS )  error();

For images in the graphics layout, the DM_IMAGE_ORIENTATION parameter is meaningless.

To set up a pool of DMbuffers that can be used with video and graphics, you do something like this:

    DMparams* imageFormat;  /* see above */
    
    VLServer server;
    VLPath path;
    VLNode node;
    
    DMbufferpool pool;
    
    /* Set up the video path before trying to create the pool. */
    
    {
        DMstatus s;
        int v;
        DMparams* poolSpec;
        int count = NUMBER_OF_BUFFERS_NEEDED_BY_APPLICATION;
        DMboolean cacheable = DM_FALSE;
        DMboolean mapped = DM_FALSE;
        
        s = dmParamsCreate( &poolSpec );
        if ( s != DM_SUCCESS )  error();
        
        s = dmBufferSetPoolDefaults( count, 0, cacheable, mapped );
        if ( s != DM_SUCCESS )  error();
        
        v = vlDMPoolGetParams( server, path, node, poolSpec );
        if ( v != 0 )  error();
        count += dmParamsGetInt( poolSpec, DM_BUFFER_COUNT );
        dmParamsSetInt( poolSpec, DM_BUFFER_COUNT, 0 );
        
        s = dmBufferGetGLPoolParams( imageFormat, poolSpec );
        if ( s != DM_SUCCESS )  error();
        
        dmParamsSetInt( poolSpec, DM_BUFFER_COUNT, count );
        s = dmBufferCreatePool( poolSpec, &pool );
        if ( s != DM_SUCCESS )  error();
        
        dmParamsDestroy( poolSpec );
    }

The count is the number of buffers in the pool. When a pool is created, it has a fixed number of buffers that never changes. To figure out the number of buffers required in the pool, you must add up all of the simultaneous requirements:

  1. Each video path to or from memory needs buffers. vlDMPoolGetParams will set BUFFER_COUNT to the minimum number required by that path. For input paths, the minimum number assumes that the application will process all VL events immediately. For output paths, it assumes that the application delivers buffers at the last possible moment.
  2. Each digital media pbuffer (see below) and texture object that is bound to a DMbuffer may require its own buffer. Add one for each of these. (In the example above, it assumes a single pbuffer owning one DMbuffer.)
  3. The application, itself, will need some number of buffers. This number might include an extra allowance for video buffers, if video events are not processed immediately. (It is usually a good idea to allow at least one extra buffer per video path.)

vlDMPoolGetParams will increase the BUFFER_COUNT in the parameter list to the minimum required for that one video path. To find out the actual number required by the path, BUFFER_COUNT should be 0 when it is called, and then queried afterwards.

A single buffer pool can be created once and then used for multiple purposes. To use one pool with multiple video paths, just call once for each video path before creating the pool. If the paths are to be used simultaneously, the buffer counts from each call should be added.

IMPORTANT: never call dmBufferMapData() on a buffer containing an image in the graphics layout. Cache coherency is not guaranteed if you do this.

Video I/O

To get video into a DMbuffer:

  1. Set up a video input path from a video node to a memory node.
  2. Create a buffer pool.
  3. Call vlDMPoolRegister
  4. Start the video transfer with vlBeginTransfer.
  5. Use vlEventRecv and vlEventToDMBuffer to get a DMbuffer holding one frame.

To take a DMbuffer that already contains an image and send it to a video output device:

  1. call vlDMBufferSend.

OpenGL: Rendering Into DMbuffers

The DMbuffer extension to OpenGL allows you to create a pbuffer, and then attach a DMbuffer to it to use as the color buffer. See the man page for glXAssociateDMPbufferSGIX (it's not in 6.3, but should be in 6.3.1).

Before you can associate a DMbuffer width a pbuffer, you must first create a special kind of pbuffer, called a digital media pbuffer. You do it like this:

        GLXFBConfigSGIX config = ...;
        int width = 640;
        int height = 480;
        GLXFBconfig int attribs [] = {
            GLX_DIGITAL_MEDIA_PBUFFER_SGIX, True,
            GLX_PRESERVED_CONTENTS_SGIX, True,
            (int) None
        };
        GLXPbufferSGIX pbuffer;
        pbuffer = glXCreateGLXPbufferSGIX(
            display,
            config,
            width,
            height,
            attribs
            );
        if ( pbuffer == None )  error();

The frame buffer configuration that is used when the pbuffer is created must match the image format of the image in the DMbuffer that will be used with it. This means that the width and the height must match, the image layout must be "graphics", and the pixel packing must match the depth of the red, green, blue, and alpha planes in the frame buffer. The allowed pixel packings are: DM_IMAGE_PACKING_RGBA, and DM_IMAGE_PACKING_XRGB1555, and DM_IMAGE_PACKING_RGB332.

The code above will create a pbuffer that does not yet have an associated color buffer, but will have all of the other buffers (depth, stencil, accumulation, etc.) that are specified in the fbconfig:

 

Allocating a DMbuffer from a pool is easy:

    DMbufferpool pool = ...;
    DMstatus s;
    DMbuffer buffer;
    s = dmBufferAllocate( pool, &buffer );
    if ( s != DM_SUCCESS )   error();

Now you have a DMbuffer and a pbuffer:

 

The next step is to associate the DMbuffer with the pbuffer. This makes

the DMbuffer be the color buffer.

    DMparams* imageFormat = ...;
    DMbuffer  buffer = ...;
    Bool ok;
    ok = glXAssociateDMPbufferSGIX(
        display, 
        pbuffer,
        imageFormat,
        buffer
        );
    if ( ! ok )   error();

As noted above, the imageFormat must specify that the layout is graphics, must match the width and height of the pbuffer, and must have a compatible pixel packing. This will make a link from the pbuffer to the DMbuffer, so that any rendering done to the pbuffer will be drawn into the DMbuffer.

 

Before you can render, of course, you must have a graphics context. This is created in the normal way, but must be created using a visual or fbconfig that is compatible with the pbuffer. Once you have a

context, you can make the pbuffer current with a context:

    GLXContext context = ...;
    Bool ok;
    ok = glXMakeCurrent( display, pbuffer, context );

This makes the context current, and now, any rending that happens will be done into the DMbuffer:

 

OpenGL: Using DMbuffers as Textures

In addition to rendering into a DMbuffer, you can also use the contents of a DMbuffer as a texture. This is done by associating the DMbuffer with a pbuffer and then using glCopyTexSubImage2DEXT to load the contents of the DMbuffer as a texture. When certain criteria are met, the image is not actually copied, but is used as a texture directly.

For this example, say you have an image in a DMbuffer that you want to use as a texture when drawing to a window. This image may have come from a video input path, or it may have been rendered with OpenGL. Either way, the procedure for using it as a texture is the same. You start with a window and a pbuffer/DMbuffer pair:

 

Calling glXMakeCurrentReadSGI will attach a context so that the glCopy calls (glCopyPixels, glCopyTexImage2D, etc.) will copy pixels from the

DMbuffer to the window or to texture:

 

At this point, any of the glCopyTexImage calls will get the image into texture memory. But, if certain criteria are met, then glCopyTexSubImage2DEXT

will not actually copy. It will free the memory holding the previous 

texture and use the DMbuffer instead:

 

The conditions that enable this "copy by reference" are (see

the man page for glCopyTexSubImage2DEXT):

  • The parameters to glCopyTexSubImage2DEXT must have these values:
    • target = GL_TEXTURE_2D
    • level = 0
    • xoffset = 0
    • yoffset = 0
    • x = 0
    • y = 0
    • width = width of pbuffer
    • height = height of pbuffer
  • The format of the already-existing texture must match the format of the pbuffer. These are the pairs of DMbuffer formats and texture formats that match:
    • DM_IMAGE_PACKING_RGBA and GL_RGBA8_EXT
    • DM_IMAGE_PACKING_XRGB1555 and GL_RGB5_A1_EXT
  • If the image layout is DM_IMAGE_LAYOUT_GRAPHICS, then the mipmap extension must be disabled.
  • If the image layout is DM_IMAGE_LAYOUT_MIPMAP, then the mipmap extension must be enabled.
  • No pixel transfer operations can be enabled.

When creating the texture to "copy" into, specifying GL_RGBA8_EXT, instead of GL_RGBA, will force the depth to be 8 bits per component. Normally, the texture will be the same depth as the current drawable.