Ork 3.1 Documentation

Introduction

Ork is a small OpenGL rendering kernel. It must not be confused with the Ogre library: although Orks and Ogres have some common characteristics, Orks are usually smaller :-). The Ork library provides core functionalities related to 3D rendering: mathematical classes for vector and matrices, an object-oriented view of the OpenGL API, a framework to load rendering resources such as meshes, textures or shaders from disk. It also provides a minimal and generic "toy" scene graph to describe 3D scenes, based on a task scheduling framework.

Core classes

The core classes of Ork are provided in the core and math modules. The first module provides memory management facilities (namely a smart pointer framework), as well as some utility classes to log messages, measure time or iterate over STL collections. The second module provides classes related to linear algebra in 2D and 3D (vectors and matrices).

Smart pointers

The main class in core is the ork::Object class, and the related ork::ptr and ork::static_ptr templates. These classes provide a smart pointer framework i.e., a framework that ensures the automatic destruction of objects when they are no longer referenced.

Note:
with the USE_SHARED_PTR preprocessor flag ork::ptr extends std::tr1::shared_ptr; without this flag ork::ptr is fully implemented in Ork (using an intrusive counter in ork::Object). Note that, with the USE_SHARED_PTR flag, you can use either ork::ptr or std::tr1::shared_ptr in your application: since ork::ptr is a subclass of std::tr1::shared_ptr, the result of an Ork function returning a ork::ptr can be stored transparently in a std:tr1::shared_ptr. Conversely, you can transparently pass a std::tr1::shared_ptr to an Ork function requiring a ork::ptr, because there is an implicit ork::ptr constructor taking a std::tr1::shared_ptr as argument.

All the classes that inherit from ork::Object have this automatic destruction behavior. All the instances of theses classes have a reference counter, declared in ork::Object, which is incremented (resp. decremented) each time a new ork::ptr or ork::static_ptr reference to an object is created (resp. deleted). For instance, in the following code

void f() {
    ptr<Object> o = new Object(); // shortcut for ptr<Object>(new Object());
    // ...
}

an object is created in heap memory with the new operator, and its reference counter is incremented during the creation of the ptr<Object> reference. At the end of the function the destructor of this reference is automatically called (as defined in the C++ specification), which decrements the reference counter of the object. And if this counter becomes null, then the object destructor is called.

Notes:

Restrictions:

Message logs

The Ork logging framework can be used to log messages. A message can be an error message, a warning message, an informative message, or a debug message. It has a topic and a content. The main class is the ork::Logger class. This class stores in static variables several loggers for error, warning, informative and debug messages, one per category. It also defines a ork::Logger::log method, to log a message in a given topic. A typical use is the following:

if (Logger::INFO_LOGGER != NULL) {
    Logger::INFO_LOGGER->log("my topic", "my message");
}

The ork::Logger class prints messages to the standard output. The ork::FileLogger subclass writes messages to an HTML file. The ork::FileLogger can be chained to another logger. It is then possible to log messages both to standard output and to an HTML file. You can also define your own logger subclass. All loggers can be configured to print only the messages related to one or more topics, with the ork::Logger::addTopic method (by default they print all messages, whatever their topic). The topics defined in the Ork library are the following:

Maths

The math module provides classes related to linear algebra in 2D and 3D (vectors and matrices).

Rendering framework

The render module provides the Ork rendering framework, an object oriented view of the OpenGL API. The OpenGL API can be quite unnatural and hard to use for C++ programmers. The first reason is that OpenGL "objects" are simply represented with identifiers, instead of instances of classes. The second and probably most important reason is that these "objects" must be bound to a "target" before being usable. Indeed, these idioms are not compatible with the basic principles of object-oriented languages. Ork solves theses problems by providing a minimal object-oriented API on top of OpenGL. This means three things:

This brings many benefits for programmers, as shown by the following example. Suppose that you want to draw a mesh in an offscreen framebuffer, with a program that uses a texture. Assuming that these objects are already created, with the OpenGL API you need to:

With the Ork API you simply need two steps (and the first one does not need to be repeated before each draw, unless you want a different texture for each draw):

Under the hood, Ork will automatically select a texture unit, bind the texture to this unit, bind this unit to the program, set the program as the current one, set the mesh VBO as the current one, set the framebuffer as the current one, etc. Moreover, Ork automatically minimizes the OpenGL state changes used to implement this API (without reordering the API calls: i.e., Ork will not reorder the sequence myFBO->draw(myProgram1, *myVBO); myFBO->draw(myProgram2, *myVBO); myFBO->draw(myProgram1, *myVBO); myFBO->draw(myProgram2, *myVBO); to minimize the number of glUseProgram calls; however, for the sequence myFBO->draw(myProgram1, *myVBO); myFBO->draw(myProgram1, myVBO); myFBO->draw(myProgram2, *myVBO); myFBO->draw(myProgram2, *myVBO);, Ork will only use two glUseProgram calls, not four).

The Ork rendering framework is organized around a few concepts: framebuffers configured via parameters (for the few remaining fixed parts of the pipeline), are used to draw meshes into one or more render targets (which can be textures) using a set of modules linked into programs. The meshes define a set of user defined attributes per vertex (there are no predefined attributes such as normals or colors), which are processed by the programs, configured via uniform variables.


The Ork rendering framework

The rendering framework defines classes for all these elements. These classes extend the ork::Object class, so that GPU resources are automatically deleted when they are no longer used (for example when a ork::Texture object is no longer referenced it is deleted, which deletes the corresponding OpenGL texture in GPU memory).

The rendering API fully covers the OpenGL 3.3 core profile, and partially covers the OpenGL 4.0 and 4.1 core profile APIs (tesselation shaders are supported, but uniform subroutines, binary shaders and programs, pipeline objects, separable shaders, and multiple viewports are currently not supported).

Meshes

A mesh is a list of vertices organized in some topology (points, lines, line strip, triangles, triangle strip, etc). Each vertex has some attributes. Attributes are user defined and can hold any kind of value (position, normal, color, texture coordinates, etc). The attributes are grouped into ork::AttributeBuffer, grouped themselves into an ork::MeshBuffers. An attribute buffer describes the storage format for one vertex attribute. The values of this attribute for all the vertices of the mesh are not stored in the attribute buffer itself, but in a separate ork::Buffer. This buffer can reside in CPU memory if an ork::CPUBuffer is used, or in GPU memory if an ork::GPUBuffer is used.

Thanks to the stride and offset parameters in ork::AttributeBuffer, the attributes of the vertices of a mesh can be organized in many different ways. Here are some possibilities:

This flexibility can be an advantage to organize your mesh data as you want, but it complicates the creation of a mesh. In order to simplify the creation of meshes using the interleaved storage mode in a single buffer, it is possible to use the ork::Mesh template. For example, the following code

