Custom Native Rendering
Custom rendering
Almost all rendering in Carmenta Engine is done by visualizers calling drawing methods on a drawable. But sometimes, when you need to render something in an unusual way, you can make Carmenta Engine call back to your own code, to do your own rendering of an object. This can be done by adding custom visualizers or symbols (classes implementing ICustomVisualizer or ICustomSymbol) to your configuration, or by installing event handlers for the View.CustomDrawBackground or View.CustomDraw events.
In all these cases, you have access to the drawable and can call its public drawing methods. It works regardless of the renderer used by the drawable - OpenGL, DirectX or Software.
Custom native rendering
In some rare cases, you may want to go further, and render directly to the underlying graphics API, without going through the Carmenta Engine drawable. This can also be be done, by using custom native visualizers or symbols (classes implementing ICustomNativeVisualizer or ICustomNativeSymbol) to your configuration, and also in the view events mentioned above.
This is however difficult to do right, and a number of restrictions apply:
It is currently supported only for OpenGL renderers.
Custom native visualizers and symbols are supported only in 2D views. The view events are supported in both 2D and 3D views.
The custom code should not make any assumptions about the current OpenGL state when called. Carmenta Engine will try to reset the most obvious things to their default values, but can't make any guarantees about the state. It may also change between Carmenta Engine releases.
The custom code is required to restore all changes to the OpenGL state before returning control to Carmenta Engine.
The custom code is allowed to create and cache OpenGL resources such as textures or shader programs, but it must be prepared that the OpenGL context may change from one call to another, and recreate the resources when necessary.
Setting Drawable.Swap to false, and explicitly calling Drawable.Swap in the View.CustomDraw is no longer supported.
The custom code should not mix native rendering with drawing calls on the drawable.
To be able to implement this, the custom code should begin by calling the Drawable.GetNativeRendererInfo method. If this returns null, native rendering is not supported at all.
It should then check the NativeRendererInfo.NativeRendererType property to make sure that the OpenGL API is the expected. On Windows, this will always be standard, desktop OpenGL. On Android it will be OpenGL ES 3. On Linux, the default is standard, desktop OpenGL, but OpenGL ES 3 can also be used, see the Graphics renderers supported by Carmenta Engine page for more information.
If it needs to reuse OpenGL resources such as textures or shader programs that were created in a previous call, it should check that the OpenGL context has not changed, through the NativeRendererInfo.ContextId property.
The current projection and view matrices are also supplied in the NativeRendererInfo class; the custom code needs to download them to the GPU, through shader uniforms or by calling glLoadMatrix().
The following C++ code snippet shows the draw() method of a simplified ICustomNativeVisualizer implementation, rendering a single, color triangle at the center of a 2D polygon.
// Render a colored triangle at the center of every polygon
CustomVisualizerCacheHint MyCustomNativeVisualizer::draw(const FeaturePtr& feature, const DrawablePtr& drawable)
{
PolygonGeometryPtr poly = feature->getGeometryAsPolygon();
auto info = drawable->getNativeRendererInfo();
if (poly && info && info->nativeRendererType() == NativeRendererTypeOpenGL)
{
// Save state that we might modify
GLboolean vertexArrayIsEnabled;
glGetBooleanv(GL_VERTEX_ARRAY, &vertexArrayIsEnabled);
GLboolean colorArrayIsEnabled;
glGetBooleanv(GL_COLOR_ARRAY, &colorArrayIsEnabled);
bool depthTestIsEnabled = (glIsEnabled(GL_DEPTH_TEST) != GL_FALSE);
GLint depthWriteMask = 0;
glGetIntegerv(GL_DEPTH_WRITEMASK, &depthWriteMask);
bool depthWriteIsEnabled = (depthWriteMask != 0);
// Disable depth buffer
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
// Set projection matrix
auto info = d->getNativeRendererInfo();
auto proj = info->projectionMatrix();
auto view = info->viewMatrix();
double m[16];
for (int32_t i = 0; i < 16; ++i)
m[i] = proj->get(i);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadMatrixd(m);
// Set view matrix
for (int32_t i = 0; i < 16; ++i)
m[i] = view->get(i);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadMatrixd(m);
// Set vertex and color arrays
float cx = (float)poly->center().x();
float cy = (float)poly->center().y();
const GLfloat n = 2.0f;
const GLfloat tri[] =
{
cx - n, cy - n / 2,
cx + n, cy - n / 2,
cx, cy + n
};
static const GLfloat cube_color[] =
{
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f
};
if (!vertexArrayIsEnabled)
glEnableClientState(GL_VERTEX_ARRAY);
if (!colorArrayIsEnabled)
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, tri);
glColorPointer(4, GL_FLOAT, 0, cube_color);
// Draw single triangle
glDrawArrays(GL_TRIANGLES, 0, 3);
// Restore state
if (!vertexArrayIsEnabled)
glDisableClientState(GL_VERTEX_ARRAY);
if (!colorArrayIsEnabled)
glDisableClientState(GL_COLOR_ARRAY);
if (depthTestIsEnabled)
glEnable(GL_DEPTH_TEST);
if (depthWriteIsEnabled)
glDepthMask(GL_TRUE);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
return CustomVisualizerCacheHintNever;
}