2 January 2013

Tutorial 4: Renderlist


In this tutorial we build upon Tutorial 2 and show you how to create a proxy geometry in Tinia.

Familiarity with basic OpenGL and C++ with object orientation is assumed.


The program consists of three files: The Job class definition and two main files. One main file will be created for the desktop program, and one for the web program. We will only highlight changes from Tutorial 2, so it's a good idea to brush up on that tutorial before reading this one.

Proxy geometry

Tinia is designed to be used in a server/client setting. What sets Tinia apart from other tools, such as remote desktop connections, is that it maintains a high degree of interactivity even without a high speed, low latency connection.

The interactivity is achieved in parts by the use of a proxy geometry on the client side. The proxy geometry is meant to be a light weight representation of the geometry being rendered on the server.

Renderlists

The proxy geometry is defined through the use of a renderlist. The OpenGLJob class has a virtual method named getRenderList.

The renderlist is represented by an instance of the DataBase class. The database contains our buffers, contains our shaders (written in OpenGL ES Shading Language), and contains a list of actions (draw order) we want to perform in each render pass.

The actual use of a renderlist will seem quite similar to that of OpenGL.

In this tutorial we will say "view the renderlist" in short for the term "view the OpenGL canvas generated by the renderlist".

Making our database

First we include an instance of our database in the Tutorial4Job class.

tinia::renderlist::DataBase m_database;

For our proxy geometry we'll just create an identical copy of our triangle, just drawn using GL_LINES.

First we create a buffer to hold the points we want to draw. We do this by first creating the buffer, then setting the actual storage using the Set method.

float vertices[] = {
                         0, 0, 0.5,
                         1, 0, 0.5,
                         1, 1, 0.5
                        };
m_database.createBuffer("vertices")->set(vertices, 3*3);

We also need to specify our shaders, which will the most simple shader we can think of:
  std::string vertexShader =
            "uniform mat4 MVP;\n"
            "attribute vec3 position;\n"
            "void\n"
            "main()\n"
            "{\n"
            "    gl_Position = MVP * vec4( position, 1.0 );\n"
            "}\n";
    std::string fragmentShader =
            "#ifdef GL_ES\n"
            "precision highp float;\n"
            "#endif\n"
            "void\n"
            "main()\n"
            "{\n"
            "    gl_FragColor = vec4( 1,0,1, 1.0 );\n"
            "}\n";
    auto shader = m_database.createShader("shader");
    shader->setVertexStage(vertexShader);
    shader->setFragmentStage(fragmentShader);
Before we can create our draw order, we need to create actions for each procedure we want to execute. First we create an action for setting our shader. We do this by passing in, as a template argument, the action type we'd like. Then we name our action (in this case "useShader"). The createAction method returns a pointer to the action created, thus we may invoke the method setShader to specify which shader we want to use.

m_database.createAction<tinia::renderlist::SetShader>("useShader")
            ->setShader("shader");

Next we need to setup an action setting the correct inputs. You will notice that we utilize the fact that most renderlist methods return a pointer to the object we're working on, thus enabling us to chain commands quite easily. To specify the buffer, we first set which shader we want to pass the buffer to, then we just use the setInput method. Notice that we set the third argument to 3, signalling that we want three components.

m_database.createAction<tinia::renderlist::SetInputs>("useBuffer")
            ->setShader("shader")
            ->setInput("position", "vertices", 3);

We also need to set the MVP matrix. Luckily for us, Tinia provides us with generated matrices, so we just need to specify which matrix go to our MVP uniform.

m_database.createAction<tinia::renderlist::SetInputs>("useBuffer")
            ->setShader("shader")
            ->setInput("position", "vertices", 3);

And we create a simple draw action

m_database.createAction<tinia::renderlist::Draw>("draw")
            ->setNonIndexed(tinia::renderlist::PRIMITIVE_LINE_LOOP, 0, 3);

Then we set up the draw order

m_database.drawOrderClear()
            ->drawOrderAdd("useShader")
            ->drawOrderAdd("useBuffer")
            ->drawOrderAdd("setUniforms")
            ->drawOrderAdd("draw");

And finally we process the databaser

m_database.process();

Exposing the renderlist

To expose the renderlist to the controller, we need to implement the getRenderList method as such:

renderlist::DataBase* getRenderList(const std::string &session,
                                        const std::string &key);
renderlist::DataBase *Tutorial4Job::getRenderList(const std::string &session, const std::string &key)
{
    return &m_database;
}


Initializing an OpenGL extension wrangler

We want to view the renderlist in our desktop program, and to do this we need to use the tinia::renderlist::gl library. This library requires that we have our extension wrangler initialized, so we need to implement the initGL method in order to initialize GLEW at the right time

