KiithCoding

Defenestrate the bifurcators!

Awesome2D (Bachelor thesis)

Intro

Awesome2D (or Advanced Lighting in 2D graphics) is my bachelor’s thesis, mentored by RNDr. Ladislav Mikeš. Its goal is to create a 2D lighting model utilizing various pre-generated data (such as normal maps) to create an illusion of 3D lighting in 2D graphics, and optionally research and compare various different 2D lighting models.

Implementation

I’m writing a demo application for this project in the D programming language, using the OpenGL API and the GLSL shading language. However, the concepts will be implementation independent.

The project will also involve one or more utility tools to generate data used for 2D lighting; for example rendering 2D normal maps from 3D models.

The code is open source, licensed under the Boost license.

Implementation start

Implementation is in very early stages at the moment; I’ve just started writing a basic rendering API to build the demo application with. Various utility code is recycled from my previous D projects. The rendering API is similar in design to my work on Stellarium, but scaled down to the bare minimum needed for this project, and improved with D-specific features.

Preparing for merge

GSoC 2012 is now over, and the graphics refactor code is ready for merging. During the last two weeks I’ve been in overdrive to ensure my work is complete and ready for inclusion in Stellarium.

I wrote the GL1 renderer backend, a new texture API, and modular shaders. I also integrated the solar system shadow code written by Jörg Müller in his GSoC project, merged new changes from Stellarium trunk and ported them to the new graphics API. Finally, I removed remaining direct GL calls, isolating it completely behind the new API.

API breaking is over

The refactor involved large changes to Stellarium core and broke compatibility with Stellarium plugins using old graphics code or direct GL (which is pretty much all plugins). Breaking compatibility is needed from time to time to avoid the Java syndrome, but requires anyone depending on the broken API to update their code. It should be done as rarely as possible, or with long - but not infinite (Java) - deprecation periods. Therefore it was important that the refactor would be complete, merged once, without requiring more breaking changes to be made in foreseeable future.

During the project I wrote a new implementation for textures, but it was still used through the old API (not exposing the new features). Another problem was shader projection, which would have to be done manually by drawing code without some API changes. I also still had some code that was not yet ported, in particular StelPainter initialization. Keeping parts of the old graphics API would be a bad idea as new code might depend on it, causing more API breakage down the line.

At first I focused on removing remaining direct GL calls and old graphics code (StelPainter, StelVertexArray). That was easier than expected as StelPainter state was no longer used and the new graphics code worked differently, so removing StelPainter didn’t break it. I removed all direct GL calls, but OctahedronPolygon depends on the GLUES library which in turn depends on GL includes. However, it seems GLUES only uses GL defines and enums, and does not actually call GL functions.

Then I completed the API changes. The shader projection implementation only exists for the stereographic projector, but at least the breaking changes are over and hopefully no more will be needed (at least until another refactor is needed in 10 years :) ).

Once I was done with that, I started to merge new changes from trunk so my branch could be merged back. I planned to do this every week, but after a few weeks I forgot about it and my branch diverged significantly. Some new changes still used the old graphics API and direct GL, so I had to port that (Exoplanets and Observability plugins, night mode changes).

Then I merged the solar system shadow code. This went without problems; the only API change needed were float textures needed by the shadow shader to store planet positions.

At 18.8, the code became synchronized with trunk and ready to merge. Currently, trunk is frozen before release (so the GSoC changes can be moved to the next major release), which will happen at 26.8. . I will continue to maintain the branch until it can be merged, and fix any bugs uncovered by testing by other Stellarium project members. I also have some optimizations I want to try out. On longer term I plan to maintain the Renderer subsystem, fixing bugs and making sure API additions are as general as possible, to avoid bloat; but that won’t be full time and I won’t be able to do much work during the university year.

StelQGL1Renderer

Stellarium needs a GL1 renderer backend for old machines and buggy drivers. GL1 fixed function pipeline is easier to use than GL2 shaders and all it took was a straightforward rewrite of the GL2 backend into GL1 code. Also, a lot of GL2 backend code could be shared with GL1 (especially state setup/reset), so that was moved to StelQGLRenderer.

The GL1 backend turned out to be much faster than the GL2 backend; almost as fast as current Stellarium trunk, sometimes faster (e.g. planet drawing). This is unexpected as the code is identical, only shader-less, and I expected it to be slower than GL2. That said, this is good news for exactly those systems that need the GL1 renderer.

Right now, the GL2 backend is default, and GL1 is a fallback used when GL2 fails to initialize. It might happen that on older GPUs/drivers that support GL2 the GL2 backend might initialize correctly but have issues (e.g. shaders too complex for old GPUs, or just low performance). If this happens, we can blacklist some older drivers/GPUs to automatically fail the GL2 initialization.

An existing example of such blacklisting is the newly added float texture support. Floating point textures are needed for solar system shadows, but not supported by open source graphics drivers because of patents (yes, 2D arrays of RGBA values that are floats instead of bytes are patented, don’t ask me why). A StelRenderer function was added to detect float texture support so we can disable solar system shadows. It requires GL 3.0 support (which includes float textures), but always returns false for open source drivers.

Performance

The most significant performance improvement was a rewrite of the internal StelQGLRenderer::makeGLContextCurrent() member function, which previously switched context even if Stellarium’s GL context was already being used. This caused great slowdown that was not measurable by the profiler. Now the context is only switched if a different context is being used. This helped most in situations with many separated textured draws, which sometimes had visible lag before.

Pulsars and Quasars plugins made many redundant draw calls drawing empty text. I’m not sure why this was done but it seemed like a hack to prevent GL state set by previous draw calls from affecting Qt paint engine. As the GL state is now contained within draw calls, these calls were removed.