ptr< Mesh<vec4f, unsigned byte> > m;
m = new Mesh<vec4f, unsigned byte>(TRIANGLE_STRIP, GPU_STATIC);
m->addAttributeType(0, 2, A32F, false);
m->addAttributeType(1, 2, A32F, false);
m->addVertex(vec4f(-1, -1, 0, 0));
m->addVertex(vec4f(1, -1, 1, 0));
m->addVertex(vec4f(-1, 1, 0, 1));
m->addVertex(vec4f(1, 1, 1, 1));

creates a mesh with the triangle strip topology, where each vertex is represented with a vec4f struct (you can use any user defined structure to describe the vertices). Each vertex has two attributes, described with ork::Mesh::addAttributeType. The first one is made of two floats, as well as the second one (they can be interpreted as a 2D position and a 2D texture coordinate). The vertices are defined with ork::Mesh::addVertex, using the type passed as template parameter for the mesh. The resulting ork::MeshBuffers can then be retrieved with ork::Mesh::getBuffers.

Attribute identifiers

Each vertex attribute has an identifier represented with an integer (specified by the first argument of the ork::AttributeBuffer constructor, or of the addAttributeType method. You must specifiy these identifiers in your shaders, with a location layout qualifier. For instance, using layout(location=0) in vec3 position;

Indexed meshes

A mesh with the triangles topology must be defined by giving the 3 vertices of a first triangle, followed by the 3 vertices of a second triangle, and so on for all triangles. This can lead to a waste of space, since a vertex shared between several triangles must be duplicated several times (the same problem occurs with other topologies as well). A way to limit this duplication is to use a triangle strip topology, but this is not always convenient. Another way is to use an indexed mesh. In this case the vertices are described once in an array, then the vertices that make up each triangle are described by their index in this array.

This kind of mesh can be defined in ork by using ork::MeshBuffers::setIndicesBuffer. This method takes an ork::AttributeBuffer as parameter, that describes the indices of the mesh vertices in the other attribute buffers, seen as vertex arrays. This can also be done with a ork::Mesh, using the ork::Mesh::addIndice method (the format of the vertex indices is then specified by the second parameter of the ork::Mesh template).

Modifying meshes

A mesh can be modified by modifying the ork::Buffer that contain the actual vertex data. In the case of a ork::CPUBuffer you can directly modify the buffer content in CPU memory. In the case of a ork::GPUBuffer, you can either use the ork::GPUBuffer::setData or ork::GPUBuffer::setSubData methods, or you can map the buffer content to CPU memory with ork::GPUBuffer::map, and then modify this mapped buffer directly in CPU memory, before unmapping it.

Drawing meshes

A mesh must be drawn with the ork::FrameBuffer::draw method, either with a ork::MeshBuffers argument, or with a ork::Mesh argument. In the first case you can specify a different mesh topology and vertex count than in the ork::MeshBuffers itself (this could be used to draw only a part of the mesh). In both cases you can specify the number of times this mesh must be drawn (this is called geometry instancing).

Modules

A mesh must be drawn by using a set of shaders linked into a program. The shaders use the vertex attributes to compute the projected positions of vertices in the render targets, and the color or other data to write in these render targets. They can also use some uniform variables, whose value is constant during the rendering of a mesh.

In Ork a ork::Program is made of one or more ork::Module linked together. Each ork::Module object groups in the same object a (part of a) vertex shader, a (part of a) tessellation control shader, a (part of a) tessellation evaluation shader, a (part of a) geometry shader and a (part of a) fragment shader (all parts are optional). All parts are optional. These parts must be defined either each in its own file:

 ... vertex shader GLSL code ...
myModuleVS.glsl
 ... tessellation control shader GLSL code ...
myModuleTCS.glsl
 ... tessellation evaluation shader GLSL code ...
myModuleTES.glsl
 ... geometry shader GLSL code ...
myModuleGS.glsl
 ... fragment shader GLSL code ...
myModuleFS.glsl

or all grouped in a single file, but separated with the following preprocessor directives (this option can reduce the redundancy between shaders, which often use the same uniform variables and user defined data types and functions):

 ... common GLSL code ...

#ifdef _VERTEX_
 ... vertex shader GLSL code ...
#endif

#ifdef _TESS_CONTROL_
 ... tessellation control shader code ...
#endif

#ifdef _TESS_EVAL_
 ... tessellation evaluation shader code ...
#endif

#ifdef _GEOMETRY_
 ... geometry shader GLSL code ...
#endif

#ifdef _FRAGMENT_
 ... fragment shader GLSL code ...
#endif
A module whose shaders are in a single file myModule.glsl

where each ifdef section is optional (the code is included in all vertex, tessellation, geometry and fragment OpenGL shaders, unless it is inside an ifdef section. In this case it is included only in the corresponding OpenGL shader). The common code can include uniform variables and data type declarations that are common to the vertex, tessellation, geometry and fragment parts.

A ork::Program is made of one or more ork::Module linked together. Using several ork::Module to create a program can be useful to define modular code, where one module uses a function implemented in another module. For example, you can use something like this in a "wood" ork::Module

... common GLSL code ...

#ifdef _VERTEX_
 ... vertex shader GLSL code ...
#endif

#ifdef _FRAGMENT_
layout(location=0) out vec4 color;

// function prototype, no implementation
vec3 illuminate(vec3 p);

void main() {
   vec3 p = ... // position
   vec3 light = illuminate(p);
   color = ... // compute reflected light
}
#endif

using an abstract illuminate function to compute the incident light at p, and a "point light" module to define how this incident light is computed:

uniform vec3 lightPos;
uniform vec3 lightColor;

vec3 illuminate(vec3 p) {
    vec3 v = p - lightPos;
    return lightColor / dot(v, v);
}

#ifdef _VERTEX_
#endif

#ifdef _FRAGMENT_
#endif

One advantage it that you can then define another version of this illuminate function, for instance for a spot light, that you can combine with the "wood" module without needing to rewrite it. Note that we defined the illuminate function in the common section so that we can use this function in a vertex shader for per-vertex lighting, or in a fragment shader of per pixel lighting. Another advantage is that you can combine these "point light" and "spot light" modules with other "material" modules, for instance with a "marble" module (this means that a ork::Module can be used by several ork::Program). Hence can you avoid code duplication between all these material shaders.

Uniforms

The uniform variables of a program are represented with the ork::Uniform class and its subclasses. Uniforms can be retrieved with the ork::Program::getUniform and getUniformXxx methods. Uniforms inside uniform blocks can be retrieved in the same way, or in two steps via ork::Program::getUniformBlock and ork::UniformBlock::getUniform.

As in OpenGL, the value of a ork::Uniform is "persistent", which means that this value remains the same forever, unless you change it with ork::Uniform::setValue (or one of the specific set methods in the ork::Uniform subclasses).

The value of a uniform defined in an uniform block is set like the value of an uniform in the default block. Under the hood, Ork automatically creates a ork::GPUBuffer object for the uniform block, and maps and unmaps it in main memory when necessary.

Important note: For the best performance you should not use ork::Program::getUniform in your rendering loop. Instead of doing this:

... initialization
for (each frame) {
    ...
    myProgram->getUniform1f("x")->set(...);
    myFrameBuffer->draw(myProgram, *myMesh);
    ...
}

Use this:

... initialization
ptr<Uniform1f> x = myProgram->getUniform1f("x");
for (each frame) {
    ...
    x->set(...);
    myFrameBuffer->draw(myProgram, *myMesh);
    ...
}

Textures

The OpenGL textures are represented in Ork with the ork::Texture class and its subclasses. Each texture has an internal format describing the number of components (or color channels) per pixel and the number of bits per components. Each texture also has a set of parameters, represented with the ork::Texture::Parameters class. In Ork the texture parameters are immutable, i.e., only the content of the texture can be changed at runtime.

The texture parameters are specified in the texture constructor. An initial texture content can also be specified in the constructor, using a ork::Buffer object. Using a CPUBuffer(NULL) can be used to left this content uninitialized. Using a ork::GPUBuffer can be used to initialize the texture content from a buffer already on GPU (the content of this buffer is copied to the texture, it is not use as is, by reference). The texture content can be read back on CPU with the ork::Texture::getImage method.

The texture content can be changed at runtime in two ways. The first one copies the new content from a buffer object, the second one from a framebuffer attachment:

Compressed textures

Ork supports compressed textures, i.e., textures whose content on GPU is compressed. There are special methods to read and write the content of these textures:

Uniform samplers

A texture can be bound to a uniform sampler in a program very simply, with a ork::UniformSampler object. For instance, if p is a ork::Program made of the following module

uniform samplerCube envMap;
...
void main() {
   ...
   vec4 c = texture(envMap, d);
   ...
}

then you can bind a texture to its envMap uniform sampler like this:

ptr<TextureCube> t = ...
p->getUniformSampler("envMap")->set(t);

Framebuffers

The ork::FrameBuffer class is used to represent both the default framebuffer and the OpenGL framebuffer objects. As in OpenGL, a framebuffer has some attachments. These attachments are associated with their framebuffer, i.e., when a framebuffer is used to draw a mesh, the textures and render buffers attached to this framebuffer become the new current render targets automatically. In Ork this idea has been extended to the pipeline state (viewport, stencil and depth tests, clearing, culling, blending and writing states, etc). This means that each framebuffer has its own associated pipeline state. When a framebuffer is used to draw a mesh, its associated pipeline state is automatically set up to replace the pipeline state of the previous framebuffer.

The framebuffer attachments are defined with the ork::FrameBuffer::setRenderBuffer and ork::FrameBuffer::setTextureBuffer methods.

The framebuffer pipeline state is represented with the ork::FrameBuffer::Parameters class and can be modified with the getter and setter methods of the ork::FrameBuffer class.

The default framebuffer is accessible via the ork::FrameBuffer::getDefault static method (the attachments of this framebuffer cannot be changed, but its pipeline state can be changed). The user defined framebuffers are created with the ork::FrameBuffer constructor.

The main methods of ork::FrameBuffer are the draw methods to draw meshes (there is also a convenient drawQuad method to draw quads). There are also some methods to clear the render targets, and to read and copy pixels from these attachments:

Multiple render targets

A fragment shader can write to several framebuffer attachments simultaneously. Before that you must select the attachments used for drawing with the setDrawBuffers method. For example, to choose the COLOR0 and COLOR2 attachments:

ptr<FrameBufer> fb = ...
fb->setDrawBuffers(FrameBuffer::COLOR0 | FrameBuffer::COLOR2);

Interactions with OpenGL

It is possible to use Ork together with the raw OpenGL API, for instance to use new OpenGL features not yet supported in Ork. However, since Ork maintains some internal state about the current OpenGL state for its own use, modifying the OpenGL state outside Ork with direct OpenGL calls can lead to inconsistencies between the internal Ork state and the real OpenGL state. To avoid this problem you must follow the following pattern when mixing direct OpenGL calls with Ork:

... // Ork code
FrameBuffer::resetAllStates();
... // direct OpenGL calls
FrameBuffer::resetAllStates();
... // Ork code

An example

This section gives the code of a full example, showing how to use textures, modules, programs, meshes and framebuffers.

#include "ork/render/FrameBuffer.h"
#include "ork/ui/GlutWindow.h"

using namespace ork;

class SimpleExample : public GlutWindow
{
public:
    ptr< Mesh<vec2f, unsigned int> > m;
    ptr<Program> p;

    SimpleExample() : GlutWindow(Window::Parameters())

The ork::GlutWindow class provides an object oriented view of the basic GLUT functionalities, namely windows and user interface events callbacks. The methods of this class can be overridden in order to define the user interface of your application. These methods are the redisplay, reshape, mouseClick, mouseMotion, keyTyped, specialKey, and idle methods. You can then create a window by instantiating this sub class. Note: if the initial window size is set to (0,0) a full screen window will be created.

    {
        m = new Mesh<vec2f, unsigned int>(TRIANGLE_STRIP, GPU_STATIC);
        m->addAttributeType(0, 2, A32F, false);
        m->addVertex(vec2f(-1, -1));
        m->addVertex(vec2f(+1, -1));
        m->addVertex(vec2f(-1, +1));
        m->addVertex(vec2f(+1, +1));

The above code creates a mesh made of quads, with only one attribute per vertex, namely a position (attribute identifier 0) made of 2 floats. It then adds four vertices to this mesh, i.e., one quad.

        unsigned char data[16] = {
            0, 255, 0, 255,
            255, 0, 255, 0,
            0, 255, 0, 255,
            255, 0, 255, 0
        };
        ptr<Texture2D> tex = new Texture2D(4, 4, R8, RED, UNSIGNED_BYTE,
            Texture::Parameters().mag(NEAREST), Buffer::Parameters(), CPUBuffer(data));

The above code creates a checkerboard texture of 4 by 4 pixels, initialized from a CPU memory buffer, using one 8 bits channel per pixel.

        p = new Program(new Module(330, NULL, "\
            #version 330\n\
            uniform sampler2D sampler;\n\
            uniform vec2 scale;\n\
            layout(location = 0) out vec4 data;\n\
            void main() {\n\
                data = texture(sampler, gl_FragCoord.xy * scale).rrrr;\n\
            }\n"));

        p->getUniformSampler("sampler")->set(tex);
    }

The above code creates a program made of a single module, itself made of a single fragment shader. It then sets the value of its "sampler" uniform to the previous texture.

    virtual void redisplay(double t, double dt)
    {
        ptr<FrameBuffer> fb = FrameBuffer::getDefault();
        fb->clear(true, false, false);
        fb->draw(p, *m);
        GlutWindow::redisplay(t, dt);
    }

In the above code we override the ork::GlutWindow::redisplay method, called at each frame, and which is responsible to redraw the window content. We clear the default framebuffer, and then use the previous program to draw the above mesh. The overridden method must be called in order to actually update the window content (with a glSwapBuffers).

    virtual void reshape(int x, int y)
    {
        FrameBuffer::getDefault()->setViewport(vec4<GLint>(0, 0, x, y));
        p->getUniform2f("scale")->set(vec2f(1.0f / x, 1.0f / y));
        GlutWindow::reshape(x, y);
        idle(false);
    }
};

In the above code we override the ork::GlutWindow::reshape method, called when the window is created or resized, in order to adapt the viewport of the framebuffer to the window size.

int main(int argc, char** argv)
{
    atexit(Object::exit);
    ptr<SimpleExample> app = new SimpleExample();
    app->start();
    return 0;
}

Finally we create an instance of our ork::Window subclass and we call its ork::Window::start method to start the user interface event processing loop (this method never returns). Before that we register the static ork::Object::exit method to properly deletes unused resources when the application stops.

Resource framework

The render classes do not provide any tool to load a 3D mesh, to load the content of a texture from a PNG file (or any other image file format), or to load the source code of a shader from a text file. This is why in the above example the mesh, the texture and the shader source code were included manually in the C++ code. Of course this way of loading content is not very convenient. The resource module provides a framework to load this content and other data from disk. In addition this framework provides the ability to update the resources at runtime. This is especially useful for shaders because you can modify a shader on disk and see instantly the effects of these changes, without needing to restart your application.

Ork resources are managed by a ork::ResourceManager, which uses a ork::ResourceLoader to load the actual resource content. The ork::ResourceLoader class is abstract, but a concrete ork::XMLResourceLoader subclass is provided. As its name implies, it uses XML files to load resources. An XML resource file describes the resource "meta data" (such as the minification and magnification filters for a texture) and specifies where the resource data (e.g., for a texture, the texture image itself) can be found. These classes are typically used as follows:

ptr<XMLResourceLoader> resLoader = new XMLResourceLoader();
resLoader->addPath("my-resources/textures");
resLoader->addPath("my-resources/shaders");
resLoader->addPath("my-resources/meshes");

we first start by creating a ork::XMLResourceLoader, and we configure it by adding paths where the resource files can be found. In this example the resources are sorted by type in 3 subdirectories in the my-resources directory, so we add these 3 paths (adding a directory does not add recursively its subdirectories).

ptr<ResourceManager> resManager = new ResourceManager(resLoader, 8);

we then create a ork::ResourceManager using the previous resource loader. The second constructor argument, 8, is the size of the cache of unused resources. Indeed, instead of deleting immediately an unused resource, a resource manager can cache it temporarily, so that it does not need to reload it from disk if it is needed again shortly after. The default cache size is 0, which means that resources are deleted as soon as they become unused.

Once the resource loader and manager are created and configured, we can load resources very easily with a code like this:

ptr<Texture> t = resManager->loadResource("envMap").cast<Texture>();
Note:
A resource is loaded only once. If you try to load an already loaded resource, this resource instance will be returned directly. Hence you cannot have several copies of a resource at the same time.

This code looks for a file named envMap.xml in the resource loader paths. This file should look like this:

<?xml version="1.0" ?>
<textureCube name="envMap" source="myEnvMap.png"
    internalformat="RGB8" min="LINEAR_MIPMAP_LINEAR" mag="LINEAR"/>

Note that this file only contains the meta data for the texture. The texture image is contained in the myEnvMap.png file, which is looked for in the configured paths of the resource loader.

Note:
The ork::XMLResourceLoader is convenient during the development phase because you can easily change a single resource with a text editor, and you can also do that at runtime. But this is a drawback when you want to distribute your application: you may not want users to be able to see your resources in "clear text" (especially for shaders), and you certainly don't want them to be able to modify them at runtime. The ork::CompiledResourceLoader solves this problem: it disables runtime resource updates, and loads all resources from a single data file (that you can encrypt if necessary), produced by the ork::ResourceCompiler. To use these classes you must first run your application with the ork::ResourceCompiler (a subclass of ork::XMLResourceLoader). This will agglomerate all the resource data in two files, that you can then use with the ork::CompiledResourceLoader.

Archive resource files

Alternatively, instead of using many small XML files, one per resource, it is also possible to use archive files containing several resources per file. In fact both can be used at the same time, i.e., some resources can be loaded from individual files, while others are loaded from one or more archive files. In order to load resources from an archive file myArchive.xml, the ork::XMLResourceLoader must be configured as follows:

resLoader->addArchive("my-resources/myArchive.xml");

In the archive file the individual resources must be put one after the other inside an archive element (they are then found based on their name attribute):

<?xml version="1.0" ?>
<archive>
    ...
    <textureCube name="envMap" source="myEnvMap.png"
        internalformat="RGB8" min="LINEAR_MIPMAP_LINEAR" mag="LINEAR"/>
    ...
</archive>

Updating resources

As said above the Ork resource manager can update already loaded resources at runtime. For instance if you modify on disk a texture image, a texture minification filter, a shader source code, a mesh, etc you will see instantly in the running application the effect of these changes: a new texture image on a 3D model, the effect of a new texture filter, the effect of a new shader, the effect of using a different mesh for a 3D model, etc. This is done with the ork::ResourceManager::updateResources method.

This method checks the last modification time of the resource files on disk to detect those that have changed since they were loaded. It then updates these resources, and returns true if the update was successful. Indeed the update can fail, for instance if there is a syntax error in a GLSL shader. In this case the shader is not updated, i.e., the old version of the shader remains in use. The updateResources method can be called in many different ways: when the user presses a specific key, at regular time intervals, when the application window gets the focus, etc.

During development it is usual to test several options in a module, using preprocessor directives to select one option:

#define OPTION2

void main() {
#ifdef OPTION1
    ...
#endif
#ifdef OPTION2
    ...
#endif
...
#ifdef OPTION3
    ...
#endif
}

With Ork you can change the first line at runtime to select a different option. This change can be done manually with a text editor, in parallel with your application.

Mesh resources

A mesh resource is loaded like this:

ptr<MeshBuffers> m = resManager->loadResource("cube.mesh").cast<MeshBuffers>();

The cube.mesh (the .mesh is important) must have the following form (the comments are not part of the file):

-1 1 -1 1 -1 1         // bounding box xmin xmax ymin ymax zmin zmax
triangles              // mesh topology (points, lines, linestrip, etc)
4                      // number of attributes per vertex
0 3 float false        // attribute 1: identifier, components, format, auto normalize
1 3 float false        // attribute 2: identifier, components, format, auto normalize
2 2 float false        // attribute 3: identifier, components, format, auto normalize
3 4 ubyte true         // attribute 4: identifier, components, format, auto normalize
36                     // number of vertices
-1 -1 +1 0 0 +1 0 0 255 0 0 0  // vertex 0: position normal uv color
+1 -1 +1 0 0 +1 1 0 255 0 0 0  // vertex 1: position normal uv color
+1 +1 +1 0 0 +1 1 1 255 0 0 0  // ...
+1 +1 +1 0 0 +1 1 1 255 0 0 0
// ...
-1 -1 -1 0 -1 0 0 0 255 255 0 0 // vertex 35: position normal uv
color
0                      // number of indices (for indexed meshes)
                       // indices (empty this case)

Module resources

A module resource is loaded like this:

ptr<Module> m = resManager->loadResource("myModule").cast<Module>();

The myModule.xml file must have the following form if the vertex, tessellation, geometry and fragment shaders are in separate files (all the shaders are optional):

<?xml version="1.0" ?>
<module name="myModule" version="330"
        vertex="myModuleVS.glsl" 
        tessControl="myModuleTCS.glsl"
        tessEvaluation="myModuleTES.glsl"
        geometry="myModuleGS.glsl"
        fragment="myModuleFS.glsl"
        options="OPTION1,DEBUG">
    <uniformSampler name="envMapSampler" texture="envMap"/>
    <uniform1f name="..." x="..."/>
    <uniform2f name="..." x="..." y="..."/>
    <uniform3f name="..." x="..." y="..." z="..."/>
    <uniform4f name="..." x="..." y="..." z="..." w="..."/>
    ...
</module>

or the following form, if these shaders are grouped in a single file, but separated with preprocessor directives:

<?xml version="1.0" ?>
<module name="myModule" version="330" source="myModule.glsl" options="OPTION1,DEBUG">
    <uniformSampler name="envMapSampler" texture="envMap"/>
    <uniform1f name="..." x="..."/>
    <uniform2f name="..." x="..." y="..."/>
    <uniform3f name="..." x="..." y="..." z="..."/>
    <uniform4f name="..." x="..." y="..." z="..." w="..."/>
    ...
</module>

where the uniformXxx are optional: if an uniform is declared it will be used to set an initial value for this uniform in programs using this module. The options attribute is also optional. It contains a comma separated list of preprocessor directives that will be prepended to the shader source. This can be useful to create several shaders with a lot of code in common from a single GLSL file.

Note:
if loaded via the Ork resource framework, a GLSL source file can include other source files with a #include "....glsl": the ork::XMLResourceLoader will automatically detect these directives and replace them with the content of the referenced file.

A program made of one or more modules can be loaded without declaring any resource file:

ptr<Program> p1 = resManager->loadResource("myModule;").cast<Program>();
ptr<Program> p2 = resManager->loadResource("module1;module2;").cast<Program>();

the first line loads the program made of the single myModule module (the ";" is important). The second line loads the program made of the two module1 and module2 modules (both ";" are important).

Texture resources

A texture resource is loaded like this:

ptr<Texture> t = resManager->loadResource("myTexture").cast<Texture>();

The myTexure.xml file must have the following form:

<?xml version="1.0" ?>
<texture3D name="myTexture" source="image.png" internalformat="..."
    min="..." mag="..." wraps="..." wrapt="..." wrapr="..." .../>

where the only mandatory attributes are name, source and internalformat, and where texture3D can be replaced with other texture types, such as texture1D, texture2D, texture2DArray, textureCube, etc. The texture image can be in JPEG, PNG, BMP, TGA, PSD or HDR (= Radiance RGBE) format (with some restrictions for each format).

Note:
A raw format is also supported, with one float per pixel component. Such a raw file must end with five 32 bits integers, the first one being 0xCAFEBABE, the others the width, height, depth and components per pixel.

The 2D texture image corresponds directly to the texture content for a 2D texture. For a 1D texture the texture image should have a single line. For a 3D texture, the 2D texture image represents the z slices of the 3D texture, layed out vertically (the same disposition is used for the 2D layers of a 2D array texture). Finally, for a texture cube, the 2D texture image represents the 6 faces of the texture cube, layed out as shown on the right (PX means positive x axis direction, NX negative x axis, and so on - for a texture cube array the layout is similar, all the cube texture layers being stacked vertically):


Texture image layout for a 3D or 2D array texture (left) and for a texture cube (right).

Render targets

Sometimes you need some textures to use them as render targets in a framebuffer. In these cases the texture content is irrelevant. These textures can easily be created with a ork::Texture constructor, but it is also possible to load them via the resource framework. The advantage is that you can share these render targets more easily (since a resource is loaded only once, if the same render target texture is loaded in several parts of your code, the same texture will be used for all). This can be done as follows:

resManager->loadResource("renderbuffer-64-I32F").cast<Texture>();

The first part gives the texture size (the texture is assumed to be a square 2D texture). The last part is the texture internal format. If you need several distinct textures of the same format, you can use names of the form renderbuffer-64-I32F-1, renderbuffer-64-I32F-2, etc.

User defined resources

You can define your own resources by extending the ork::Resource class. The common pattern used in Ork is to define a base class for the resource (so that you can instantiate and use it without needing the resource framework), and a sub class of this base class that also extends ork::Resource, to be able to instantiate this resource via the Ork resource framework. The base class has the following form:

class MyClass : public Object
{
public:
    MyClass(int p1, int p2)
    {
        init(p1, p2);
    }

    ...

protected:
    MyClass();

    void init(int p1, int p2)
    {
        this->p1 = p1;
        this->p2 = p2;
    }

    virtual void swap(ptr<MyClass> c)
    {
        std::swap(p1, c->p1);
        std::swap(p2, c->p2);
    }

private:
    int p1, p2;
};

When the class is instantiated directly the public constructor is used. The protected constructor is used when the class is instantiated via the Ork resource framework (this constructor must have no parameters). The constructor code is placed in a separate init method so that it is not duplicated between the base class and the resource sub class (see below). The swap method is used to replace the current instance with another one. This is how Ork can dynamically update resources when their content has changed on disk (there is no magic, you must implement the swap method yourself).

The resource sub class is defined by using the ork::ResourceTemplate class:

class MyClassResource : public ResourceTemplate<0, MyClass>
{
public:
    MyClassResource(ptr<ResourceManager> manager, const string &name,
            ptr<ResourceDescriptor> desc, const TiXmlElement *e = NULL) :
        ResourceTemplate<0, MyClass>(manager, name, desc)
    {
        e = e == NULL ? desc->descriptor : e;
        int p1;
        int p2;
        checkParameters(desc, e, "name,p1,p2,");
        getIntParameter(desc, e, "p1", &p1);
        getIntParameter(desc, e, "p2", &p2);
        init(p1, p2);
    }
};

extern const char myClass[] = "myClass";

static ResourceFactory::Type<myClass, MyClassResource> MyClassType;

this makes MyClassResource extend both MyClass and ork::Resource (see the ork::ResourceTemplate definition). The constructor must have this predefined signature, and its role is to decode the XML descriptor to extract the arguments to be passed to the init method of the super class. The last two lines register this resource under the myClass name. This means that when the Ork resource loader will encounter a <myClass name="..." p1="..." p2="..."> element it will create a MyClassResource object, which will initialize itself by decoding the XML attributes.

An example

We can now rewrite the above example by using the Ork resource framework to load the resources:

#include "ork/resource/XMLResourceLoader.h"
#include "ork/resource/ResourceManager.h"
#include "ork/render/FrameBuffer.h"
#include "ork/ui/GlutWindow.h"

using namespace ork;

class SimpleExample : public GlutWindow
{
public:
    ptr<ResourceManager> resManager;
    ptr<MeshBuffers> m;
    ptr<Program> p;

    SimpleExample() : GlutWindow(Window::Parameters())
    {
        ptr<XMLResourceLoader> resLoader = new XMLResourceLoader();
        resLoader->addPath("resources/textures");
        resLoader->addPath("resources/shaders");
        resLoader->addPath("resources/meshes");

        resManager = new ResourceManager(resLoader);
        m = resManager->loadResource("quad.mesh").cast<MeshBuffers>();
        p = resManager->loadResource("basic;").cast<Program>();
    }

    // rest of the code unchanged
};

The mesh, texture and module resources can now be defined in separate files:

-1 1 -1 1 0 0
trianglestrip
1
0 3 float false
4
-1 -1 0
+1 -1 0
-1 +1 0
+1 +1 0
0
The quad.mesh file
<?xml version="1.0" ?>
<texture2D name="checkerboard" source="checkerboard4x4.png" internalformat="R8"
    min="NEAREST" mag="NEAREST" wraps="CLAMP" wrapt="CLAMP"/>
The checkerboard.xml file
<?xml version="1.0" ?>
<module name="basic" version="330" source="basicModule.glsl">
    <uniformSampler name="sampler" texture="checkerboard"/>
</module>
The basic.xml file
#ifdef _VERTEX_
layout(location=0) in vec4 vertex;
out vec2 uv;
void main() {
    gl_Position = vertex;
    uv = vertex.xy * 0.5 + vec2(0.5);
}
#endif
#ifdef _FRAGMENT_
uniform sampler2D sampler;
in vec2 uv;
layout(location=0) out vec4 color;
void main() {
    color = texture(sampler, uv);
}
#endif
The basicModule.glsl file

Scene graph

Most of the interesting 3D scenes contain more than a single quad. In these cases you have to manage for each object one or more mesh (for instance if you want to use different meshes depending on the level of details), one or more textures, one or more modules, etc. You also have to manage reference frames to place the objects in a global scene, ignore objects that are not in the view frustum, etc.

Then you have several options to draw your scene:

In order to leave all these options open, Ork provides a "toy" scene graph which is minimal but fully extensible (at the price of efficicency, i.e., this scene graph cannot be used with scenes made of hundreds or thousands of nodes). In fact an Ork scene graph is a tree of generic scene nodes ork::SceneNode, where each node can be seen as an object with a state (fields) and a behavior (methods). The state is made of a reference frame (relatively to the parent node), some meshes, modules and uniforms (that can reference textures), and any other user defined values. The behavior is made of methods, completely defined by the user by combining basic tasks (draw a mesh, set a projection matrix, etc) with control structures (sequences, loops, etc). Scene nodes, methods and tasks are explained below.

Nodes

A scene node is an instance of the ork::SceneNode class. The state associated with a scene node is made of:

The behavior associated with a node is defined as a set of ork::Method. They are explained below (see Methods). Finally each scene node has a list of child nodes, that you can get and modify with ork::SceneNode::getChild, ork::SceneNode::addChild and ork::SceneNode::removeChild.

Since an Ork scene node is fully generic, there are no specific scene node classes for lights, objects, cameras, etc. In fact all the nodes in a scene graph are instances of the ork::SceneNode class, but their state and behavior can be completely different because they are fully specified by the user.

Scene node resources

Scene nodes can be loaded with the Ork resource framework. A scene node resource must have the following form:

<node name="cubeNode" flags="object,castshadow">
    <translate x="0" y="0" z="10"/>
    <rotatex angle="5"/>
    <rotatey angle="10"/>
    <rotatez angle="15"/>
    <bounds xmin="-1" xmax="1" ymin="-1" ymax="1" zmin="-1" zmax="1"/>
    <uniform3f id="ambient" name="ambientColor" x="0" y="0.1" z="0.2"/>
    <module id="material" value="plastic"/>
    <mesh id="geometry" value="cube.mesh"/>
    <field id="..." value="..."/>
    <method id="draw" value="objectMethod"/>
    <method id="shadow" value="shadowMethod"/>
</node>

The elements inside the node element are all optional and can be in any order. There can be any number of translate, rotatex, rotatey and rotatez elements, to define the transformation from the local node to the parent node (the first transformation is applied last). The bounds element defines the bounding box, in local coordinates. If there is a mesh (as in this example), this element is not necessary: the bounding box is automatically set to the bounding box of the mesh. You can also declare some uniforms (uniform1f, uniform2f, uniform3f, uniform4f, uniformMatrix3f, uniformMatrix4f or uniformSampler), some modules and some meshes with nested module and mesh elements. You can also declare arbitrary fields and methods with the field and method elements (id is the field or method name, value its value, which must be the name of a resource). Finally you can associate some flags to the scene node in the flags attribute, which is a comma separated list of flags.

It is also possible to load a full scene graph with the Ork resource framework. Indeed you can also specify in a scene node resource the child nodes of this node. You can specify these child nodes by reference or by value:

<node name="myScene">
    <node flags="camera">
        <module id="material" value="cameraModule"/>
        <method id="draw" value="cameraMethod"/>
    </node>
    <node name="myCube" value="cubeNode"/>
    <node flags="overlay">
        <method id="draw" value="logMethod"/>
    </node>
</node>

here the camera and overlay nodes are specified by value, i.e. directly inside the myScene node (nodes can be nested inside nodes without limit), but the myCube node is specified by reference to the above cubeNode node. The advantage of specifying nodes by reference is that you can reuse them more easily. You can also reference them as individual resources, while nested nodes cannot be loaded separately (you have to load to whole root node to load them).

Scene manager

The ork::SceneManager manages a scene graph:

Methods

A ork::Method defines a behavior of a scene node. It can be a basic task or a combination of basic tasks using sequences, loops or method calls. The basic tasks are presented in the next section. We present here the control structures to combine them, using examples showing how methods can be organized in a scene graph (like scene nodes, methods can be loaded from XML files with the Ork resource framework. The examples below show how these XML files must be defined).

As said above a scene is drawn by executing a specific method on the scene node that is defined as the "camera" node, which must perform all the passes needed to render the scene. This method is often the most complex method in a scene. We show here an example with four passes: a pass to update animated objects, a pass to draw shadow maps, a pass to draw the objects, and a pass to draw overlays (e.g. a framerate indicator). This method is organized as a sequence of 4 loops: there is one loop per pass, and the sequence ensures that the passes are executed in order:

<?xml version="1.0" ?>
<sequence>
    <foreach var="o" flag="dynamic" parallel="true">
        <callMethod name="$o.update"/>
    </foreach>
    <foreach var="l" flag="light">
        <callMethod name="$l.draw"/>
    </foreach>
    <foreach var="o" flag="object" culling="true">
        <callMethod name="$o.draw"/>
    </foreach>
    <foreach var="o" flag="overlay">
        <callMethod name="$o.draw"/>
    </foreach>
</sequence>

The sequence element contains any number of tasks: these tasks can be basic tasks, sequence tasks, loop tasks, etc. They are executed one after the other.

The loop task executes the tasks specified as nested elements on an unordered set of scene nodes (again these nested tasks can be arbitrary: basic tasks, sequence tasks, loop tasks, etc). The nested tasks are executed in sequence on each scene node. The scene nodes are specified via the flag attribute: indeed the loop tasks are executed, by default, on all the nodes of the scene graph that have the specified flag. This default behavior can be changed with the culling and parallel options:

The var attribute is the loop variable. It can be used to reference the scene node to which the loop is currently being applied to. For instance if the loop variable is var="l" then in the nested tasks $l refers to the scene node to which the loop is currently being applied to (nested loops must use different loop variable names).

The callMethod task executes another method on some scene node. It takes a single name argument that specifies the target scene node and the target method, separated by a dot. The target node can be one of the following:

We can now understand the above example as follows: call the update method, in parallel, on all nodes with the dynamic flag, then call the draw method on all nodes with the light flag, then call the draw method on all nodes with the object flag that are visible, and finally call the draw method on all nodes with the overlay flag.

Note that this example use method calls to perform the actual work of drawing shadows maps, drawing objects or drawing overlays. It would have been possible to include the actual tasks to do this directly in the loops, but this would have been much less generic. Indeed, with the above organization, you can use polymorphism, as in object oriented languages, to implement the same method in different ways in different scene nodes. For instance the draw method of a light could either draw a shadow map or do nothing, depending on whether this light should cast shadows or not.

Tasks

This section presents the basic tasks that can be used to implement scene node methods. They are presented via their XML representation, but you can also use them programmatically. There are tasks to select a framebuffer and its attachments, to set its associated pipeline state, to select a program, to set transformation matrices from local to world, camera or screen space, to draw a mesh, and to draw some information messages. This section also shows how you can define your own tasks if needed.

setTarget task

The ork::SetTargetTask task selects a framebuffer and its attachments. It can be used to select the default framebuffer, or an offscreen framebuffer with some specific attachments. To select the default framebuffer, use:

<setTarget/>

To select an offscreen framebuffer with some attachments, use:

<setTarget>
    <buffer name="COLOR0" texture="..."/>
    <buffer name="COLOR2" texture="..." level="1" layer="3"/>
    ...
</setTarget>

The name attribute specifies the attachment point (it can be COLORi or DEPTH). The texture attribute designates a texture, and can have the following forms:

The level and layer attributes are optional. They specify the mipmap level of the texture you want to attach (0 by default), and the layer (resp. z slice, resp. face number) of the texture you want to attach, for a 2D array texture (resp. 3D texture, resp. 2D cube texture).

setState task

The ork::SetStateTask task sets the pipeline state of the currently selected framebuffer (read and draw buffers, stencil, and depth tests, clearing, blending, culling and writing states, etc). It has the following form (all attributes and nested elements are optional):

<setState readBuffer="COLOR0" drawBuffer="COLOR1"
        clearColor="true" clearStencil="false" clearDepth="true">
    <clear r="0" g="0" b="0" a="0" stencil="0" depth="1"/>
    <polygon front="FILL" back="CULL"/>
    ... TODO ...
</setState>

setProgram task

The ork::SetProgramTask task selects a program made of one or more modules. It has the following form:

<setProgram setUniforms="true">
    <module name="atmosphereModule"/>
    <module name="camera.material"/>
    <module name="light.material"/>
    <module name="this.material"/>
    ...
</setProgram>

Each module is specified with a module element. The module name can have the following forms:

In the above example the program is made of a module that defines functions for atmospheric effects (designed by its resource name), a module that defines functions to project 3D points to screen space (attached to the "camera" scene node under the "material" name), a module that defines functions to illuminate surfaces from a light (attached to the "light" scene node under the "material" name) and a module that combines all these functions with a surface material (attached to the scene node on which the method that executes this task has been called).

The setUniforms attribute is optional. If present, the uniforms defined in the scene node from which this task is executed will be set in the program. This option can be useful to set object specific values in a program before drawing it on screen (like a color, a texture, etc).

setTransforms task

The ork::SetTransformsTask task can set some transformation matrices from local to world, camera or screen space in a program. It has the following form (all attributes are optional):

<setTransforms localToWorld="..." localToScreen="..."
    screen="..." screenToCamera="..." cameraToWorld="..."
    module="..." worldToScreen="..." worldPos="..." worldDir="..."/>

Both screen and module can be of the form name, this.name, $v.name or flag.name (see Methods).

drawMesh task

The ork::DrawMeshTask task draws a mesh using the currently selected framebuffer and program. It has the following form:

<drawMesh name="..." count="..."/>

The mesh name can have the following forms:

The count attribute is optional (its default value is 1). This integer specifies the number of time the mesh must be drawn (using geometric instancing).

showInfo task

The ork::ShowInfoTask task displays the framerate and other text information in the current framebuffer. It has the following form (all the attributes are optional: their default values are the one of the example):

<showInfo x="4" y="-4" maxLines="8"
    fontSize="16" fontAspect="0.59375" fontProgram="text;" font="defaultFont"/>

The x and y attributes specify where the information must be displayed, in pixels from the bottom left corner of the screen (or, if y is negative, from the top left corner). The maxLines attribute specifies the maximum number of lines of text that can be displayed. The fontSize attribute specifies the vertical size of characters, in pixels. The fontAspect attribute specifies the width / height ratio of each character (a fixed width is assumed for each character). Finally the fontProgram attribute must be the name of a program resource. This program is responsible to draw characters. It must take as input triangles whose vertices have xy coordinates in screen space, and uv coordinates (in the zw coordinates) in a font texture.

By default the showInfo task displays only the framerate. You can display additional text by using the ork::ShowInfoTask::setInfo method in your code.

showLog task

The ork::ShowLogTask task is similar to the showInfo task, but displays the message logs on screen (see Message logs). By default this task is disabled, i.e. it displays nothing, until a warning or error log occurs. It then stays enabled as long as the ork::ShowLogTask::enabled flag is not set to false by the user. If a new warning or error log occurs the task will again enable itself automatically. And so on. This can be useful to be notified of warnings or errors, in particular after an update of resources on disk (see Updating resources). The showLog task has the same format as the showInfo task :

<showLog x="4" y="-4" maxLines="8"
    fontSize="16" fontAspect="0.59375" fontProgram="text;" font="defaultFont"/>

User defined tasks

You can define your own tasks and your own task resources by extending the ork::AbstractTask class. Using the basic pattern to define resources (see User defined resources), a new task can be defined as follows:

class MyTask : public AbstractTask
{
public:
    MyTask(...) { init(...); }

    virtual ptr<Task> getTask(ptr<Object> context)
    {
        return new Impl(...);
    }

protected:
    MyTask() { }

    void init(...) { ... }

    void swap(ptr<MyTask> t) { ... }

private:
    ...

    class Impl : public Task
    {
    public:
        ...

        Impl(...) { ... }

        virtual bool run()
        {
            // put your task implementation here
        }
    };
};

with the corresponding resource class:

class MyTaskResource : public ResourceTemplate<0, MyTask>
{
public:
    MyTaskResource(ptr<ResourceManager> manager, const string &name,
            ptr<ResourceDescriptor> desc, const TiXmlElement *e = NULL) :
        ResourceTemplate<0, MyClass>(manager, name, desc)
    {
        ...
        init(...);
    }
};
extern const char myTask[] = "myTask";
static ResourceFactory::Type<myTask, MyTaskResource> MyTaskType;

An example

We can now rewrite the above example (see also An example) by using an Ork scene graph, together with the Ork resource framework:

#include "ork/resource/XMLResourceLoader.h"
#include "ork/render/FrameBuffer.h"
#include "ork/ui/GlutWindow.h"
#include "ork/taskgraph/MultithreadScheduler.h"
#include "ork/scenegraph/SceneManager.h"

using namespace ork;

class SimpleExample : public GlutWindow
{
public:
    ptr<SceneManager> manager;

    SimpleExample() : GlutWindow(Window::Parameters())
    {
        ptr<XMLResourceLoader> l = new XMLResourceLoader();
        l->addPath("resources/textures");
        l->addPath("resources/shaders");
        l->addPath("resources/meshes");
        l->addPath("resources/methods");
        l->addPath("resources/scenes");

        ptr<ResourceManager> r = new ResourceManager(l, 8);

        manager = new SceneManager();
        manager->setResourceManager(r);
        manager->setScheduler(new MultithreadScheduler());
        manager->setRoot(r->loadResource("scene").cast<SceneNode>());
        manager->setCameraNode("camera");
        manager->setCameraMethod("draw");
    }

    virtual void redisplay(double t, double dt)
    {
        ptr<FrameBuffer> fb = FrameBuffer::getDefault();
        fb->clear(true, false, true);
        manager->update(t, dt);
        manager->draw();
        Window::redisplay(t, dt);
    }

    // rest of the code unchanged
};

using the following new resource files:

<?xml version="1.0" ?>
<node name="scene">
    <node flag="camera">
        <method id="draw" value="basicCamera"/>
    </node>

    <node flag="object">
        <mesh id="geometry" value="quad.mesh"/>
        <module id="material" value="basic"/>
    </node>
</node>
The scene.xml file
<?xml version="1.0" ?>
<foreach var="o" flag="object">
    <setProgram>
        <module name="$o.material"/>
    </setProgram>
    <drawMesh name="$o.geometry"/>
</foreach>
The basicCamera.xml file

Task graph

As explained in the previous section, the methods of a scene node are made of basic tasks organized with sequences and loops. Thanks to parallel loops tasks can be executed in parallel, using the multiple cores of modern CPUs. This is especially useful in a context where the scene data must be streamed to the GPU or produced on the fly on GPU (because this data is too large to fit in GPU memory). Indeed, in this case, several threads can be used to produce the scene data, in parallel with the main thread that displays this data on screen. The scene data can even be produced ahead of time in these threads, using predictions of the next viewpoints for the future frames.

However this parallel execution of tasks can not be done without a scheduling framework: there must be a definition of tasks, and the dependencies between tasks must be explicit, so that a task is not started before the tasks that produce the data it needs are all executed. This is why Ork provides such a framework, in the taskgraph module. This framework provides the following features:

Basic tasks

Basic tasks are represented with the ork::Task class. The ork::Task::isGpuTask method indicates wheter this task is a GPU task or not. If it is a GPU task, its context is given by ork::Task::getContext. It is set and unset with the ork::Task::begin and ork::Task::end methods. The task itself is defined by the ork::Task::run method. The task deadline is managed with ork::Task::getDeadline and ork::Task::setDeadline.

Task graphs

Task graphs are represented with the ork::TaskGraph class. In order to create a task graph you must first add some tasks in the graph, with ork::TaskGraph::addTask method. You can then define the dependencies between them with ork::TaskGraph::addDependency.

The ork::Scheduler class is an abstract class that defines how tasks can be scheduled for execution. The ork::Scheduler::run method is used to schedule a task or task graph for immediate execution. It does not return until all the tasks with an immediate deadline have been executed (the task graph can contain tasks whose deadline is not immediate). The ork::Scheduler::schedule method is used to schedule tasks whose deadline is not immediate. It puts these tasks in a pool of tasks to be executed, and returns immediately. This method must not be called if the scheduler does not support prefetch (this can be determined with ork::Scheduler::supportsPrefetch). Finally the ork::Scheduler::reschedule method is used to reexecute some tasks. It marks these tasks and all the tasks that depend on them as "not executed", puts them in the pool of tasks to be executed, and returns immediately.

The ork::MultithreadScheduler is a concrete implementation of ork::Scheduler. Its constructor takes a framerate and a number of threads in parameter. If the framerate is 0 then no framerate is imposed. Otherwise the scheduler tries to impose this target framerate (if necessary it waits between frame to avoid increasing the framerate above the target). The number of threads indicates how many additional threads the scheduler can use, in addition to the main threads. A number of 0 means that all tasks will be executed in sequence with a single thread. Such a scheduler can be loaded with the resource framework, using the following format:

<?xml version="1.0" ?>
<multithreadScheduler name="myScheduler" nthreads="3" fps="0"/>
Note:
The ork::AbstractTask class is not a ork::Task, but a ork::TaskFactory, i.e. something that creates tasks. This means that all the "tasks" presented in the previous section are in fact task factories. This is because, as said above, a task can not be reexecuted at each frame unless a new instance is created each time.

An example

Here is an example of a task graph, with several nested graphs. The basic tasks are represented with squares, the task graphs with rectangles, and the tasks dependencies with arrows:


A task graph example. Note that tasks and graphs can be shared between graphs (e.g., T3).

This task graph is equivalent with the following "flattened" graph, without any nested graph:


The flattened graph corresponding to the above nested graphs.

Note that the shared tasks that appeared in several graphs now appear only once. Note also that the dependency between the task graph T9 and the task T0 has been replaced with several dependencies, between all the sub tasks without dependencies of T9, and T0. If the T0 task is rescheduled, then all tasks are reexecuted. If the T6 task is rescheduled, then only T6, T7 and T9 tasks are reexecuted.

User interface

Ork also gives the ability to easily create Windows and handle events such as those provided by glut.

Event Handlers

The ork::EventHandler class contains functions that should be called when an event occurs. Some of them return a boolean indicating if the event has been handled by this handler. The EventHandler abstraction provides independence from the interface system that you want to use (i.e. glut, MS Windows native windows, ...).

The event functions are the following:

ork::EventHandler also provides enums describing the events (Mouse button name and state, key modifiers, special key names, mouse wheel state).

Windows

Windows are EventHandler themselves, so once initialized, they behave just like any other EventHandler. They can be moved, resized, navigated through,... via this system.

ork::Window is the abstract super class for windows. A concrete subclass ork::GlutWindow is provided. This implementation is based on GLUT.


Generated on Mon Oct 18 09:36:06 2010 for ork by  doxygen 1.6.1