Legacy code: revisiting that OpenGL API

In this blog post we will be concerning ourselves with one of those areas in large-scale and long-term project development that no one really wants to talk about: the legacy code base. There’s a good reason why this is generally an avoided topic, just in the same way that open source developers would avoid testing: it’s not as shiny, interesting, new or awesome as the cool features that can be added instead.

Disclaimer: I’ll be focusing on updating your old OpenGL code and the benefits that can be drawn from that. Having a large scope, such as “Legacy code should be worked on!” can be very interesting but it’s harder to actually make a point when generalizing and so it’s all OpenGL from here on in. Furthermore, a large amount of this post has been derived from personal experience. The opinion that I have is from looking at and improving the legacy code that I have seen and as I haven’t seen every single code base that may contain legacy code, your opinion may vary.

What is legacy code?

The term ‘legacy’ has been used for all sorts of scenarios where some code is old or outdated. Even amongst programmers, it seems like a common definition is rare to find [13][14][15][16]. Therefore when I am talking about legacy code, I will be talking about the code that is still in the system, won’t be removed for the foreseeable future, serves an important role (such as backwards compatibility) and no one can be bothered to look into as there are more interesting things to be doing. As an aside, I understand deprecated to mean that a mistake was made, there is a newer and shinier way of doing it, and this way of doing things will be removed.

 

OpenGL and legacy code

So pretty much every code base in a long lasting system will probably have some legacy code. It might be due to some backwards compatibility issues that an operating system must have. It might be due to the unwillingness for coders to look into an area of code that “just works” and has been working as long as everybody can remember. I will be focusing on the latter because of the role that OpenGL plays in that part.

OpenGL (short for Open Graphics Library)  is an API for rendering 2D and 3D graphics, often with the use of hardware acceleration [1]. For a long time, it has been considered the competitor [2] to Direct3D [5] (the 3D part of the DirectX APIs), however, with the decline of computers running Windows (the only platform on which you can use Direct3D), and the increase of users using Unix-based systems that all support OpenGL (or a subset of it in the case of the mobile world), OpenGL is the go-to solution for everything requiring 3-dimensional graphics, that isn’t gaming. But in our interest, it is also an API that has existed since 1992 [4] and thus has seen some significant changes in the way that programmers should be using this API. This is exactly what makes it interesting when applied to legacy code bases, as the old and outdated way of doing things, have been superseded by a cleaner and faster solution. Yet because no one is looking at this code because it’s old and it just works, programmers, users, and the companies themselves are missing out on experiences and performance gains that can be achieved by just doing a little bit of plumbing.

 

Performance difference

A great example of the kind of performance difference that is achievable is the difference between immediate mode OpenGL and using VBOs.

Immediate mode can easily be identified by the intuitive nature of the code. It is a procedural and step-by-step way of drawing objects in a world. Before you want to start drawing you call the glBegin() function with the parameter GL_TRIANGLES (or whatever it is you want to be drawing). To stop drawing objects, you just need to call the glEnd() function. In between is where you actually draw the objects (triangles in our case). So if you have 10,000 triangles, then there will be 10,000 calls to a function that will draw the triangle.

The problem with immediate mode is that it is directly bounded by the CPU of the program. As these individual triangle draw calls are instructions that are executed on the CPU before being sent to the GPU to be drawn, your CPU automatically becomes the bottleneck. What is generally really bad about this is that GPUs are really good at drawing stuff. The reason why most computers contain a dedicated piece of hardware just for drawing pixels on a screen is because the very nature of graphics is not suited to the SISD [9] (Single-Instruction Single-Data) architecture of a processor. So limiting the amount of items that you can draw in your program by your processor when you have a graphics card that can handle significantly more, just seems like a bit of a waste, especially when you could be using the processor’s resources for something else. As a note on the amount of floating point operations per second (FLOP/s) [11]: Nvidia’s GTX280 provides 0.9 Tflop/s while Intel’s Core2Quad CPU only delivers around 0.1 Tflop/s [12].

Since 2003 and the release of OpenGL 1.5 [4], there has been a new way of doing things in OpenGL-land. That’s not to say that immediate mode was deprecated, but the concept of buffer objects was introduced for graphics programmers. Buffer objects are objects that are stored on the graphics card and can be used for draw calls. A simple example, and a common use-case,  is to store all of the vertex data in the buffer object. Creating the buffer object and binding it means that afterwards you can draw all of the triangles contained in that buffer object with a single CPU draw call. The aforementioned example drew 10,000 triangles by having 10,000 draw calls from the CPU. The buffer object can draw 10,000 triangles using a single CPU draw call. Ultimately, this is analogous to the CPU saying “draw all those triangles I told you about” instead of “draw this triangle, and this triangle, …”, which will allow the GPU to do some work while the CPU can continue on something else.