Rectangle drawing always created and destroyed a vertex buffer. I moved it to StelQGLRenderer so it can reuse the same buffer. More optimization is possible (custom draw code specific to rectangles, a small array optimization on vertex buffer backend, batching of rectangle draws).

Vertex buffer backend originally created for testing (StelTestQGL2VertexBufferBackend) was renamed to StelQGLArrayVertexBufferBackend, with GL1 and GL2 specific code moved to StelQGL1 and StelQGL2 derived classes. This vertex buffer backend still uses vertex arrays. The GL2 version should eventually be replaced by VBOs, or, if Qt3D gets into Qt, Qt3D classes. However, this is unlikely to improve performance and might actually cause slowdown without large changes to some other Stellarium code.

Right now, most graphics data is still generated or modified each frame and explicitly uploading it to the GPU each frame would hardly bring any speedup. So before a considering a VBO backend, most per-frame work like projection should be moved to the GPU or removed. The main problem is StelSkyDrawer which regenerates all star sprites each frame. If the main StelSkyDrawer users, e.g. ZoneArray, are rewritten so that they store the stars in vertex buffers that are initialized once and never modified, performance should increase despite drawing more vertices per frame and the VBO backend (which should bring massive speedup) should become viable. This is not a simple project, however, so it should probably be a long-term goal.

New textures

As I wrote previously, I reimplemented textures so that they could have renderer-specific backends, but due to the scale of the changes required, I wrapped the new implementation in the old (StelTexture, StelTextureMgr) API which worked differently and didn’t expose all functionality of the new implementation.

I now replaced StelTexture with a new interface class, StelTextureNew. I didn’t keep the name StelTexture to avoid silently breaking older code as some member functions have identical names. StelTextureNew is constructed by StelRenderer and wraps StelTextureBackend provided by the renderer backend. Having separate backend and interface class allows transparent texture caching on the backend; if the user deletes a StelTextureNew with a backend stored in a cache, the cache can decide whether to delete the backend or just decrease its reference count.

As I explained before, the new texture code defines states the texture can be in - Uninitialized, Loading, Loaded and Error. Texture creation never fails but the created texture can be in Error state (e.g. if file reading failed) I wanted to simplify this so it would not be necessary to check for these states. I changed the renderer so that if a texture is in a state different than Loaded, it can still be used(bound), but a placeholder texture will be used instead. Now, even if loading fails, code using the texture can still run, and the error is easy to spot as the a generic checkers texture will be seen instead.

The placeholder texture is also used during loading. This can be seen with planets, which load textures asynchronously. When first zooming to see a planet, the placeholder texture can be seen for a brief moment. Previously, the planet would simply not be drawn during this moment. This can be reverted if undesirable, but I think it’s better to see something than nothing, and seeing a sphere with a checkers texture (instead of nothing) makes it obvious that texture loading is stuck, making it easier to fix.

Another addition is texture creation from QImage and raw data. Both are supported to allow in-memory generation of texture data. QImage is supported for convenience, but as QImage doesn’t support every possible texture format (in particular, floating point textures), a raw data loading function was added. This is also necessary for solar system shadows.

After these changes StelTextureMgr was removed as it is no longer used (StelRenderer now creates textures).

GLSL projection, modular shaders

Projection is one of the main blockers for VBO viability. Most vertices are projected on CPU every frame. This can’t be done with the fixed function pipeline, vertex shaders can do it on the GPU, removing the need to modify and reupload vertex data each frame (some code still needs to do this, e.g. StelSkyDrawer, hence this is not the only VBO blocker).

Previously, this would only be possible with custom shaders separate for each case (e.g. planets, point light sources, etc). This would require a great amount of duplicate work so I wanted to do this internally.

I knew that modular shaders can be done with GLSL, but I had no idea about how to do it. Googling didn’t help much - there were various threads about something not working, but I found no detailed information about how it works and no in-depth tutorials. Some people seem to preprocess shaders, generating one large shader for each combination of modules. I tried to do this but it turned out to be too hacky. I ended up trying to use multiple shader sources with separate function declarations/definitions, like C. Fortunately it ended up working just like C.

GLSL has a C-like compilation model. Shader sources can be compiled separately as long as they declare functions they use, even if they don’t contain their definitions. These definitions are only required once the shaders are linked to form a complete program that can run on the GPU. Each shader source just needs to declare the uniforms and functions it uses.

It would be inconvenient to specify shaders to link every time we want to change a function, and I would also need to rewrite the shader API I already wrote, so I turned that into an implementation detail. The user can add a shader with a name, and, using that name, enable or disable it. User-specified shaders will work with shader projection without work on the user’s part - the projector just disables the default (built in dummy) projector shader and enables its own version. Any linking happens behind the scenes. As re-linking each shader on each draw call would be too slow (I tried it; ~0.1 FPS), shader programs are cached so no combination of shaders needs to be linked more than once. All the user specified shaders need to do is declare and call a project() function.

Re-linking/exchanging programs caused a new problem: as StelGLSLShader bind()/release() functions no longer directly bind/release the underlying program, uniform passing was broken. Uniforms can only be passed once the final program is bound, but this only happens in the middle of a draw call. To work around this I needed to delay passing of uniform variables. Due to speed requirements I ended up using a fixed-size array inside the shader backend; uniforms set by StelQGLGLSLShader::setUniformValue() are stored as raw data in an array of bytes and this data is interpreted and passed just before drawing. The only drawback of this is that the total size of uniforms is limited; currently 512 bytes, but it can be expanded if needed (an assert will trigger in case of overlow, with a message saying that the limit can be expanded). Currently, no shader (including solar system shadows) uses more than 256 bytes of uniform data.

