The Render Anemone is a programming concept to encapsulate rendering functionality in a cacheable entity with properties that resemble the style of efficient modern GPU programming. It hides actual OpenGL function calls from the rendering code and routes them through an instance that implements these abstract Anemone API to concrete calls. Such an implementation of the Anemonia API may be provided by

  • OpenGL (implemented in ocean/GLVish/ )
  • WebGL (partially implemented in ocean/Argyroneta/ )
  • DirectX (not implemented)
  • PDF Writing (currently not implemented via the Anemonia API)
  • Remote Rendering (similar to GLX, but higher level of abstraction; not implemented)
  • anything else…

Using the Anemonia API allows render objects to be implemented independently from the graphics backend and once for all the diverse output possibilities. Yet the Anemonia API is not only highly efficient with negligible overhead to direct OpenGL function calls, it furthermore provides an advanced caching mechanism that allows to re-use and update Anemone objects and their properties. Moreover, the Anemonia API is compatible with the Fiber Bundle Library and thus allows very easy data transfer to the GPU for rendering purposes. Complexity, version dependencies and hardware-specifics in OpenGL programming are hidden in the backend of the Anemonia API, thus simplifying application code in actual render objects.

How it works

C++ Code in a Render Object make use of an Object instantiated from class Anemone. To this object certain properties can be attached, most notably vertex buffer objects, render parameters, textures and shader programs. These properties that are attachable to an Anemone object are called Tentacles (which come with different categories). Once an Anemone object has been constructed, it is ready for rendering via the Anemone::wave() function.

Tentacles are abstract base classes. Their implementation provides certain functionality. Application code can implement such tentacle objects itself, in which case the Tentacle will be able to issue OpenGL calls directly and the Anemone is used as mere management for such Tentacle objects. The RenderBasin base class provides a set of virtual functions to create tentacles. An instance of the RenderBasin class is provided to each Render Object during the render() call, such that these Render Objects may request creation of a Tentacle from an instance of the RenderBasin. Such would be implemented for instance in an OpenGL viewer which creates OpenGL-specific tentacles. Alternatively some WebGL viewer would provide a Tentacle that implements the same functionality using WebGL. In any case, a Render Object that implements its functionality entirely using the Anemonia API will be shielded from implementation issues as in OpenGL calls. If a tentacle can be created or not may still depend on the current hardware capabilities, however, an implementation of the RenderBasin may be able to emulate some functionality to some extent.

Examples

The following C++ excerpt demonstrates how to render the edges of box as lines from two points given using the Anemonia API. It constructs the eight corner vertices from the two points and sets up the 24 edges of a cube for rendering. Additional settings such as colorization are omitted in this example for the sake of simplicity.

using namespace Eagle::PhysicalSpace; 

void renderBox(RenderBasin&Context, const point&P0, const point&P1)
{
point P000 ( P0.x(), P0.y(), P0.z() ),
       P001 ( P0.x(), P0.y(), P1.z() ),
       P010 ( P0.x(), P1.y(), P0.z() ),
       P011 ( P0.x(), P1.y(), P1.z() ),
       P100 ( P1.x(), P0.y(), P0.z() ),
       P101 ( P1.x(), P0.y(), P1.z() ),
       P110 ( P1.x(), P1.y(), P0.z() ),
       P111 ( P1.x(), P1.y(), P1.z() );

RefPtr<Anemone> BBoxAnemone = new Anemone();
MemVector<point> BoxVertices(24);

       BoxVertices[ 0] = P000; BoxVertices[ 1] = P010;
       BoxVertices[ 2] = P010; BoxVertices[ 3] = P011;
       BoxVertices[ 4] = P011; BoxVertices[ 5] = P001;
       BoxVertices[ 6] = P001; BoxVertices[ 7] = P000;

       BoxVertices[ 8] = P100; BoxVertices[ 9] = P110;
       BoxVertices[10] = P110; BoxVertices[11] = P111;
       BoxVertices[12] = P111; BoxVertices[13] = P101;
       BoxVertices[14] = P101; BoxVertices[15] = P100;

       BoxVertices[16] = P000; BoxVertices[17] = P100;
       BoxVertices[18] = P010; BoxVertices[19] = P110;
       BoxVertices[20] = P011; BoxVertices[21] = P111;
       BoxVertices[22] = P001; BoxVertices[23] = P101;

       BBoxAnemone->insert( Context.createCoordinates( BoxVertices ) );
       BBoxAnemone->insert( Context.drawPrimitives( RenderBasin::LINES ) ); 
       BBoxAnemone->wave(); 
}

Note that this code contains no direct OpenGL calls. However, OpenGL calls can be inserted at any point.

Once constructed, the Render Anemone, i.e. the BBoxAnemone object in the example, can be reused at any time within a given OpenGL context. It will reproduce the actions performed before, but no data is loaded or accessed from the CPU at this point of a repeated wave() call.

Caching

A Render Anemone can be created each time when render() is called. However, Anemone creation is inefficient as it implies uploading possibly large data from the CPU to the GPU. Instead, a Render Anemone can be created just once and re-used by merely calling its wave() member function. The Render Anemone keep track of references to the data as uploaded to the GPU via their OpenGL ID’s and merely re-activates them on subsequent wave() calls. Thus application code should store a pointer to the Render Anemones once created and reuse them as good as possible.

 

However, application code must not simply store a reference pointer to the created Render Anemone because the destruction of a Vish Object happens outside of an OpenGL Context. The availability of an OpenGL Context however is a requirement for destructing Render Anemones. To facilitate proper destruction of Render Anemone, the class AnemoneCreator is provided as an active handle to Render Anemones. It works similar to a reference pointer, but does not immediatly destruct Render Anemones when destructed itself, but schedules them for destruction to the Seagrass cache management. The Seagrass resembles a list of Anemones associated with an OpenGL Context and will destruct the Render Anemones at the next render() to release resources on the GPU as occupied by one or more Render Anemone. This happens asynchroneously and independently of the destruction of Vish Objects.

 

When re-using a previously created Render Anemone, the values of certain parameters, such as uniform variables for Shaders, can be modified without need to re-create the entire Anemone. For this to work, the application code must retain a pointer to the respective Render Parameter of the Render Anemone and call the Tentacle’s modify() function. This call does not require an OpenGL Context and can be performed any time. The Render Anemone will update an internal copy of the numerical value and send it to OpenGL at the next render call.

 

To ease this process, the Anemonia Library provides a convenience API that allows coupling of Vish input parameters with Render Parameters via operator overloading. The application code then looks similar to IO Streams, for instance:

 RenderAnemone << Context << "MonochromeColor" << inColor;

Summary: Vish Objects must never store reference pointers to Render Anemones (e.g. in their state objects), but only to Anemone Creators. Anemone Creators can be destructed savely without an OpenGL Context (for instance, in a Vish Object’s update() function or its destructor), but a Render Anemone must not be accessed without an OpenGL Context. Vish input parameters can be coupled to Render Parameters via Anemone Render Streaming.

 

Application

See Rendering Fiber Bundle Objects using the Render Anemone