Today, we are pleased to announce the first stable release of Forge - a high performance OpenGL visualization library for C/C++ applications that use CUDA/OpenCL as computational backbone.

In Forge v1.0, two kinds of renderings are possible. One is an Image and the other is a Chart. Chart by itself doesn't render anything other than axes and some labels on it. Chart has to be embedded with one or more of the following graphs to render the respective renderings.
<ul>
<li> 2D Line Plots </li>
<li> 3D Line Plots </li>
<li> Surfaces </li>
<li> Histograms </li>
<li> Vector fields, both 2D and 3D</li>
<li> Images </li>
</ul>
First, we shall go through the details of how to create a window using Forge and later illustrate the rest of the functionality from v1.0 using some sample code snippets and images.
<h5>How to Create a Window</h5>
The code used to create a window is identical in all the examples that illustrate inter-operation of CPU/CUDA/OpenCL with OpenGL. Therefore, the following window creation code is not repeated in later code snippets where only the draw calls and data-update steps will be shown.

<pre><code>#include <forge.h>

int main(void)
{
    /*
     * First Forge call should be a window creation call
     * so that necessary backend context is created
     */
    forge::Window wnd(512, 512, "Forge Demo");

    /* GPU buffers creation and population may happen here */
    
    do {// Render loop
        // draw calls go here
        // May update the data inside loop to creation animations
    } while(!wnd.close());

    return 0;
}</code></pre>

<h5>Rendering an Image to Window</h5>
Image rendering example will be demonstrated using GL-CPU interop. Once the window creation is completed, we have to create an object of class <em>forge::Image</em>. 
<pre><code>forge::Image img(width, height, FG_RGBA, forge::u8);</code></pre>

Now, create a variable of type <em>GfxHandle</em> that points to the OpenGL buffer and transfer the
data from CPU memory to OpenGL memory. The functions <em>createGLBuffer</em>, <em>copyToGLBuffer</em> are defined in ComputeCopy.h header.
<pre><code>#define USE_FORGE_CPU_COPY_HELPERS
#include <ComputeCopy.h> //User may implement their own data update code

kernel(bmp);                    // create the fractal

GfxHandle* handle = 0;          // GL-CPU interop buffer handle
    
createGLBuffer(&handle, img.pixels(), FORGE_IMAGE_BUFFER);

// copy the data from compute buffer to graphics buffer
copyToGLBuffer(handle, (ComputeResourceHandle)bmp.ptr, img.size());</code></pre>

It's time to render the image to window. If at all, the image needs to be updated during the render loop, you can do so by calling <em>copyToGLBuffer</em> with updated data pointer inside the render loop.
<pre><code>do {
    wnd.draw(img); // Draw call to render an image to window
} while(!wnd.close());</code></pre>

Clean up the resources.
<pre><code>releaseGLBuffer(handle); // In ComputeCopy.h
destroyBitmap(bmp); // Clear CPU memory buffer</code></pre>

You can look up the entire example's source code <a href="https://github.com/arrayfire/forge/blob/devel/examples/cpu/fractal.cpp">here</a>. The output of fractal render is shown below.
[caption id="attachment_9630" align="aligncenter" width="728"]<a href="https://sandbox.arrayfire.com/wp-content/uploads/2017/05/fgv1_image.png"><img src="https://sandbox.arrayfire.com/wp-content/uploads/2017/05/fgv1_image.png" alt="Fractal Demo" width="728" height="772" class="size-full wp-image-9630" /></a> Fractal Demo[/caption]

<h5>Rendering a Histogram</h5>
We will use CUDA-GL interop for this example. Let's create an object of class <em>forge::Histogram</em> and add it to a <em>forge::Chart</em> object.
<pre><code>forge::Image img(IMGW, IMGH, FG_RGBA, forge::u8); //Image placeholder

forge::Chart chart(FG_CHART_2D); // Histogram is a 2D graph, hence a 2D chart

//Axes Labels Format uses printf style formatting
chart.setAxesLabelFormat("%3.1f", "%.2e");

/* set x axis limits to maximum and minimum values of data
 * and y axis limits to range [0, number of pixels ideally]
 * but practically total number of pixels as y range will skew
 * the histogram graph vertically. Therefore setting it to
 * 25% of total number of pixels */
chart.setAxesLimits(0, 1, 0, IMGW*IMGH/(float)(NBINS/4.0));

// Create histogram object specifying number of bins
// this call automatically appends the histogram to list
// of objects to be rendered by chart after it is created.
forge::Histogram hist = chart.histogram(NBINS, forge::s32);

// Set global bar color which is used if no per-bar colors are used
hist.setColor(FG_YELLOW);</code></pre>

Now, create GL buffer handles for image, histogram data points and colors of histogram bars.
<pre><code>#define USE_FORGE_CUDA_COPY_HELPERS
#include <ComputeCopy.h>

GfxHandle* handles[3];

createGLBuffer(&handles[0], img.pixels(), FORGE_IMAGE_BUFFER);
createGLBuffer(&handles[1], hist.vertices(), FORGE_VERTEX_BUFFER);
createGLBuffer(&handles[2], hist.colors(), FORGE_VERTEX_BUFFER);

