I have been using OpenGL and OpenGL ES for a long time, and I know them pretty well. They're also really, really annoying to use, and I haven't really seen this discussed anywhere. Here are some examples of bad design I have noticed:
- Global states everywhere. There are states for every feature, often implicit, sometimes hidden. Without reading documentation in detail it's hard to figure out what values are legal for which states and what influences them. Since they are global, it's a nightmare to debug, even with a single-threaded program.
- Error handling is quite possibly hands down the worst I have ever seen in any framework. I consider the fact that errors need to be checked manually a hardware limitation, so I'll let that slide. But there are only a couple of error flags. Those flags are often used multiple times within the context of a single function to signify different things, leaving you with no options but to guess why it occurs. There is only one "slot" for errors so effectively if you don't check after every function, you will never get to the bottom of it properly, since errors will get lost if there is more than one before checking.
- Function signatures are often counterintuitive and misleading. Arguments that take any number, but actually only a couple of values are legal. Enums Arguments that have only one possible legal value (such as border=0 in glTexImage2D) leaving you wondering why they're even there (compatibility with future versions of the API? No, they're never changed to accept any other values). Redundant enums accepted by functions that can only handle one type of value anyway, such GL_TEXTURE_3D for functions that only operate on 3D textures.
- Extensions required to do almost everything. Querying the GPU for supported extensions is a huge pain.
- Misleadingly similar function names for functions that actually do very different things, such as glGenTextures/glCreateTextures
- Obscure, arcane rules for framebuffer completeness (for example when you want to render to a texture). Without reading documentation in detail you will routinely run into situations where you're unable to figure out why your framebuffer is considered incomplete. This is made even worse by the previously mentioned fact that error handling is abysmal, and error codes are shared by different errors. Try reading the attachment completeness rules to get an idea just how bad it is. There is also no feedback as to which framebuffer attachment is the bad one.
- Fixed function pipeline can be mixed with the new pipeline with shaders, leading to a HUGE mess
- Absolutely no checking for any kinds of errors at compile time, although I appreciate that most of this would be hardware-dependent anyway
- Lots of redundant functions (just see how many variants of glUniform or glGet there are)
With all these, and many other flaws, OpenGL seems to be just fine. It's extremely popular, dominates on mobile devices, is in active development, and supported on all kinds of GPUs. Why? Why is all this tolerated, and most importantly why was it designed this way? I know that GPUs are very particular devices and communication between CPU, GPU, memory, etc requires certain constraints, but most of it seems to be due to bad design that could be avoided.