Defenestrate the bifurcators!

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.


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.