//Helper fn used to create histogram from image
kernel(bmp, noiseGenerator);

//Copy image to img resource handle
copyToGLBuffer(handles[0], (ComputeResourceHandle)bmp.ptr, img.size());

// create the histogram from image and generate
// histogram bar colors
populateBins(bmp, histOut, NBINS, histColors);

//Copy histogram frequencies and bar colors to OpenGL buffers
copyToGLBuffer(handles[1], (ComputeResourceHandle)histOut, hist.verticesSize());
copyToGLBuffer(handles[2], (ComputeResourceHandle)histColors, hist.colorsSize());</code></pre>

It's time to render the histogram and image to the window. Note that we added only histogram to the chart. So, the image has to be somehow rendered to the same window such that we can view both the image and histogram at the same time. Multiple view rendering comes to rescue here. Let's take a look at the code to see how it helps.
<pre><code>do {
     /*
      * Split the window into grid regions
      */
     // Render the image to the sub-view with index 0 if the
     // entire window is divided into 1 row and 2 columns
     wnd.draw(1, 2, 0, img,  "Dynamic Perlin Noise" );
     // Render the image to the sub-view with index 1 if the
     // entire window is divided into 1 row and 2 columns
     wnd.draw(1, 2, 1, chart, "Histogram of Noisy Image");
     // Swap the render buffers to display new rendering
     wnd.swapBuffers(); 
} while(!wnd.close());

// cleanup resources
FORGE_CUDA_CHECK(cudaFree(histOut));
FORGE_CUDA_CHECK(cudaFree(histColors));
releaseGLBuffer(handles[0]);
releaseGLBuffer(handles[1]);
releaseGLBuffer(handles[2]);</code></pre>

You can look up the entire example's source code <a href="https://github.com/arrayfire/forge/blob/devel/examples/cuda/histogram.cu">here</a>. The output of histogram render is shown below.
[caption id="attachment_9629" align="aligncenter" width="1228"]<a href="https://sandbox.arrayfire.com/wp-content/uploads/2017/05/fgv1_hist.png"><img src="https://sandbox.arrayfire.com/wp-content/uploads/2017/05/fgv1_hist.png" alt="Histogram" width="1228" height="829" class="size-full wp-image-9629" /></a> Histogram[/caption]

<h5>Multiple View Rendering</h5>
Given below are some examples of non-regular grid layouts that are possible using Forge's multiple view rendering.
<a href="https://sandbox.arrayfire.com/wp-content/uploads/2017/06/grids.png"><img src="https://sandbox.arrayfire.com/wp-content/uploads/2017/06/grids.png" alt="grids" width="942" height="419" class="aligncenter size-full wp-image-9664" /></a>

<h5>Rendering Multiple Plots on a Chart</h5>
We will use OpenCL-GL interop for this example. When it comes to interop with OpenCL, please make sure OpenGL context is created first before setting up any of the OpenCL objects.
<pre><code>// Do "#define USE_FORGE_OPENCL_COPY_HELPERS" before including ComputeCopy header.

forge::Chart chart(FG_CHART_2D);
chart.setAxesLimits(FRANGE_START, FRANGE_END, -1.0f, 1.0f);

/* Create several plot objects which creates the necessary
 * vertex buffer objects to hold the different plot types
 */
// Line plot with triangle markers at data points
forge::Plot plt1 = chart.plot(DATA_SIZE, forge::f32, FG_PLOT_LINE, FG_MARKER_TRIANGLE);

// Line plot with circular markers at data points
forge::Plot plt2 = chart.plot(DATA_SIZE, forge::f32, FG_PLOT_LINE, FG_MARKER_CIRCLE);

/* Set plot colors */
//use a forge predefined color FG_GREEN & FG_RED
plt1.setColor(FG_RED);
plt2.setColor(FG_GREEN);

/* Set plot legends */
plt1.setLegend("Cosine");
plt2.setLegend("Tangent");

/* set plot global marker size */
plt1.setMarkerSize(20);
//plt2 will use default marker size

/*
 * Helper function to create a CLGL interop context.
 * This function checks for if the extension is available
 * and creates the context on the appropriate device.
 *
 * Note: context and queue are defined in cl_helpers.h
 *       and these are returned by getContext() and getCommandQueue()
 *       functions when they are called by createGLBuffer, copyToGLBuffer
 *       and releaseGLBuffer copy helper functions
 */
context = createCLGLContext(wnd);
Device device = context.getInfo<CL_CONTEXT_DEVICES>()[0];
queue = CommandQueue(context, device);

GfxHandle* handles[5];

// create GL-OpenCL interop buffers
createGLBuffer(&handles[0], plt1.vertices(), FORGE_VERTEX_BUFFER);
createGLBuffer(&handles[1], plt2.vertices(), FORGE_VERTEX_BUFFER);
createGLBuffer(&handles[2], plt2.colors(), FORGE_VERTEX_BUFFER);
createGLBuffer(&handles[3], plt2.alphas(), FORGE_VERTEX_BUFFER);
createGLBuffer(&handles[4], plt2.radii(), FORGE_VERTEX_BUFFER);