Now since 2008 and the release of OpenGL 3.0 [4]. the immediate mode, amongst over things, has been deprecated. However, I believe that due to the age of the API and the amount of applications that are using OpenGL out there, the OpenGL ARB [3] (Architecture Review Board) cannot remove the functionality without a huge uproar from the industry. Applications whose very core for rendering contains immediate mode OpenGL in the legacy code base.

 

Do APIs always change drastically?

OpenGL is a special case. Most APIs just use the typical semantic versioning (major.minor.path) [10] to signify large changes in the application. If functions have been superseded, then just mark them as deprecated and give all API programmers a warning that this functionality will be removed in 2 or so years time. But due to the longevity of the API, and the amount of long-term applications that rely on this API for 3D rendering, it becomes harder and harder to remove functionality. A similar case can be seen in the Windows API where the API had several functions [7] that were specifically aimed at making life easier for applications going from 16-bit applications to 32-bit, such as long pointers [8]. Now that that’s ancient history, there are still redundant functions, or parameters in functions, that are for that 16-bit conversion. Similar cases exist with the x86 instruction set architecture and their policy for backwards compatibility [6]. Longevity and APIs seem to not go well together.

Cost of rework is too great

But back to the point of legacy code. Why don’t companies and programmers invest more resources in making sure that the legacy code base is up-to-date? I think in lots of cases it is just simply a matter of why bother changing anything that currently works just fine. Why bother wasting resources on maintaining something that already works when those resources could be spent on the new features that will make more people buy the software, thus generating more money. However, this simplistic mindset is exactly what will keep people from seeing what is wrong with an application. A simple example can be illustrated with graphics as the domain: if application A draws something at a low frame rate and people just take it as a given that this is the way that things are, then only when application B comes in and shows that with simple changes the frame rate can be increased dramatically, does application A realize that there is room for improvement and in the process has lost all of its customers to application B. Now in a general case for legacy code, you will have may have no idea what part of the code can be improved so that this doesn’t happen, but with OpenGL, it is known that the immediate mode is significantly slower, yet legacy code bases still use it. Is it the case that in a large-scale project, it is just too much work to change the legacy code?

Personal experience

I have very strong feelings about removing the old OpenGL in legacy code bases because of personal experience. I was working on an application where no one had looked at a portion of the code in a long time because “it works just fine and screwing it up may have major consequences”. After spending quite some time looking through the code, I found that all of the places that actually execute draw calls were all using the immediate mode OpenGL. Within months I had added simple graphics functionalities, the likes of which had not been seen before in the application, and all due to the simple fact that I removed the immediate mode OpenGL that would have made the addition of those functionalities impossible. This is the simple scenario of prototyping in an isolated space to find out if a feature is meaningful before adding it. Of course, prototyping and changing the legacy code base so that these features can be added is very different but at least prototyping will show how valuable these features are and if reworking the legacy code is worth it. But again, this is for a general case. For OpenGL, throw out that old immediate mode for instant performance gains!

 

Conclusion

The problem with longevity in computer software is that the software needs to keep up with the rapidly evolving hardware underneath. Moore’s Law makes it possible to implement a feature in a certain way that would have been really slow 2 years prior. An API has to evolve with this change, and this will mean that a certain way of doing something may not be the right way of doing that same thing in the future. Legacy code has the problem that is often untouched because it currently works just fine. However, because no one is modifying it and keeping it future-proof, it can very quickly become out-of-date, using API features that are no longer relevant. Personally, I found that throwing out the old OpenGL and using current techniques not only gave a huge boost in performance, but also allowed cool features to be added very easily. I’m not trying to blow my own trumpet here, but rather, explain how simple it is to add new and meaningful features by digging around in the legacy code base and removing all of the immediate mode OpenGL.

 

References:

  1. OpenGL – http://www.opengl.org/

  2. OpenGL – http://en.wikipedia.org/wiki/OpenGL

  3. OpenGL ARB – http://www.opengl.org/archives/about/arb/

  4. OpenGL History – http://www.opengl.org/wiki/History_of_OpenGL

  5. Direct3D – http://en.wikipedia.org/wiki/Direct3D

  6. x86 – http://en.wikipedia.org/wiki/X86

  7. Win32 API –http://msdn.microsoft.com/en-us/library/ff818516(v=vs.85).aspx

  8. Long pointers (Win32) – http://msdn.microsoft.com/en-us/library/windows/desktop/ff381404(v=vs.85).aspx

  9. Flynn’s taxonomy – http://ieeexplore.ieee.org/xpl/articleDetails.jsp?arnumber=5009071

  10. Semver –  http://semver.org/

  11. FLOP/s – http://en.wikipedia.org/wiki/FLOPS

  12. CPU  & GPU speeds – http://dl.acm.org/citation.cfm?id=1555775

  13. Legacy code – http://en.wikipedia.org/wiki/Legacy_code

  14. Legacy code – http://stackoverflow.com/questions/4174867/what-is-the-definition-of-legacy-code

  15. Legacy code – http://stackoverflow.com/questions/479596/what-makes-code-legacy

  16. Legacy code – http://programmers.stackexchange.com/questions/94007/when-is-code-legacy