Other changes

  • Added a class to iterate over triangles in vectors of vertices with any vertex type (replacing StelVertexArray functionality).
  • Readded outline drawing of spherical regions, and fixed StelSkyImageTile debug code.
  • TextParams now has a constructor taking projector and 3D coordinates, determining 2D position of the text internally.
  • StelRenderer can work with StelProjector* as well as StelProjectorP. This was needed to avoid circular dependencies in StelSphereGeometry.
  • StelCircleArcRenderer can now draw series of great circle arcs (needed for plugins and spherical regions outlines).
  • Improved debug output.
  • Added GPL headers.
  • Changed most setGlobalColor() calls to the non-vector overload (which was added later during the project).
  • Optimized culling in StelRenderer::drawText().
  • Minor optimizations, especially devirtualization and inlining of some functions.
  • Removed temporary StelSphereGeometry code.
  • Renamed StelTextureParams to TextureParams.
  • Simplified sphere and ring generation code.
  • Qt painting is now an implementation detail of renderer backends.
  • BlendMode state is now fully contained within draw calls.
  • Fixed texture state inconsistencies.
  • Fixed a bug that prevented changing observer’s planet.
  • Fixed a planet shaking bug with refraction projection.
  • Worked around some Qt paint engine bugs (both GL1 and GL2 paint engines expect particular GL state to be set).
  • Fixed memory leaks and branch-on-uninitialized-value bugs found by Valgrind.
  • Fixed shader warnings on Intel, NVidia and AMD GPUs.
  • Fixed compiler warnings (there are some compiler warnings left in release mode, but they seem to be caused by temporaries created by compiler optimizations)
  • All documentation in the Renderer subsystem has been reviewed and improved.
  • Modified documentation and functions all over the code to reflect the fact that GL is no longer used directly and might not even be used at all.
  • Fixed most Doxygen warnings.
  • Various minor bugfixes and cleanup.

Mesh generation, moving over 2

I’m now porting remaining direct GL and StelPainter code to the Renderer API. This again required extending the API, mostly to work with the stencil and depth buffer and to generate 3D shapes such as spheres and cylinders.

Generating 3D shapes

Some code in src/core/modules needs to draw various 3D shapes. Various types of Landscapes need to draw the surrounding landscape and fog on the interior faces of a sphere or a cylinder, planets need to be drawn as spheres and so on.

Previously, these were usually generated from scratch every frame using static vertex arrays that were reused (but completely rewritten) every frame. StelVertexBuffer, however, is designed to work with any kind of storage (e.g. VBOs), so this kind of optimized generation on every frame is not viable without adding special functions such as drawSphere, drawCylinder and so on to StelRenderer.

I didn’t want to make StelRenderer bloated and force every backend to implement such a huge amount of code so I rewrote shape generation on top of StelRenderer and StelVertexBuffer instead. Building such vertex buffers would not efficient as directly working on arrays, so it would lead to slowdown if this geometry was regenerated every frame. So I concentrated on generating the data only once, or only when needed.

StelGeometryBuilder

To organize the generation code, I added a new class, StelGeometryBuilder. Right now, this class only acts as a namespace and has no data members, but the functions are non-static so they might use StelGeometryBuilder for variables shared between subfunctions.

StelGeometryBuilder has member functions such as buildCylinder or buildRing. The general idea is that the user manages vertex (and sometimes index) buffers and provides them to the generator functions. This allows the user to reuse the buffers.

With spheres (generated in multiple ways - lit, unlit, and fisheye-mapped), this was to be too inconvenient due to a huge number of generation parameters and a need to provide a particular number of index buffers (for stacks forming the sphere, each of which is a separate triangle strip). To simplify work with spheres, another class was added: StelGeometrySphere , which encapsulates a generated sphere. This is also constructed by StelGeometryBuilder, by one of three functions: buildSphereUnlit, buildSphereLit and buildSphereFisheye. However, geometry itself is only generated on demand when the sphere is first drawn (using the draw member function). Vertex and index buffers of the sphere are inaccessible to the user, but to reuse the buffers, sphere parameters (such as geometry detail, i.e. how many faces the sphere has) can be changed, which will trigger the sphere to regenerate (using the same buffers) at next draw call.

As the sphere class turned out to be very convenient, it might be a good idea to wrap other possible generated shapes in such classes, where generation is used heavily.

A few examples on how geometry is generated with StelGeometryBuilder follow.

Generating a cylinder (without the top and bottom caps)

// Note that this would normally be divided into some initialization function,
// drawing and deinitialization function.

// VertexP3T2 is a generic vertex type that can be included through a header
StelVertexBuffer<VertexP3T2>* cylinder =
     renderer->createVertexBuffer<VertexP3T2>(PrimitiveType_TriangleStrip);
// Build a cylinder, writing vertex data to specified vertex buffer.
// The cylinder has a radius of 5, height of 15, and is composed of 64 slices.
StelGeometryBuilder().buildCylinder(cylinder, 5.0f, 15.0f, 64);
renderer->drawVertexBuffer(fogCylinderBuffer, NULL, projector);
delete cylinder;

Generating a sphere (unlit sphere in this case)

// Note that this would normally be divided into some initialization function,
// drawing and deinitialization function.

// Radius 20, 40 rows and 40 columns forming the sphere,
// and a bit oblate.
const SphereParams params = SphereParams(20.0f).resolution(40, 40)
                            .oneMinusOblateness(0.9);
StelGeometrySphere* sphere = StelGeometryBuilder().buildSphereUnlit(params);

sphere->draw(renderer, projector);

// Decrease the resolution
sphere->setResolution(20, 20);
// The sphere is regenerated as the parameters have changed, and drawn with lower resolution.
sphere->draw(renderer, projector);

delete sphere;

Index buffers