bool initGL() { glewInit(); return true; }

Running the desktop program

To actually view the renderlist in the desktop program, you need to start the executable with the –renderlist option. In the lower left corner of the OpenGL canvas, you should have a pull down menu saying "Native Rendering", changing this to "Render List" should show something similar to this:


Running the web program

If you've successfully installed Tinia you should be able to run the web program as tutorial4_web through the mod_trell web interface.

The renderlist will show up whenever you hold your mousebutton down on the canvas. The program should look something like this:


The full Job file

All changes in this tutorial have been done in the Job file of the tutorial:

/* Copyright STIFTELSEN SINTEF 2012
 *
 * This file is part of the Tinia Framework.
 *
 * The Tinia Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * The Tinia Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with the Tinia Framework.  If not, see <http://www.gnu.org/licenses/>.
 */
#pragma once
#include <tinia/tinia.hpp>
#include <GL/glew.h>
namespace tinia { namespace tutorial {
class Tutorial4Job : public tinia::jobcontroller::OpenGLJob {
public:
    Tutorial4Job();
    bool renderFrame( const std::string &session,
                      const std::string &key,
                      unsigned int fbo,
                      const size_t width,
                      const size_t height );
    renderlist::DataBase* getRenderList(const std::string &session,
                                        const std::string &key);
    bool initGL() { glewInit(); return true; }
private:
    tinia::renderlist::DataBase m_database;
};
Tutorial4Job::Tutorial4Job()
{
    m_model->addElement( "myViewer", tinia::model::Viewer() );
    m_model->addElement("boundingbox", "0 0 0 1 1 1");
    auto layout = new tinia::model::gui::VerticalLayout();
    auto canvas = new tinia::model::gui::Canvas("myViewer");
    canvas->boundingBoxKey("boundingbox");
    layout->addChild(canvas);
    m_model->setGUILayout(layout, tinia::model::gui::ALL);
    float vertices[] = {
                         0, 0, 0.5,
                         1, 0, 0.5,
                         1, 1, 0.5
                        };
    m_database.createBuffer("vertices")->set(vertices, 3*3);
    std::string vertexShader =
            "uniform mat4 MVP;\n"
            "attribute vec3 position;\n"
            "void\n"
            "main()\n"
            "{\n"
            "    gl_Position = MVP * vec4( position, 1.0 );\n"
            "}\n";
    std::string fragmentShader =
            "#ifdef GL_ES\n"
            "precision highp float;\n"
            "#endif\n"
            "void\n"
            "main()\n"
            "{\n"
            "    gl_FragColor = vec4( 1,0,1, 1.0 );\n"
            "}\n";
    auto shader = m_database.createShader("shader");
    shader->setVertexStage(vertexShader);
    shader->setFragmentStage(fragmentShader);
    m_database.createAction<tinia::renderlist::SetShader>("useShader")
            ->setShader("shader");
    m_database.createAction<tinia::renderlist::SetInputs>("useBuffer")
            ->setShader("shader")
            ->setInput("position", "vertices", 3);
    m_database.createAction<tinia::renderlist::SetUniforms>("setUniforms")
            ->setShader("shader")
            ->setSemantic("MVP", tinia::renderlist::SEMANTIC_MODELVIEW_PROJECTION_MATRIX);
    m_database.createAction<tinia::renderlist::Draw>("draw")
            ->setNonIndexed(tinia::renderlist::PRIMITIVE_LINE_LOOP, 0, 3);
    m_database.drawOrderClear()
            ->drawOrderAdd("useShader")
            ->drawOrderAdd("useBuffer")
            ->drawOrderAdd("setUniforms")
            ->drawOrderAdd("draw");
    m_database.process();
}
bool Tutorial4Job::renderFrame( const std::string &session,
                                const std::string &key,
                                unsigned int fbo,
                                const size_t width,
                                const size_t height )
{
    tinia::model::Viewer viewer;
    m_model->getElementValue("myViewer", viewer);
    glClearColor(0, 0, 0 ,0 );
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glViewport(0, 0, width, height);
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf( viewer.projectionMatrix.data() );
    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf( viewer.modelviewMatrix.data() );
    glColor3f(1, 0, 0);
    glBegin(GL_TRIANGLES);
    glColor3f(1, 0, 0);
    glVertex3f(0, 0,0.5);
    glVertex3f(1, 0, 0.5);
    glVertex3f(1, 1, 0.5);
    glEnd();
    return true;
}
renderlist::DataBase *Tutorial4Job::getRenderList(const std::string &session, const std::string &key)
{
    return &m_database;
}
} // of tutorial
} // of tinia

No comments:

Post a Comment