KiithCoding

Defenestrate the bifurcators!

Interfaces

This is an introduction to interfaces Of the new graphics subsystem. It is not a complete list - more interfaces will be added and some might be heavily changed (I’m still prototyping).

Renderer

StelRenderer is the central class of the new graphics subsystem. Its main purpose is constructing other graphics related objects and drawing.

For instance, if you have a GL2 based renderer, and request a new vertex buffer, you get a GL2 based vertex buffer - if VertexBuffer was constructed by itself you would have to specify whether to construct e.g. a GL1 or GL2 version, defeating the purpose of the refactor.

Renderer is a “singleton” in the sense that there is only one - but I want to avoid making it an actual (globally accessible) singleton if possible. Initialization can gracefully fail so a different implementation can be swapped in. If a GL2 backend fails, GL1 might still work.

Currently StelRenderer is a base of the following class hierarchy:

StelRenderer (abstract interface)
|
- StelGLRenderer (abstract)
  |
  - StelQGLRenderer (abstract)
    |
    - StelQGL2Renderer

StelRenderer defines the interface and has no functionality except convenience wrappers. StelGLRenderer contains code common for any OpenGL backend (even non-Qt based), but as we’re depending heavily on QGL, there’s not much there and it will probably be removed. StelQGLRenderer contains code shared between Qt-OpenGL based backends, QGL1 and QGL2. StelQGL2Renderer is the actual GL2 backend, using QGL.

After the GL2 backend is done, I’ll work on a GL1 backend, and maybe a GLES2 backend, but the latter might not be needed as QGL abstracts away most differences between GL2 and GLES2.

VertexBuffer

StelVertexBuffer constructs and stores geometry data. It is templated by a custom vertex type, which should be a struct with data members representing vertex attributes, e.g. position, color, texture coordinates and so on.

StelVertexBuffer can add, read and modify vertices of this type. That is, on the outside, it acts as a dynamic array containing user-defined vertex structs, leaving no way to mess up geometry as with separate vertex attribute arrays which might fall out of sync.

The interface is minimal and simple - only 3 functions are used to construct geometry (more functions might be added to work on more vertices at once if performance is too slow working on them one at a time).

Internally, vertex data is stored in child classes of StelVertexBufferBackend which is wrapped by StelVertexBuffer. This is separate to get around incompatibilities between virtual functions and templates. Vertex storage itself is completely separate from the interface - right now, even though it looks like an array of vertex structs, internally each vertex attribute is in a separate array. Storage format can be changed without affecting the user - this should allow us to use VBOs on some or all vertex data.

StelVertexBuffer can be locked or unlocked. It is unlocked during geometry construction and must be locked before drawing - locking can be used e.g. to upload geometry to the GPU.

Currently, the StelVertexBufferBackend hierarchy looks like this:

StelVertexBufferBackend (abstract)
|
- StelTestQGL2VertexBufferBackend

The weird name of the implementation reflects the fact that it is temporary, but as its (vertex array style) implementation is straightforward, it might be kept to test other implementations.

Code example showing the current API (note - drawing code is not shown as right now it is a mixture of new and old API - using StelPainter for some things):

// Vertex format specification:
struct SomeVertex
{
    // Vertex position in 3D.
    Vec3f position;
    // RGBA vertex color.
    Vec4f color;
    // Metadata describing vertex attributes (defined outside the class body).
    static const QVector<StelVertexAttribute> attributes;
}
// This might need to be defined in a .cpp file to prevent multiple definition errors
const QVector<StelVertexAttribute> SomeVertex::attributes =
    (QVector<StelVertexAttribute>() << StelVertexAttribute(AT_Vec3f, Position)
                                    << StelVertexAttribute(AT_Vec4f, Color));

...

// Code generating geometry in our vertex format
StelVertexBuffer<SomeVertex>* constructPyramid(StelRenderer* renderer)
{
    StelVertexBuffer<SomeVertex>* vertexBuffer =
        renderer->createVertexBuffer(Triangles);

    // Note - I didn't test that this is an actual pyramid
    Vec3f nw = Vec3f(-1.0, -1.0, 0.0f);
    Vec3f ne = Vec3f(1.0,  -1.0, 0.0f);
    Vec3f sw = Vec3f(-1.0, 1.0, 0.0f);
    Vec3f se = Vec3f(1.0,  1.0, 0.0f);

    Vec3f top = Vec3f(0.0, 0.0, 1.5f);

    Vec4f red   = Vec4f(0.0, 0.0, 1.5f);
    Vec4f white = Vec4f(0.0, 0.0, 1.5f);

    //Bottom
    vertexBuffer->addVertex(nw, red);
    vertexBuffer->addVertex(ne, red);
    vertexBuffer->addVertex(sw, red);

    vertexBuffer->addVertex(ne, red);
    vertexBuffer->addVertex(se, red);
    vertexBuffer->addVertex(sw, red);

    //Walls
    vertexBuffer->addVertex(sw,  red);
    vertexBuffer->addVertex(se,  red);
    vertexBuffer->addVertex(top, white);

    vertexBuffer->addVertex(se,  red);
    vertexBuffer->addVertex(ne,  red);
    vertexBuffer->addVertex(top, white);

    vertexBuffer->addVertex(ne,  red);
    vertexBuffer->addVertex(nw,  red);
    vertexBuffer->addVertex(top, white);

    vertexBuffer->addVertex(nw,  red);
    vertexBuffer->addVertex(sw,  red);
    vertexBuffer->addVertex(top, white);

    //Lock the finished vertex buffer.
    vertexBuffer->lock();

    return vertexBuffer;
}

I’m also planning to add an IndexBuffer interface. IndexBuffer will optionally be used to specify which vertices to draw, removing duplicate vertices (In particular, the code example above would be simpler/more efficient with an index buffer).

Texture

Right now I’m working on textures. Textures in Stellarium are a bit better encapsulated than other graphics code, and the implentation can be changed without changing the interface. Textures are represented by the StelTexture class, constructed by StelTextureMgr.

Some textures must be loaded asynchronously, especially when loading from the web. Due to this texture creation can’t succeed or fail immediately - an error might happen later if the texture is loaded asynchronously or lazily.

My current status is that StelTexture works as before, but is a wrapper around StelTextureBackend, which can be implemented differently by each renderer backend.

Initially I tried to keep image loading in StelTexture and only the texture code in backend, but I ended up with everything in the backend as different backends might load data differently - e.g. on some mobile devices we have a compressed texture format that can only be used by a particular backend - if image loading was in StelTexture, it would have to know about implementation details of any backend with specific image loading.

Unlike StelTexture, StelTextureBackend can clearly determine whether the texture is not yet loaded, loading, loaded or an error occurred (TextureState), and specifies possible transitions between states (in particular, a texture can only get into Error state while loading).

Eventually I plan to remove StelTexture and replace it with StelTextureBackend (renaming it) as the latter offers the same functionality without problems of the former - but for now I’m keeping StelTexture so I can work on other code (especially how StelProjector interacts with vertex buffers and on StelSkyDrawer, which will likely pose most problems during refactoring).

I removed texture functionality that is not used at all currently to avoid code rot (in particular setImage(), which should upload a different image to the same texture - but didn’t even work, and loadingProcessFinished - signal emitted when asynchronous loading of a texture finished).

These should be easy to add once they are actually needed.