Previously, many generated draws didn’t use index buffers, increasing vertex count by a factor of 2 or more. I rewrote them to use index buffers where possible. Due to the unfortunate fact that we’re projecting on the CPU, this initially slowed down the code as most of the vertex buffer was projected for each index buffer used in drawing. This was particularly bad with spheres, which now use many index buffers with a single vertex buffer.

I optimized vertex projection for cases when index buffers are relatively small to only project the vertices used. However, a much better solution would be to track when a StelProjector is changed (so a single vertex buffer can be projected once per frame, regardless of how many draws it was involved in), and even better, GPU based projection with GLSL.

Shader lighting

The worst case of geometry generation was Planet, which regenerated spheres used to draw the planets on each frame. It was easy to avoid this when lighting was disabled (e.g. Sun), as the sphere generation always had the same parameters except for subdivision - and generating the sphere only once would outweigh generating it every frame with potentially lower detail, as the generation was more expensive than the on-CPU projection (which is the other major piece of code run on every vertex every frame).

A bigger problem was generating a lit planet, which baked the lighting into vertex colors. The lighting generation depends on StelProjector, which might change between frames. However, the lighting code was very simple and only depended on a few variables. This made it easy to implement it in shaders, passing these parameters as uniforms.

Shader API and vertex buffer backend were modified so a vertex attribute storing vertex position before projection can be added if requested - this is needed for lighting and might be useful for other shaders. Currently, the shader lighting code is an exact copy of the lighting done on CPU when generating a lit sphere, but with shaders it might also be viable to implement more advanced lighting without significant performance penalty.

Porting to StelRenderer

I continued to port the code based on direct OpenGL and StelPainter to the new Renderer API. I’m now done with the code in the src/core/modules directory and am now working on the plugins included in the Stellarium repository (plugins directory). This was the code that depended on geometry generation detailed above.

I also noticed a few bugs in my code while porting, in particular, the Oculars plugin was broken for a while due to a culling bug. These have been fixed.

Depth/Stencil buffers

Code in src/core/modules/Planet.cpp uses some depth and stencil buffer tricks to draw planet rings and lunar eclipses. As StelRenderer should support any backend, it would not be a good idea to expose the full power of depth, and stencil buffer, as some backends might have problems implementing that. Instead I chose to enumerate the various ways we need to use the depth and stencil buffer. For example, the DepthTest enum allows to disable depth test completely, do read-only depth test (draws don’t write their own depth values) or read-write depth test.

Stencil test can be disabled, set so that draws write the value 1 to the stencil buffer for pixels drawn, and it can be set so that only pixels with value 1 in the stencil buffer can be drawn to.

Enumerating the ways we want to use stencil/depth buffer might seem unelegant and greatly reduces the power of OpenGL. New depth/stencil effects might require expanding the enumerations with more “modes”. However, this is an advantage for backend implementors as it is easier to support a finite number of ‘tricks’ than a whole range of functions and their combinations (for example glStencilFunc, glStencilOp and the possibiities they cover).

Separating Renderer state from GL state

OpenGL behaves as a state machine, and any state set (say, stencil test) that is not reset might unintentionally affect following draw calls. This is somewhat OK if it is only Stellarium code that does the drawing, as we can find fix the bug. However, Qt on some machines uses an OpenGL backend for drawing which might act in unpredictable ways. From time to time stray state can mess up Qt drawing. In order to avoid this, such state is now set at the start of a draw call (drawVertexBuffer), and reset afterwards. This might be changed if it measurably decreases performance, but it is quite advantageous to control what GL state does a draw call leave.

Current status

I’m now close to moving all user code to draw with the Renderer subsystem. Once that is done, I can remove StelPainter, its initialization and global variables. Once that is done the GL code will be completely isolated.

The graphics refactor is not the only graphics related project in this GSoC: Jörg Müller is working on solar system shadows which should be built on top of the refactor. The Renderer API might be modified for this purpose if needed.

It’s been a while since the last time I pulled back new changes from the trunk, so I’m also going to work on that. Finally, the OpenGL 1 backend work has not even started. This is not as large a problem as it might seem, though, since most of the code is shared (in StelQGLRenderer, which is the parent of StelQGL2Renderer and the not-yet-existing StelQGL1Renderer).

As a whole, I’m behind schedule I set at the beginning. I’m still on track to get the refactor done and ready for merging, but any serious optimization work might wait till after the end of GSoC - I intend to continue working on it (although once the semester starts I will have much less time). The backend is still very similar to previous code, e.g. vertex arrays are still being used. However, now that the code is isolated, changes like replacing vertex arrays with VBOs or Qt3D classes can be done in one location (in this case, vertex buffer backend) instead of all over the code.

Currently, performance of the refactored code is significantly worse than Stellarium trunk. Release build on my machine is running with only half the FPS compared to trunk. This is expected - I did very little optimization work so far. It is likely that a bit of work with perf/oprofile/callgrind will considerably improve performance.

However, I expect that even after optimization the new code is going to be somewhat slower until the main graphics problem is solved - which is that most graphics data is generated or processed from scratch on every frame. (The most significant problems are vertex projection, which could be moved to the GPU using shaders, and point source drawing, which might be partially cachable) The main reason for this slowdown is that the new API doesn’t allow direct manipulation of vertex data in memory - as it might be stored elsewhere, e.g. VRAM.

Other changes

  • GLee is no longer needed and has been removed.
  • Back or front face culling can now be used in StelRenderer.
  • Line stippling used for constellation boundaries was removed. It could only work with the GL1 backend. While it could be faked to some degree by using textures or shaders, if this done, it should be done on top of Renderer, not inside it.
  • Added some generic vertex types to be used with vertex buffer generation code.
  • Fog cylinder used in LandscapeOldStyle is now reused between frames.
  • More code examples in documentation.
  • Various bugfixes.
  • Fixed some build warnings (other warnings are known and will be fixed as old code is removed).
  • Added a very simple light source struct, used in lit sphere generation and shader lighting.

