PDA

View Full Version : Speed of glGetUniformLocation



chbaker0
12-05-2013, 07:10 PM
Is glGetUniformLocation a slow operation? I ask because I'm trying to wrap the GL functions and my variables in my program, and want to access it from another function by simply calling a function and passing the name of the uniform in a string and the value to set it to. However, I am wondering which will be faster: to directly call GetUniformLocation each time (possibly repeatedly for each uniform) or to use a std::map to map between the names and the GLints. Or alternatively I could #define names for each uniform and map it that way, but I'd rather try not to do that because that will make it really static.

Osbios
12-06-2013, 02:03 AM
You can not really map glGetUniformLocation to a string map because the function does a bit more. For example it will resolve array locations like ARRAYNAME[5]. It also resolves variables from uniformblocks and structs.
For easy of development I just use a simple function that calls glGetUniformLocation every time like setUniform(std::string name, TYPE value);
And as soon as you really care about performance just use some GLuint variables and initiate them once with glGetUniformLocation after loading the shader.

tksuoran
12-06-2013, 02:11 AM
You cannot use defines or constants because values of uniform locations are not constants. Each driver counts uniforms differently.

Fastest is to call GetUniformLocation once, and store the value somewhere. One of the simplest and fastest storages is simply a struct, that is what I prefer. A string map is more generic, with a tiny (hard to measure) extra cost.

chbaker0
12-06-2013, 08:42 AM
Osbios:
That is what I was already doing. I wanted to wrap my uniform accesses, since I already wrapped my program objects.

tksuoran:
That is what I meant: calling it once and storing it in a map vs calling it every access. But I decided to use an enum to index a map

Thank you.

mhagain
12-06-2013, 09:06 AM
The other option is GL_ARB_explicit_uniform_location (http://www.opengl.org/registry/specs/ARB/explicit_uniform_location.txt).

Aleksandar
12-06-2013, 01:13 PM
Is glGetUniformLocation a slow operation? I ask because I'm trying to wrap the GL functions and my variables in my program, and want to access it from another function by simply calling a function and passing the name of the uniform in a string and the value to set it to. However, I am wondering which will be faster: to directly call GetUniformLocation each time (possibly repeatedly for each uniform) or to use a std::map to map between the names and the GLints. Or alternatively I could #define names for each uniform and map it that way, but I'd rather try not to do that because that will make it really static.
I really don't understand using of hash tables or any other fast search structure to get uniform IDs. Is it much easier and more intuitive to have a variable (or the object's attribute) of type integer that will receive uniform's ID (at some initialization code) and be used in all subsequent accesses. So, call glGetUniformLocation() just once, and glUniform*() for all subsequent calls. The name can be the same as uniform name. Do you need anything better? :) Considering performance, drivers are usually optimized to eliminate superfluous glUniform*() calls (at least for NV). Btw, explicit uniform location is a pretty new feature (not supported in drivers older than a year or so).

chbaker0
12-06-2013, 03:58 PM
I wanted that because I was trying to wrap all my objects in classes so I could set a uniform like this:


Program.SetUniform("name", value);

or some similar way. And I got it to work with enums instead of strings, but I changed my mind about doing this anyway. I decided to just keep all my uniform GLints global and initialize them all at once. I was really overthinking how to simplify my program, to the point where I was mak
Thanks :)

Aleksandar
12-06-2013, 04:12 PM
I decided to just keep all my uniform GLints global and initialize them all at once. I was really overthinking how to simplify my program, to the point where I was mak
That's a very bad design! It drives me crazy when my students use globals instead of attributes for everything. :dejection:
You are using object-oriented programming language, right?
Then make classes for all your shader-programs. Uniforms should be attributes of those classes.

chbaker0
12-06-2013, 11:03 PM
That was what I was originally doing, and that brings me full circle: In the beginning I was trying to find a way to make a generic program class and be able to access uniforms through it, without having to change the class's code every time I change a uniform in my shader code. Object-oriented programming can be great, but it can certainly add pains as well...there's more recoding involved if I am dealing with a class.
But now that I am thinking more clearly, perhaps I could have a "RegisterUniform" method in a program class that would take an input of a string and would output an index value that I could then use with a "SetUniform" method taking the index and what I want to set it to. However, how could I maintain type safety for a method like that, where the value could be a float, vector, matrix, or anything?

