If you have been following this blog, you know about the BHM file format and the OpenGL-based example that I made for it. I have been using Direct3D since around 1999-2000, and before that (and during my Direct3D years), I have been working on my own software renderers for various platforms, including Java and assembly-optimized 486 and Pentium renderers. As I also mentioned before, I was not completely oblivious to OpenGL. I had used it during an internship at a company developing a CAD/CAM/CAE suite for hydraulic manifolds. During this internship I had developed a test application with an OpenGL renderer that I made from scratch. However, this was in the pre-shader era, so it was pretty much a standard OpenGL 1.2 application, without using any of the ‘modern’ extensions that I have used this time around.
But I have been using OpenGL almost exclusively now for the past few months, and I’d like to share my views on the OpenGL ecosystem, from the perspective of an experienced Direct3D developer. My OpenGL code is BSD-licensed and can be found in the repository on the SourceForce project page. Everyone is free to take a look at the code or use it for their own projects. Looking at the code can serve two goals:
- It puts this blog in perspective, as you can see the things I discuss here in a practical context.
- I could be wrong or have made some mistakes in the code. Then people can correct me, so I can adjust my views and improve the code, so we can all benefit.
The API design
The most immediately apparent difference between OpenGL and Direct3D is the way the API is set up. Direct3D uses an object-oriented approach, via the Component Object Model. OpenGL uses a procedural approach. I do not want to get into COM specifics too much, as it has both pros and cons. However, in my OpenGL framework I have adopted some of the ideas that I found useful. By wrapping OpenGL handles into a reference-counted object, it makes it easier to manage and share OpenGL handles between different objects (such as re-using a texture or shader on multiple objects), and through the use of a virtual destructor you don’t have to even know what type of object it is, in order to release the reference and have it cleaned up. If OpenGL just had a glDeleteObject() or such that worked on any OpenGL handle, this wouldn’t be all that useful, but the OpenGL design has separate functions for deleting textures, shaders, vertex buffers and the like.
Another thing is that OpenGL still revolves very much around the idea of a state machine. This means that you often have to call various functions that each change 1 particular state, in order to perform something that is one conceptual operation. For example, setting the vertex data description for a vertex buffer. In OpenGL you have to enable and set the offset for each individual attribute in the vertex (eg position, normal, texture coordinate). It seems that the OpenGL designers themselves are aware of this too, as they recently introduced the ARB_vertex_array_object extension, which is a wrapper around a set of such state changes. It is similar to the idea of display lists which wrapped up legacy state changes into a single object. State machines just aren’t the proper way to do graphics anymore. Direct3D also stems from a similar state machine design, but when Microsoft extended the API to use shaders, they did not extend the state machine to include shader support. Instead, the shaders were almost completely independent from the legacy state machine. In Direct3D 10, the legacy state machine was abandoned altogether, and replaced with a system using a handful of state objects.
The philosophy of Direct3D is that the API should match the hardware closely (as far as a vendor-agnostic hardware abstraction API allows), so that the developer has maximum control over the hardware, resulting in the best possible performance and graphics quality. In order to maintain this philosophy, the API is ‘disposable’. It is updated every few years, according to the latest hardware developments. Each new API consists of new COM objects, and as such does not re-use any of the previous API directly. In some cases the API has been almost completely redesigned, and bears very little resemblance to its predecessor. Developers had to start from scratch with the new API.
With only a few exceptions (most notably software vertex processing), Direct3D exposes only functionality that is physically implemented in the hardware. There is no software emulation fallback when the hardware does not support something. This makes performance more transparent to the developer.
In order to keep the API up-to-date with hardware developments, Microsoft works together with the hardware vendors while drafting the API. Over the years, Microsoft and Direct3D became more influential in the hardware developments because of this. More or less since the Direct3D 9 era, hardware vendors design and optimize their hardware almost entirely towards the upcoming Direct3D API, where earlier hardware often had various extra features not available in the standard Direct3D API, or in some cases lacked some key features of the Direct3D API, or implemented them poorly. These days, a new Direct3D API and the supporting hardware are released at about the same time.
OpenGL on the other hand has always strived for continuity. Initially the philosophy was that every OpenGL feature should always work. If the hardware did not support a certain feature, it was up to the driver to silently provide a software emulation of the feature. Since OpenGL was originally designed for high-end graphics workstations, this was not that big of a problem. However, when the first consumer 3D accelerators arrived on the market, this suddenly was a big problem, as these consumer chips only supported a small subset of the OpenGL featureset, which often led to horrible performance as most of an application had to run in software simulation mode. Since it was impossible for a developer to detect the actual hardware featureset, it was very difficult to work around these performance pitfalls.
Another side of OpenGL is its extension mechanism. Any driver is allowed to expose extended functionality, which the developer can query. This is the complete opposite of the core API, since these extensions will generally only be exposed when the physical hardware supports them. Since this extension mechanism has always been part of OpenGL, the API could easily be extended to include new functionality, without changing the core API. A developer never has to start from scratch with OpenGL. He can just add new extensions to his existing code. The downside to such extensions is that they usually appear as a vendor-specific extension first. They are standardized into a vendor-agnostic version of the extension after-the-fact, by the ARB committee. This means that new functionality in OpenGL generally isn’t available in a vendor-agnostic way until long after the hardware has been on the market, unlike with Direct3D.
However, over the years these extensions have gotten somewhat out of hand. The arrival of programmable shaders and hardware vertex buffers meant that significant portions of the OpenGL API were completely bypassed/replaced with extensions, as the new hardware worked in a very different way from what went before it. In Direct3D this functionality simply became part of the new core API implicitly, and vertex buffers could simply be used on legacy hardware as well. In OpenGL the designers have been struggling with this problem ever since. Originally they wanted to introduce a completely new API design with OpenGL 2.0, but in the end they just moved the most common extensions into the core, which doesn’t really solve the problem (which I will get into later). They then marked various legacy functions as ‘deprecated’, and eventually removed the deprecated functionality in a newer OpenGL revision. But still this doesn’t really solve the problem (which I will get into later).
I personally get the idea that it’s no use to try and change the API now. I have already made my own wrapper objects and functions around some of the more annoying parts of the OpenGL API, and I guess that most developers have done the same. A new API would only lead to extra work for developers, and probably doesn’t have any extra benefits.
A major difference between Direct3D and OpenGL is that Microsoft not only offers the API itself, but also the SDK, containing many valuable samples, tools and documentation. There is an “OpenGL SDK” on OpenGL.org, but it is mostly a compilation of the reference documentation and third-party tools, samples and tutorials. As such it is quite incoherent, and not all-encompassing. An extra problem is that the documentation itself is not very up-to-date. The OpenGL reference only documents the OpenGL 2.1 API, and the rest should be taken from the new API specifications and independent extension documentation. The extension documentation in itself is horrible, since it is a combination of the discussion during standardization of the extension, a brief documentation of the new functionality, and a lot of references to the OpenGL reference… so you need to filter out and piece together the information that you’re actually looking for. Not very productive or inspiring.
Direct3D offers you many useful tools for determining the capabilities of your hardware, compiling and troubleshooting shaders, profiling your code, and the invaluable D3DX library for a lot of functionality that is not covered in the Direct3D API itself, but is indispensible for most 3D applications, such as texture loading and basic matrix/vector/quaternion math operations.
For OpenGL, you are better off downloading an SDK from one of the major vendors, such as nVidia or ATi. Trouble there is that those are not exactly vendor-neutral. Most of the things included in the Direct3D SDK exist for OpenGL as well, but as third-party tools and libraries. For this reason I have made my own GLUX library, to be more or less a D3DX replacement for OpenGL. I have found that although various libraries exist, you still need to piece them together yourself, in order to have them integrate with OpenGL as seamlessly as D3DX does with Direct3D. For example, texture loading is something that you take for granted in Direct3D. D3DX supports all the major file formats, and automatically detects the file format for you, you just need to pass a filename. In OpenGL the API itself only allows you to pass a raw buffer of pixels during texture creation. So you have to load it from disk yourself, and perform any pixel conversion if necessary. I chose to wrap FreeImage for this. Now I can have the same userfriendly texture loading as with D3DX.
Since extensions are so important to modern OpenGL, as I mentioned above, you also need to have an extension loading framework. The extensions are rather crude in nature… You simply load each function’s address by its name (much like GetProcAddress() for Windows DLLs). Therefore there are various frameworks available to make the task of checking for extension support and loading functions more automated. Sadly they are all third-party open source projects, maintained by one or two individuals in their spare time, and as such, they are usually not up to date with the latest OpenGL API and extensions.
Compiling shaders is another thing. In Direct3D you get a commandline compiler, which can compile your shader and tells you of any compiler problems, so that you can fix your shader code easily. In OpenGL there is no such thing, and you manually have to implement a lot of code to get any feedback from the compiler in case of problems (which I used as an example in this earlier blog). I have added this to my GLUX library as well.
All in all, OpenGL literally is ‘all over the place’, and every developer has to go out and find all the useful libraries and tools by himself (there are usually various very similar offerings, so you have to try them all and figure out which one works best for you… which is something that is apparently popular in the *nix world, but I personally prefer having one good product that does everything over having the choice between various half-baked alternatives), and piece together his own ‘SDK’, and reinvent the wheel on more than one occassion.
The driver model
Here is another big difference between Direct3D and OpenGL. In OpenGL, the driver implements both the hardware-dependent part and the entire high-level API itself. In Direct3D, there is a split between the hardware-dependent driver and the API itself (the runtime). The hardware developers only need to write the hardware-dependent driver. The API is implemented by Microsoft itself, including the shader compiler and everything.
Microsoft’s approach has various advantages, for example:
- The hardware-dependent driver is smaller, simpler and easier to implement for the hardware vendors. This means it is easier for them to deliver up-to-date, optimized, compatible and reliable drivers.
- The API and compiler are consistent among different hardware. You will not run into problems compiling the same shader on a different machine.
- The API and compiler can be upgraded independent of the hardware vendors. This means that for example DirectX 11 became available to all Vista users with just a simple Windows update, on all DX 9.0c and newer hardware. Even hardware that is no longer supported by the vendors.
This is where I get back to what I mentioned earlier, about how newer OpenGL API revisions don’t necessarily solve legacy problems. Because of a combination of OpenGL drivers being a lot of work to write, debug and optimize, and the relatively low popularity of OpenGL, especially for games, hardware vendors don’t give OpenGL driver development a lot of priority. This means that the OpenGL implementation in the drivers is not always entirely up-to-date. Older hardware simply will no longer receive driver updates, so their OpenGL implementation is effectively frozen in time. In the case of Intel, even their current hardware is lagging behind with OpenGL. Although their chips have been fully DX10-compliant for a few years now, their drivers only support OpenGL 2.1 at best. This means that a lot of functionality of the chips is simply not exposed by OpenGL (or extensions). The GLSL shader compiler is very limited, not allowing you to do various things that have been supported for years in Direct3D HLSL on the same hardware.
So that is a huge pitfall. You can introduce new OpenGL API revisions, but as long as they require a driver update for all hardware, a lot of hardware just is never going to receive support. This also means that developers can’t simply adopt the newest OpenGL API revision, because it isn’t compatible with older hardware. They will have to build a fallback path as well. The irony is that so far, OpenGL API revisions have only adopted functionality from existing ARB extensions into the core, and eliminated some legacy functionality. This means it is not at all compelling to use a newer API revision. After all, you could already use the functionality through extensions anyway, and it is easier to use an older API revision with optional extensions, than to use a newer revision and have a complete fallback path for an older revision. The eliminated legacy functionality also isn’t very interesting for the developer. It may be nice for hardware vendors in the distant future, when they can remove the older revisions from their drivers altogether, and only implement the cleaned up functionality in the new revision, but a developer wasn’t using that legacy functionality anymore anyway, it doesn’t really matter if the actual functions are there or not.
Ofcourse I am not the only one who has noticed this problem, and people are already working on a solution, in the form of the Gallium3D project. We can only hope that this takes off, as it will bring the benefits of the Direct3D API/driver model to OpenGL/non-Windows platforms. It might finally unlock a lot of functionality that is left untapped in the Intel GPUs for example, which can make life for developers a lot easier, as they don’t have to cover that much ground with fallback paths.
In my opinion, the ‘weaknesses’ of OpenGL aren’t all that dramatic… I managed to work my way around them, and I suppose that most other developers did as well. Having said that, I do see plenty of room for improvement. I tried to do my share by releasing the GLUX library as open source, so that others can build on my work, rather than having to start from scratch and re-invent the wheel. But I think Khronos (the organization which is curently behind the OpenGL standard) can and should do a whole lot more. I think they should back a project such as Gallium3D very aggressively. I also think that they should put some effort into providing up-to-date documentation, and perhaps offering a real SDK as a single download, containing all the useful tools and libraries in one place (they’re all free, mostly open source, so no problem in distributing it… in fact, anyone could do this, even I could… I could extend my GLUX project to be a more complete SDK… but right now I am not willing to invest the time). I also think that the extensions are a fundamental part of the OpenGL API, and they should adopt one of the extension frameworks, and employ someone to keep it up to date. It’s very simple… You define the standards. Everytime a new extension or API revision is standardized, have someone work on the extension framework to implement it RIGHT AWAY. In fact, I would urge you to not even publish the extension until the extension framework has implemented it as well. It’s not a lot of work, but someone has to do it, and we know from experience that volunteers aren’t always up for the job.
If Khronos (or someone else, perhaps one of the larger Linux distrubutors, such as Ubuntu or Red Hat) could work on these things, I think it would make OpenGL considerably more powerful and attractive, especially for new developers. It could make OpenGL more competitive with Direct3D, but more importantly, it would give non-Windows platforms a better 3D environment. To be honest, I don’t think OpenGL can ever really threaten Direct3D on the Windows platform… But that is a non-issue on Linux or FreeBSD for example. I developed part of my BHM sample on FreeBSD, to be sure that it would be platform-independent… but I found that the environment on FreeBSD is very limited. Driver support is poor, there is no SDK or documentation readily available etc. It could greatly benefit from Gallium3D and from a standardized SDK.