The Vish Visualization Shell 0.3
Vish
The Render Anemone

How to use the Render Anemone

The Render Anemone is an API that bundles everything together that is needed for rendering call such as OpenGL's glDrawArray, whereby it encapsulates the actual rendering API to allow independence from OpenGL.

Getting Started

The render Anemone is designed for caching of data transfers. The first step is to send data to an Anemone object:

VRenderContext&Context;
RefPtr<Anemone> myAnemone = new Anemone{};
{
MemVector<Eagle::point3> ScreenVertices(4);
ScreenVertices[ 0] = {-1.0, -1.0, 0.0 };
ScreenVertices[ 1] = {+1.0, -1.0, 0.0 };
ScreenVertices[ 2] = {-1.0, +1.0, 0.0 };
ScreenVertices[ 3] = {+1.0, +1.0, 0.0 };
myAnemone->insert( Context.createCoordinates( ScreenVertices ) );
}
myAnemone->insert( Context.drawPrimitives( RenderBasin::TRIANGLE_STRIP ) );

This sets up an Anemone using virtual member functions from a VRenderContext, which may implement those via OpenGL or another graphics API. The second step is to actually invoke the rendering:

myAnemone->wave();

The Anemone will remember all properties (the so-called Tentacles) from the setup and subsequent code may just invoke the Anemone::wave() function to render again.

Remembering an Anemone

A Render Anemone may be created simply via the new operator as in

RefPtr<Anemone> myAnemone = new Anemone{};

which is fine for code that only temporarily requires a Render Anemone during render time, but in general it is preferable to create a Render Anemone for more persistent usage across multiple rendering calls. This allows to setup the Render Anemone once while subsequent render calls merely invoke the Anemone::wave() function.

A VObject allows to define a local State object to hold context-dependent data. To place a RefPtr<Anemone> there will not work well because this State object will be destructed outside of the rendering thread without an OpenGL context available. Instead, the lifetime of an Anemone must be handled via an AnemoneCreator as in:

struct MyState : State
{
myAnemoneCreator;
};
StrongPtr< Object, ObjectBase > RefPtr
Convenience template typedef to use RefPtr instead of StrongPtr.
Definition RefPtr.hpp:776

Then use the Anemone Creator with the Context Seagrass cache to store and handle the lifetime of the Render Anemone:

bool MyRenderer::render(VRenderContext&Context)
{
RefPtr<MyState> S = myState(Context);
if (!S->MultiviewAnemoneCreator)
S->MultiviewAnemoneCreator = new AnemoneCreator<>( Context.getSeagrass() );
RefPtr<Anemone> myAnemone = S->MultiviewAnemoneCreator->create();
if (myAnemone->empty() )
{
... setup the anemone ...
}
// actual rendering
myAnemone->wave();
}

Note that the AnemoneCreator ceases to exist, then also the corresponding Anemone will cease to exist, but not immediately, rather at the next rendering call when the Seagrass Cache has the ability to manage its unused Anemones.

Adding a Shader Program

To add some shaders programs, GLSL source code may be conveniently defined in the C++ source code like this:

GLSL_SHADER(my_vertex_shader) =
R"SHADER(
#version 450
layout(location = 0) in vec4 inVertex;
void main()
{
gl_Position = inVertex;
}
)SHADER"
;
GLSL_SHADER(my_fragment_shader ) =
R"SHADER(
#version 450
out vec4 fragmentColor;
void main(void)
{
fragmentColor = vec4(1,0,0,0);
}
)SHADER"
;

Basically shader code is just a string, but the GLSL_SHADER macro defines some line number information that is useful for debugging shaders. That context information is used by the GLSL_SOURCE macros when creating a GLProgram that can be used as a tentacle to be added to the Anemone:

RefPtr<Program> Program = Context.createProgram( GLSL_SOURCE( vertex_shader ),
GLSL_SOURCE( fragment_shader ) );
myAnemone->insert( myProgram );

Setting Shader Uniforms

Two groups of functions exist to send values to shader uniforms:

  1. Immediate setting via Program::setUniform*() functions
  2. Delayed setting via Program::setValue*() functions The first group of functions is close to OpenGL in its functionality: A Program must be in use ( Program::use() has to be called ), then on this Program a value can be set.

The second group of functions is more advanced: The Program maintains a set of variables with name, type and value. The Program::setValue() functions adds data there, but does not invoke any OpenGL (or similar) call yet. The Program::activateUniforms() call then sets all uniforms at once. However, this function does not need to be called explicitly but is performed by the Anemone::wave() when a Painter is activated. A major benefit is that the set of ProgramVariables may be updated any time, even without an OpenGL context being available or in the same thread, but the respective Program will always "see" the most recent values during drawing.

The most convenient way to set a uniform value in a shader via the setValue() API is provided by the array access, enabled by the Program's base class ProgramVariables:

RefPtr<Program> myProgram = ...;
int NumViews = 2;
(*myProgram)["NumberOfViews"] = numViews;

The type of the uniform will be figured out automatically and implicitely ( using std::variant<> internally ), but must match the type used in the Program shader, including signedness of integers (int vs uint ).

The Render Stream

The Render Stream API is an API in the Anemone library to allow coupling of VObject input parameters or functions with shader variables. It uses a syntax similar to the Standard C++ I/O Stream library.

Setting constants

Simplest verson: constant value.

RenderAnemone << Context << "myVariable" << 2.0;

Note that setting a boolean is enabling / disabling an OpenGL state:

RenderAnemone << Context << "GL_LIGHTING" << true;

(Remark: supported OpenGL flags are implemented in VGLRenderContext::Enable(), which is both a subset and extension of the full OpenGL functionality).

Coupling Wizt::VObject inputs with render parameters

Couple a uniform variable with an object's input slot using the name of the input slot. Given an VObject's input slot:

in<Wizt::Color> inColor;

this allows for a syntax such as:

RenderAnemone << Context << inColor;

(Note that in<> is a member template of Wizt::VSlotContainer::in which is available in child classes of VObject . Other classes require full class name specification. ).

The name of the shader variable must be identical to the name of the input slot. This name can also be specified explicitly to use a different name in the shader code than the object's input:

RenderAnemone << Context << "MyColor" <<inColor;

Invoking functions to compute render parameters

More complex version: via a context-relative member function:

double computeFloatVariable(VRenderContext&V) const;
...
RenderAnemone << Context << "myFloatVariable" << &MyObject::computeFloatVariable << this;

Advanced version: transforming the value of an object's type slot:

static double computeDoubleValueFromInteger(int i);
RenderAnemone << Context << "myFloatVariable" << &computeDoubleValueFromInteger << inIntegerInput;

Via a member function that adjust a value considering a Context

double adjustVariable(const double&Value, VRenderContext&Context) const;
RenderAnemone << Context << "myFloatVariable" << &MyObject::adjustVariable << this << 5.0;

Couple a Vish Object's input slot with a uniform shader variable via some functor, for instance a function:

in<Options> inOptions;
static int PolygonMode(const Options&Op)
{
if (Op("wireframe")) return 1;
else return 2;
}

, allowing for the following syntax:

RenderAnemone << Context << "glPolygonMode" << &PolygonMode << inOptions;

The functor's return type must correspond to the uniform shader variable, the functor's parameter must correspond to the input slot's type.

Finally, we can couple an Anemone's update function with a functor, in particular a lambda function:

RenderAnemone << Context << "Observer" >>
[](const VRenderContext&C) { return C.getCameraSettings().Observer; };

Note the special syntax >> used here.