More API work, moving over

This update summarizes the progress during the last two weeks. Most of the work involved moving code to use the new renderer subsystem, but there was still some API work (in particular, a shader API).

API additions

StelGLSLShader

Some Stellarium code requires direct usage of shaders, and I expect the amount of this code will only increase. However, shaders are an implementation detail of a Renderer backend and might differ significantly between backends (e.g. GLSL for OpenGL2/ES, HLSL for DirectX, nothing for GL1 or some custom DSL for a software renderer).

Instead of attempting to wrap all of these behind a completely generic “Shader” interface, StelRenderer can report whether it supports GLSL or not and if it does, it can create a GLSL shader - StelGLSLShader interface, which might be implemented differently between different GL based backends (in our case, StelQGLGLSLShader). This is because a generic Shader would require user code to supply shader code for any supported shader format. The user can check if GLSL is supported, if not (GL1), use shader-less code (which will work with any backend, regardless of shader support, as e.g. a GL2 backend will handle it using shaders internally), and have optional shader-based code used when GLSL is supported. If ever needed, a check for HLSL support and a HLSL shader API can be added in future.

Before drawing with a shader, the shader must be bound, which, in case of the GL2 backend, overrides default internal shaders. It also must be released after drawing to reenable default shaders.

API-wise, it is a simplified version of Qt’s QGLShader allowing to set uniforms with Stellarium data types such as Vec3f, even automatically handling transposition for matrices. Attributes are part of the vertex buffer and are set internally by the backend. Only vertex and fragment shaders are supported, but geometry shaders can be added once needed (although they should only be optional).

Other Renderer API changes

I’ve added a StelCircleArcRenderer class which handles circle arc drawing logic (previously in StelPainter) on top of *StelRenderer. The logic is basically unchanged, with slight documentation improvements. It would be good to revisit it later to add caching or further simplifications.

StelRenderer now has text drawing functionality (drawText(), setFont()). As text drawing has many optional parameters, which previously caused unreadable argument lists, a builder struct was used to emulate optional/named arguments, e.g:

renderer->drawText(TextParams(16, 16 "Hello World!").angleDegrees(30.0f).shift(8.0f, 4.0f));

I’ve’ split StelRenderer::drawRect() into drawRect() and drawTexturedRect() for more self-documenting code and to decrease number of parameters. These replace all variations of StelPainter::drawRect2d() and StelPainter::drawSprite2d(), even supporting rotation (around the center of the rectangle, which is what drawSprite2d() handled previously). Previously, drawRect2d() had many versions which made it difficult to read at call site, and drawSprite2d() had very similar functionality, except that it supported rotation and only worked with texturing.

Moving

Most of the work was moving code to the new Renderer subsystem, in particular, the code in src/core/modules . Most of these classes are based on the StelModule interface. To avoid (de-facto) singletonification of StelRenderer (which would make any future refactorings more difficult), a StelRenderer pointer is now passed through the StelModule::draw() function.

The following classes were ported:

  • StelSkyImageTile
  • StelSkyPolygon
  • TrailGroup
  • Nebula/NebulaMgr
  • Atmosphere (this required the shader API)
  • Meteor/MeteorMgr (which previously didn’t seem to be drawn)
  • ZoneArray
  • StarMgr
  • LabelMgr
  • Constellation/ConstellationMgr (work in progress)

There are still more classes that need to be ported here, as well as in plugins.

I noticed that the GeodesicGridDrawer class is never used and doesn’t conform to the StelModule API - it only compiles because StelModule member functions have default empty implementations. I.e; it seems unmaintained.

Meteor drawing didn’t work before the move, but I’m not sure if this is still the case in trunk.

Many of these classes are rather poorly documented/messy, and could use more refactoring. (Again, Meteor, for example the train data member can’t be set from outside and seems like a premature optimization.)

Other changes

  • Added the line strip primitive type.
  • StelSphereGeometry contained a stubbed SphericalTexturedPolygon class - this was removed.
  • StelRenderer now has a drawLine() member function.
  • Graceful failure when trying to load a non-power-of-two texture on a machine that does not support these textures.
  • Fixed some bugs in new SphericalRegion code, and implemented some previously stubbed functions.
  • Removed some now unused StelPainter code.
  • Various minor bugfixes and improvements.

Vertex projection, spherical regions

This update is a bit late; usually I try to make commits as small as possible, but this time I spent more than a week in uncommitable state (something didn’t compile, run or there was a lot of temporary code). Most work was done on drawing of spherical regions and projection of vertices. That said, I’m mostly done with some of the most problematic code.

Dependencies, dependencies

StelSkyDrawer, which I originally expected to be difficult, turned out to be rather simple to get done. I continued to replace current graphics code with code based on the new Renderer subsystem, and then I ran into the StelPainter::drawSphericalRegion() function, which turned out to be a major challenge due to a massive chain of dependencies on other code.

drawSphericalRegion() has many different code paths depending on its parameters such as draw mode (outline, fill, textured-fill) or whether to subdivide drawn triangles. These branch off to other functions, mainly in StelPainter, some of which depend on SphericalRegion functions, and through that, OctahedronPolygon. Some of these branches need StelProjector to project vertices before drawing, while some use more complex logic, also involving StelProjector (the subdividing code paths do the projection while subdividing).

drawSphericalRegion() and its callees operate on a pointer to a SphericalRegion, which could poin to any one of multiple classes derived from SphericalRegion, but based on these flags they make assumptions about implementation details of the spherical region (e.g. whether or not it has texture coordinates), even though it isn’t known which particular class is being used. Because of this, adding a new SphericalRegion class might break the code as it wouldn’t necessarily fit all these assumptions.