cl::Buffer cosOut(context, CL_MEM_READ_WRITE, sizeof(float) * DATA_SIZE * 2);
cl::Buffer tanOut(context, CL_MEM_READ_WRITE, sizeof(float) * DATA_SIZE * 2);
cl::Buffer colorsOut(context, CL_MEM_READ_WRITE, sizeof(float) * DATA_SIZE * 3);
cl::Buffer alphasOut(context, CL_MEM_READ_WRITE, sizeof(float) * DATA_SIZE);
cl::Buffer radiiOut(context, CL_MEM_READ_WRITE, sizeof(float) * DATA_SIZE);
cl::Buffer dummy;

kernel(cosOut, 0, 0, dummy, dummy, dummy, queue, device);
kernel(tanOut, 1, 0x00000007, colorsOut, alphasOut, radiiOut, queue, device);

/* copy your data into the opengl buffer object exposed by
 * forge::Plot class and then proceed to rendering.
 * To help the users with copying the data from compute
 * memory to display memory, Forge provides copy headers
 * along with the library to help with this task
 */
copyToGLBuffer(handles[0], (ComputeResourceHandle)cosOut(), plt1.verticesSize());
copyToGLBuffer(handles[1], (ComputeResourceHandle)tanOut(), plt2.verticesSize());

/* update color value for tan graph */
copyToGLBuffer(handles[2], (ComputeResourceHandle)colorsOut(), plt2.colorsSize());

/* update alpha values for tan graph */
copyToGLBuffer(handles[3], (ComputeResourceHandle)alphasOut(), plt2.alphasSize());

/* update marker sizes for tan graph markers */
copyToGLBuffer(handles[4], (ComputeResourceHandle)radiiOut(), plt2.radiiSize());</code></pre>

We don't have multiple view rendering here and the draw call is simple and similar to how image example does it. You can look up the entire example's source code <a href="https://github.com/arrayfire/forge/blob/devel/examples/opencl/bubblechart.cpp">here</a>. The output of multiple plot rendering is shown below.
[caption id="attachment_9628" align="aligncenter" width="998"]<a href="https://sandbox.arrayfire.com/wp-content/uploads/2017/05/fgv1_bubble.png"><img src="https://sandbox.arrayfire.com/wp-content/uploads/2017/05/fgv1_bubble.png" alt="Multiple Plots Per Chart" width="998" height="844" class="size-full wp-image-9628" /></a> Multiple Plots Per Chart[/caption]

<h5>Sample Renders of Surface & Vector Field</h5>
[gallery columns="2" size="medium" ids="9634,9665"]
Source code for these examples can be found <a href="https://github.com/arrayfire/forge/tree/devel/examples">here</a>.

<h5>Font Management</h5>
The class <em>forge::Font</em> provides the choice of selecting the font for your program's rendering. You may skip using this class and the library will defer to the default fonts.
<pre><code>/* create an font object and load necessary font
     * and later pass it on to window object so that
     * it can be used for rendering text
     *
     * NOTE: THIS IS OPTIONAL STEP, BY DEFAULT WINDOW WILL
     * HAVE FONT ALREADY SETUP*/
    forge::Font fnt;
#ifdef OS_WIN
    fnt.loadSystemFont("Calibri");
#else
    fnt.loadSystemFont("Vera");
#endif
    wnd.setFont(&fnt);</code></pre>

<h5>Window Interactivity</h5>
Forge provides basic mouse interactivity such as pan, zoom and trackball rotation.

<h5>Error Handling</h5>
<em>forge::Error</em> is the exception class that you should use to catch the errors thrown by Forge.

<h5>Documentation</h5>
Full documentation of the library can be found <a href="http://arrayfire.org/forge/index.htm">here</a>.

<h5>Building from Source</h5>
You can find all the build dependencies required to build Forge from source <a href="https://github.com/arrayfire/forge/blob/devel/README.md">here</a>. Default window toolkit used by Forge is <a href="http://www.glfw.org/" target="_blank">GLFW</a>. However, you can also build Forge with <a href="https://www.libsdl.org/" target="_blank">SDL2</a>.

<h5>Community</h5>
Forge is continually improving through addition of new features. We welcome your feedback. Please feel free to leave your comments on any of the following channels.

General discussion forums on the <a href="https://groups.google.com/forum/#!forum/arrayfire-users" target="_blank">ArrayFire Google Group</a>
Live discussion chat on the <a href="https://gitter.im/arrayfire/forge?utm_source=badge&amp;utm_medium=badge&amp;utm_campaign=pr-badge&amp;utm_content=badge" target="_blank">Forge Gitter</a>
Issue reports on the <a href="https://github.com/arrayfire/forge/issues" target="_blank">Forge GitHub</a>
Finally, as you find success with Forge, we invite you to contribute a post to this blog to share with the broader community. Email sales@arrayfire.com to contribute to this blog.