Or maybe I'm just way overthinking the implementation. I guess I could simply have a method in the program class that would simply give me the GLint uniform location for a string passed to it. But doesn't that kinda defeat the purpose of object-oriented-ness?

Cornix
12-07-2013, 04:32 AM
Here is an example of how I implemented Shaders and Uniforms object-oriented in my Java engine:



public class Uniform {

private final ShaderProgram owner;
private final String name;
private final UniformType type;
private final int size;
private final int location;

public Uniform(ShaderProgram owner, String name, UniformType type, int size, int location) {
this.owner = owner;
this.name = name;
this.type = type;
this.size = size;
this.location = location;
}

public ShaderProgram getProgram() {
return owner;
}

public String getName() {
return name;
}

public UniformType getType() {
return type;
}

public int getSize() {
return size;
}

public int getLocation() {
return location;
}

public static class NoUniform extends Uniform {
public NoUniform(ShaderProgram owner, String name, UniformType type,
int size, int location) {
super(owner, name, type, size, location);
}
}

public static class Float extends Uniform {

public Float(ShaderProgram owner, String name, UniformType type,
int size, int location) {
super(owner, name, type, size, location);
}

public void set(float value) {
if (!getProgram().isInUse()) {
throw new IllegalStateException("Program must be in use.");
}
glUniform1f(getLocation(), value);
}

}

public static class FloatVec2 extends Uniform {

public FloatVec2(ShaderProgram owner, String name, UniformType type,
int size, int location) {
super(owner, name, type, size, location);
}

public void set(float x, float y) {
if (!getProgram().isInUse()) {
throw new IllegalStateException("Program must be in use.");
}
glUniform2f(getLocation(), x, y);
}

}

public static class FloatVec3 extends Uniform {

public FloatVec3(ShaderProgram owner, String name, UniformType type,
int size, int location) {
super(owner, name, type, size, location);
}

public void set(float x, float y, float z) {
if (!getProgram().isInUse()) {
throw new IllegalStateException("Program must be in use.");
}
glUniform3f(getLocation(), x, y, z);
}

}

public static class FloatVec4 extends Uniform {

public FloatVec4(ShaderProgram owner, String name, UniformType type,
int size, int location) {
super(owner, name, type, size, location);
}

public void set(float x, float y, float z, float w) {
if (!getProgram().isInUse()) {
throw new IllegalStateException("Program must be in use.");
}
glUniform4f(getLocation(), x, y, z, w);
}

}

/*
* And many more...
*/

}



public class ShaderProgram {

private static ShaderProgram inUse;

private final int glName;
private final Uniform[] uniforms;

public ShaderProgram() {
glName = glCreateProgram();
/*
* Initialization code: Compile, link, validate shaders...
*/
int uniformCount = glGetProgrami(getGlName(), GL_ACTIVE_UNIFORMS);
int uniformLength = glGetProgrami(getGlName(), GL_ACTIVE_UNIFORM_MAX_LENGTH);
uniforms = new Uniform[uniformCount];
int index = 0;
for (int i = 0; i < uniformCount; i++) {
String name = glGetActiveUniform(getGlName(), i, uniformLength);
int type = glGetActiveUniformType(getGlName(), i);
int size = glGetActiveUniformSize(getGlName(), i);
int location = glGetUniformLocation(getGlName(), name);

Uniform uniform = makeUniform(name, type, size, location);
uniforms[index++] = uniform;
}
}

public void use() {
inUse = this;
glUseProgram(getGlName());
}

public boolean isInUse() {
return inUse == this;
}

public void delete() {
if (isInUse()) {
inUse = null;
glUseProgram(0);
}
glDeleteProgram(getGlName());
}

public int getGlName() {
return glName;
}

public Uniform getUniform(String name) {
for (int i = 0; i < uniforms.length; i++) {
if (uniforms[i].getName().equals(name)) {
return uniforms[i];
}
}
return null;
}

private Uniform makeUniform(String name, int type, int size, int location) {
UniformType enumType = UniformType.getByGlEnum(type);
Uniform uniform;
switch (enumType) {
case FLOAT:
uniform = new Uniform.Float(this, name, enumType, size, location);
break;
case FLOAT_VEC2:
uniform = new Uniform.FloatVec2(this, name, enumType, size, location);
break;
case FLOAT_VEC3:
uniform = new Uniform.FloatVec3(this, name, enumType, size, location);
break;
case FLOAT_VEC4:
uniform = new Uniform.FloatVec4(this, name, enumType, size, location);
break;
/*
* And many more...
*/
default:
uniform = new Uniform.NoUniform(this, name, enumType, size, location);
break;
}
return uniform;
}

}