The new Renderer subsystem API is designed to be as minimal as possible. This helps both users (less documentation to read) and implementers (backends can be simpler). Ideally, all drawing should be done through one function (drawVertexBuffer()). It is useful to define some convenience functions; e.g. drawRect(), which internally uses drawVertexBuffer() and doesn’t require backends to implement it. However, unlike drawRect(), which is used in various ways, drawSphericalRegion() is only used in combination with a SphericalRegion, so it made more sense for me to move it there as a function, built on top of the Renderer API.

Additionally, a SphericalRegion member function can have different versions for each spherical region class, removing the problem with assumptions about implementation details. Any new spherical region class would simply need to override drawing functions.

SphericalRegion

So I started rewriting drawSphericalRegion() as SphericalRegion::draw(), using the Renderer API. Then I found that I also need to reimplement half of StelPainter and various other code. I made some changes to simplify this.

Code for boundary and and fill draw modes was completely different, and so I separated it into two methods, drawFill() and drawBoundary()/drawOutline() (the latter is not implemented yet). Also, textured fill draw mode is only really used with spherical region classes supporting texturing, while normal fill mode is used with others. However, drawSphericalRegion() could be called with a SphericalRegion without texturing support in combination with textured fill mode, which doesn’t make sense. Due to this I removed the draw mode parameter completely, turning texturing into an implementation detail. That is, if a spherical region class needs texturing, it overrides the draw functions to support it. If we evever need to draw SphericalRegions with texture support without textures, a simple enableTexture()/disableTexture() flag could be added either to the SphericalRegion derived class or to StelRenderer.

Much of the code would be shared between classes in SphericalRegion hierarchy, so I implemented drawFill() as a non-virtual function containing common code calling overridable functions (like vertex buffer caching and drawing). Also, other StelPainter code used by drawSphericalRegion() ended up in SphericalRegion, e.g. triangle subdivision.

Every time a spherical region is drawn, all vertices must be processed, either when subdividing (which invloves projection - outside Renderer) or when projecting vertices (on Renderer side). As the vertex buffer backend might store vertex data in any way (e.g. on the GPU), we add one more copy of each vertex, causing even more overhead. I tried to decrease these costs by caching vertex buffers ready for drawing based on drawing arguments, but this could be done only in few cases (in particular, we can’t cache when subdividing triangles, as subdivision happens together with projection, and we can’t really determine whether the projector (StelProjector) has changed or not.).

The only real solution is to handle projection on the GPU with shaders, which would enable storing vertex data in VRAM in a vertex buffer object. However, that can’t be done without writing all projection code twice as long as we support both GL1 and GL2 (since GL1 still needs to do it on the CPU). One way to do this this is to force all StelProjector classes to have a GLSL implementation (as well as one for any other shading language that might be supported in future). Another is to expose shaders if supported, which in turn would require code outside renderer to have two (or more, for more shading languages) separate paths. I’m not doing this in this project - it is a large project by itself, but if needed, it should be possible to do it after the refactor. Handling projection on the GPU should drastically (order of magnitude?) improve performance as we wouldn’t need to send the data to the GPU with each frame.

I also wrote a vertex buffer “cache” that would let Renderer manage some vertex buffers’ deallocation instead of user code. This is because some code in OctahedronPolygon used vertex arrays in static variables, but vertex buffers can’t really be static - they need a Renderer to be initialized and destroyed (as their backend might be implemented as a VBO, or another not-in-RAM way). With this cache, a static object would have a vertex buffer ID that lazily initialized to point to a buffer once a Renderer is available, with buffer being destroyed and ID invalidated at Renderer destruction. The code was not needed in the end, though, so for now it isn’t committed. It might be useful later, though.

StelProjector and vertex buffers

Non-subdividing SphericalRegion drawing code path needed to project vertices before drawing with StelProjector, which is done in Renderer backend (previously in StelPainter). It could be done outside, but doing it internally enables some optimizations (this feature in particular causes considerable overhead). Until now, this was stubbed in backend code for drawVertexBuffer(). That said, we still only have the vertex array based vertex buffer backends (same principle as previous StelPainter code), so this is pretty much same as in StelPainter.

This code is not yet used/tested, though - it will be tested in following work.

Other changes

  • StelRenderer API:

    • Added new setBlendMode() function to set blend modes. Not as powerful as glBlendFunc() in OpenGL, but much more intuitive, with blend modes named as in image editing: multiply, add, none, alpha. Only blend modes that are actually used exist so backends don’t have to implement every posible blend mode (especially problematic in a software renderer). If needed, adding new modes is trivial.
    • Global color is now set in floating point, so we can set colors outside the 0-1 range.
  • StelVertexBuffer API:

    • New clear() function to clear vertices, allowing the backend to reuse previously allocated space (if possible) when adding new vertices.

    • Triangle fan, lines and line loop primitive types (couldn’t avoid using them).

    • Base class for all vertex buffer types, so we can have a pointer to any vertex buffer regardless of the vertex type it’s templated with. Mostly for internal Renderer usage, like vertex buffer caching.

    • Vertex type definitions are now much simpler.

      Previously, a vertex with a 3D position and color would be defined like this:

      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));
      

      Now, it can be defined like this:

      struct SomeVertex
      {
          // Vertex position in 3D.
          Vec3f position;
          // RGBA vertex color.
          Vec4f color;
          // Vertex attribute metadata.
          VERTEX_ATTRIBUTES(Vec3f Position, Vec4f Color);
      }
      
  • Index buffer API and backend (only a plain array implementation so far). 16bit (default) or 32bit indices can be used.

  • We don’t set point size when drawing stars as points. This only works on very rare GPUs/drivers.

  • Generic triplet struct to replace static arrays of size 3 used to pass triangles, which have to be passed as plain pointers without array length (in C++11, this could be done with tuples).

  • Various minor cleanup, optimizations and bugfixes.

