Well, I don't agree that getting uniforms' (or attributes') location is cumbersome, especially because that should be done only once, during the initialization phase.
I'm using class-wrapper for each shaders. Uniforms and attributes locations are attributes of the instance, and they are set in the Init() function (at the same place where I load and compile shaders, and link the program). After that, I just call appropriate function (method) of the particular object to set values. It cannot be easier.
It can be a lot easier -- this is one of the long-standing design faults in GL, which has only recently been addressed with UBOs and explicit uniform locations.
On SM2 and early SM3 GPUs, uniform variables (
i.e. constants that can be modified by the host API) didn't actually exist in hardware; uniforms were implemented by using hard-coded literal values in the shader code, and having the host API
patch/recompile the shader code with new literal values when necessary.
Given this hardware design, the "old" GL way of managing uniforms is a perfect fit -- each "instance" of a shader can have it's uinforms set to some values, and those values are a property of the "instance". Two instances can't share values, and whenever switching instances they each retain their own values that were set on them.
Late SM3 GPUs replaced this design by actually adding hardware support for uniform variables. Instead of hard-coded literals, uniforms are now offsets into a shared "uniform" register bank. Now when the API sets a shader program, it's uniforms will contain the values in the register bank, so the API must also issue many command packets instructing the GPU to first set those registers to the values that 'belong' to that program instance.
Now the API no longer matches the hardware -- a more accurate API would instead expose the idea of there being a register bank in which values can be stored, and of shader programs that do not store uniform values as properties.
Under this hardware design, if two shader programs use the same uniform layout, and require the same values, then no work needs to be done when switching programs -- however, because GL's API is mismatched, the driver is forced to do a lot of useless work (
setting redundant values, or caching/checking all values to avoid this) and the user is also forced to do a lot of useless work (
any values to be shared between two different 'shader instances' have to be set on both instances).
Also, if the user is able to specify the register layout, then they can greatly reduce the number of API calls by setting arrays of contiguous registers instead of each uniform individually.
If the user is given the ability to explicitly state uniform locations, and the ability to write values to the shared uniform registers (
instead of writing values to shader instance) then a smart rendering engine can
greatly reduce the amount of time spent in the GPU API/driver.
However, SM4/SM5 GPUs also gave us the alternative of UBO's, which give you the same idea of having "shaders without values" and a "uniform register bank", except each UBO instance is it's own "register bank" that can be used by any shaders of your choosing -- so the UBO API is superior to both the "old" method, and GL4.3's "
Explicit uniform location" API (
which was the model used by D3D9), and I'd recommend you use UBO's, if targeting GL3.1 and up.
On a side note -- on all other APIs, I can write tools to pre-compile both my shader code and my UBO values, so that all the shaders and uniform values for a specific scene full of objects/materials can be saved in a file, and loaded straight into memory with no parsing/compiling/processing steps, which makes the runtime code about as easy as it gets.