public class SomeCustomShader extends ShaderProgram {

private final Uniform.Float uniformBias;
private final Uniform.FloatVec2 uniformOffset;

private float bias;
private float offsetX;
private float offsetY;

public SomeCustomShader() {
super();

uniformBias = (Uniform.Float) getUniform("bias");
uniformOffset = (Uniform.FloatVec2) getUniform("offset");
}

public void setBias(float value) {
bias = value;
uniformBias.set(value);
}

public float getBias() {
return bias;
}

public void setOffset(float x, float y) {
offsetX = x;
offsetY = y;
uniformOffset.set(x, y);
}

public float getOffsetX() {
return offsetX;
}

public float getOffsetY() {
return offsetY;
}

}



As you can see, if you do it this way you get Type-Safety, good performance, and good design.
I am very satisfied with my solution and it has always worked out very well for me.

chbaker0
12-07-2013, 04:23 PM
OK, that looks cool. Thanks :) I'll probably keep the program GLuint outside of the uniform class, however it is still pretty much the same idea.

chbaker0
12-08-2013, 05:03 PM
If anyone is interested, here was my solution for the uniforms:


template <class T>
class _UniformBase
{
protected:
GLint Location;
T Data;

virtual void SetUniform() {/*Dummy*/}; // Must be implemented in child classes

public:
_UniformBase(): Location(-1) {}
virtual ~_UniformBase() {}

int Register(_Program Program, const char *Name_in)
{
Name = Name_in;
if((Location = glGetUniformLocation(Program.GetHandle(), Name_in)) == -1) return 1;
return 0;
}
T GetData()
{
return Data;
}
GLint GetLocation()
{
return Location;
}

void SetData(T Data_in)
{
Data = Data_in;
SetUniform();
}
};

template <class T>
class _Uniform : public _UniformBase<T>
{
// Just a blank class for the template...the specializations extend the needed _UniformBase::SetUniform() method
};

template <>
class _Uniform<glm::mat4> : public _UniformBase<glm::mat4>
{
protected:
void SetUniform()
{
glUniformMatrix4fv(Location, 1, GL_FALSE, glm::value_ptr(Data));
}
};

template <>
class _Uniform<glm::vec3> : public _UniformBase<glm::vec3>
{
protected:
void SetUniform()
{
glUniform3fv(Location, 1, glm::value_ptr(Data));
}
};

I set it up so I have a single base class "_UniformBase" that implements everything but the code that actually sends the data to the driver. Then, I made a blank templated dummy class just to allow me to write the specialized classes for each type (in my case, so far, it only implements it for glm::vec3 and glm::mat4). I did it like this because, while all the other code could be written with the template, the GL calls could NOT be templated (i.e. Uniform3f vs Uniform4f vs UniformMatrix4f, etc...) and I didn't want to use a big switch as having all those conditional branches could seriously affect performance. In the end, it let me access the uniforms just like this:



_Uniform<glm::mat4> Perspective;
Perspective.Register(Program, "perspective");
Perspective.SetData(glm::perspective(75.0f, 1.0f, 0.5f, 5.0f));

and similar for all the other ones.

tonyo_au
12-08-2013, 07:15 PM
You can improve this a bit by testing if the value is the same as the one already loaded and skip the SetUniform call.

chbaker0
12-08-2013, 08:04 PM
Good point, will do :D