Many small changes

This post summarizes the progress I’ve made during the last two weeks. I made improvements in many areas but there was no single massive new feature. I’m continuing to isolate graphics code and I’ve also ran into some bugs/issues that had to be resolved.

StelSkyDrawer

I started refactoring StelSkyDrawer, which until now used its own drawing code. The drawing code (drawing stars and other point sources) mapped cleanly to the VertexBuffer/Renderer interface, so porting it was straightforward, and resulted in significant simplification.

That said, this code projects the point sources before drawing one-by-one, and doing some extra checks (unlike StelPainter based code that passes a StelProjector to a StelPainter method, letting it project all the vertices when drawing).

For now, I simply hacked the drawVertexBuffer API to disable this projection if it’s done already, but this is not the most elegant solution. I might return later and refactor StelSkyDrawer (and the code that’s using it) so that it doesn’t require such a special case.

There is still drawing other code in StelSkyDrawer that needs to be refactored/isolated, and that is what I’m working on right now.

Texture cache

When profiling texture code I noticed that some textures were loaded more than once - even 3 or 4 times. Qt does have a texture cache, but it only returns previously loaded texture if trying to load from the same QImage. Loading multiple QImages from the same file (sometimes in multiple threads) wastes texture memory as well as the time spent loading.

To avoid this, I added a texture cache based on filenames/URLs. It stores references and reference counts of all loaded textures, and if a new texture is requested from the same file, already loaded texture is returned.

It also handles synchronous and asynchronous loading - if function A starts loading a texture asynchronously and function B requests the same texture, but needs it to be loaded immediately (i.e. synchronously) B waits for the texture to load instead of starting to load it again. Similarly, if the texture is lazy and didn’t start loading yet, it loads it, and so on, for various loading mode combinations)

The texture cache is an implementation detail of the backend and the user doesn’t even have to be aware of it.

StelRenderer API

Renderer can now draw (optionally colored, textured) rectangles on screen. This is just replacing StelPainter functionality - and is implemented in terms of other Renderer functions. If needed, a backend can define a custom, more efficient version.

Blend modes are now set through Renderer. Blend mode names are based on image editing terminology: BlendMode_None, BlendMode_Add and BlendMode_Alpha. More blend modes might be added (e.g. BlendMode_Multiply), but there is no low-level API like glBlendFunc. While powerful, it is less intuitive, and more importantly it might be difficult to implement on some backends (e.g. a software rasterizer) than a finite set of blend modes.

FPS (or rather, update per second) control

If drawing is slow, Stellarium improves responsiveness by drawing frames in parts, allowing other code to run in-between. This requires FBO (framebuffer object) support, which is not present on all machines.

As not every Renderer backend might have FBOs, this means the FPS control depends on implementation details of a particular backend.

To allow Renderer backends to control FPS in the best way they can, I moved FPS control into Renderer, which draws the scene in parts through a wrapper class, RenderClient, that provides access to high-level drawing functions.

Code outside Renderer is unaware of FPS control - it doesn’t know if a frame was drawn fully and put on the screen or just suspended to be finished during the next update. Renderer backends can control FPS in specific ways, e.g. a GL1 backend without FBO support could temporarily disable some more expensive but non-essential features, or even skip frames (none of these things is currently done, but it can now be added without breaking the rest of the code).

ViewportEffects and FBOs

StelViewportEffect is another piece of code that depended on FBOs, but as there might be many different viewport effects (and we don’t want to write one version of each for each renderer), moving that code behind Renderer was not an option. Viewport effects don’t need full-blown FBOs, however - they only need a texture of what is already drawn to the viewport. Renderer now has a function to create a texture of the viewport, which works regardless of FBO support (internally, if FBOs are not supported, pixels are copied from the framebuffer to a texture).

With this change, StelViewportEffect still works outside of the Renderer subsystem and also on machines where it previously did not. That said, the non-FBO solution can cause measurable slowdown on high resolutions (like 1920x1080), so it is more practical to not use viewport effects on machines without FBO support (which is the default setting).

Various other small stuff

  • StelGLRenderer was merged into StelQGLRenderer.
  • Viewport related code from StelQGLRenderer was moved to a new class, StelQGLViewport, as the former was getting too complex.
  • StelQGL2Renderer now checks for features it uses - if they are not present, its initialization fails, allowing GL1 backend (not yet present) to be used instead.
  • More asserts with more descriptive messages. Also many invariant calls. Some of the latter might be removed as they’re slightly, but measurably decreasing FPS (at least in debug builds).
  • Attempts to use unsupported texture units are now ignored, so if you are using e.g. texture unit 8 on an ancient GeForce3 that doesn’t have 8 texture units, it is ignored. (That said, pretty much everything has 4 texture units, and Stellarium doesn’t use multitexturing much at the moment)
  • Enum values in the renderer subsystem now use the EnumType_EnumValue naming scheme. This is verbose, but it prevents potential collisions without requiring to put the enum into a class (to use ClassName::EnumValue) that might or might not be intuitive.
  • Fixed a potential bug - if framebuffers were supported but non-power-of-two textures were not, power-of-two framebuffers would still be used, which wouldn’t work. (Not sure if there is any configuration on which this could happen, though.)
  • Various small bugfixes.

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.

Stellarium OpenGL refactor

Why rewrite something that already works?

When I first looked at Stellarium for a GSoC project, I wanted to work on one of graphics projects proposed on the Stellarium wiki, as graphics is what I’m most interested in.

After taking a closer look, I noticed that while these projects weren’t difficult in concept, I would spend most time fighting with the existing code; OpenGL is often used directly instead of being isolated into a coherent subsystem, and overall, the graphics code is quite messy.

There are some classes wrapping OpenGL but they are rather inconsistent or incomplete. For example, drawing is often done with StelPainter but one-two direct GL calls must be done to achieve the intended effect, defeating encapsulation and duplicating graphics code all over the place.

This makes things like debugging and switching OpenGL versions difficult. There’s no single location to look at if there’s a graphics bug. Inconsistent compiler switches are used to switch between GL1, GL2 and GL ES, and GL calls in various locations don’t even obey those.

Supporting new OpenGL versions (ES 3.0 is on the horizon) or alternative graphics APIs would be even harder, and might require duplicating much of the code without any clean separation.

So, that’s what my project is about. I intend to isolate graphics code behind a set of interface classes and only use graphics through these. Stellarium shouldn’t even know how the graphics are drawn - it could be GL1, GL2 or even a software rasterizer.

Once I’m done it should be easy to e.g. add OpenGL 3 support - you just need to write a new backend based on OpenGL 3, without changing any code that uses graphics. Debugging and profiling should also be easier, as there will be a clearly separated subsystem to look at, and each graphics API will have its own backend, separate from others.

I’m not adding any significant new functionality. There might be some new OpenGL features used on the backend (e.g. VBOs), but the point is to make the code more maintainable.

KISS, DRY, YAGNI...

Current graphics code contains functions or data members that are never used, but “might be used in the future”. Many of these are unmaintained and not in sync with other changes done since they were introduced. Sometimes, they are not even functional. Member functions of classes often duplicate each other’s functionality or implement features that would require other - nonexistent - methods to actually work.

APIs are often unintuitive. StelTextureMgr functions createTexture() and createTextureThread() have similar names and documentation - but they behave differently. Only createTexture() can load a PVR texture and only createTextureThread() looks in the textures directory as a fallback.

I want to ensure there is always one way to do something and that way is obvious and easy to use. A function should have an intuitive name and do no more and no less than what its documentation states.

If I see a feature that is not used and unmaintained, I’m not rewriting it in the refactored code - it can be added once it is actually needed. The point of refactoring is that it should make it easy to add new features.

Isolation

Graphics code should be a clearly isolated subsystem. I’m separating graphics code into classes/interfaces in a specific directory (src/core/renderer). Any new graphics features should be added by adding to these interfaces.

When the graphics code is separated behind a few interface classes (and has no significant global state), it can be switched at run time. For instance, if a GL2 backend fails to load, we can fall back to a GL1 backend - without restarting the application.

I’m making a very clear distinction between interface and implementation, or frontend and backend. When the user draws something, they use the interface. Implementation is inaccessible and hidden from the user.

Interface might be an abstract class (from which an implementation class is derived), a wrapper class (which can internally swap implementation classes or functions), a naming convention used by a template, and so on. The latter is not an option as it is compile-time only.

In this project I’m concentrating on the interface. Once the implementation is isolated behind an interface, it can be improved without changing code that uses it.

Stellarium uses OpenGL 1.x (for old machines and some drivers) as well as OpenGL 2.x and OpenGL ES 2 (for mobile devices). Each of these needs an implementation (although OpenGL 2.0 and ES might share most of it due to their similarity).

Different implementations might be needed in future. For instance a software renderer as a fallback, OpenGL 4 or OpenGL ES 3. This project should make it possible to create such implementations.

Performance

After I’m done with refactoring do I plan to look at performance. Once implementation is behind a stable interface optimizations can be done without breaking user code. Thanks to tools like APITrace graphics performance can be measured without running the application itself, which can help detect which bottlenecks are on the CPU and which on the GPU. It should also be possible to use VBO (Vertex Buffer Objects) to store some or all vertex data, which might result in a large speedup.

It is counterintuitive that moving performance-sensitive code behind a bunch of virtual function calls can improve performance, but as the graphics code is no longer all over the place, it’s easier to keep track of in a profiler. If a particular piece of code is a significant bottleneck, it can be made a special case and separately optimized (even interally breaking the interface/implementation distinction - but without breaking the interface itself).

Another advantage of well separated graphics subsystem is that it should be easy to gather statistics about how it is used. For instance how many vertex buffers are used, how many shader/texture/buffer binds, how many vertices and so on. If I have any time left I’d like to look at that as well.

Current status

Right now I’m still not working full time on this project. This is because I’m still finishing some tests in the university.

I’m not a proponent of detailed planning in programming. From my experience planning in detail anything larger than a class or two results in hitting unexpected issues that haven’t been planned for, at which point the plan becomes useless. It helps to plan on high-level, but determining every function or data member in advance is usually counterproductive.

With this project I’m’ taking a prototyping approach. This basically means I’m coding as fast as I can trying to discover issues by actually hitting them. Once I’m done with that, some code will work, some won’t. Anything that won’t work will be thrown out and replaced.

This is especially important when rewriting code used throughout the project. I can’t keep track of every way graphics code is used and write something that will magically fit everywhere at the first try. So I design an interface, try to use it, and once I find there is some place I can’t use it I modify it or throw it away.

Another important thing is to test the code as I go - I could try to replace everything at once, but that way I’d only have running code in a month or two, and would end up a huge mass of accumulated bugs. So instead I’m replacing code bit by bit, always keeping it in running state. However, this requires writing temporary code only to keep the old and new code working together.

Hello World!

After years of open source experiments, I’m finally starting my first blog.

My name is Ferdinand Majerech and I’m an Informatics s student at the University of Pavol Jozef Šafárik in Košice, Slovakia.

Currently, I’m working on the Stellarium project as a part of Google Summer of Code.

I’ll be writing about any projects I’m working on as well as any random stuff that comes to my mind.