1

I am fairly new to OpenGL and trying to achieve instancing using uniform arrays. However the number of instances I am trying to invoke is larger than the MAX_UNIFORM_LOCATIONS limit:

QOpenGLShader::link: error: count of uniform locations > MAX_UNIFORM_LOCATIONS(262148 > 98304)error: Too many vertex shader default uniform block components
error: Too many vertex shader uniform components

What other ways are possible that will work with that large a number of objects? So far this is my shader code:

layout(location = 0) in vec4 vertex;
layout(location = 1) in vec3 normal;
out vec3 vert;
out vec3 vertNormal;
out vec3 color;
uniform mat4 projMatrix;
uniform mat4 camMatrix;
uniform mat4 worldMatrix;
uniform mat4 myMatrix;
uniform sampler2D sampler;
uniform vec3 positions[262144];
void main() {
   vec3 t = vec3(positions[gl_InstanceID].x, positions[gl_InstanceID].y, positions[gl_InstanceID].z);
   float val = 0;
   mat4 wm = myMatrix * mat4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, t.x, t.y, t.z, 1) * worldMatrix;
   color = vec3(0.4, 1.0, 0);
   vert = vec3(wm * vertex);
   vertNormal = mat3(transpose(inverse(wm))) * normal;
   gl_Position = projMatrix * camMatrix * wm * vertex;
}

If it should matter, I am using QOpenGLExtraFunctions.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Herr von Wurst
  • 2,571
  • 5
  • 32
  • 53
  • Why not to use vertex attributes for instancing? – Ramil Kudashev Sep 26 '16 at 16:08
  • A uniform array seemed the most straightforward way. So if I understand correctly I would use the same VBO and append my position attribute to the end of the buffer, right? – Herr von Wurst Sep 26 '16 at 17:30
  • 2
    You can use **any** buffer. The key point is that you will specify an attribute out of that buffer, and you will tell to the GL (via `glVertexAttribDivisor`) that that particular attribute should not be advanced once per vertex, but once every *divisor* instances. Simplest case, *divisor* being 1: the attribute gets advanced at every instance; all vertices drawn in a given each instance get the same value of that attribute. – peppe Sep 26 '16 at 18:39
  • A few years later, which buffer did you end up using? – javaLover Sep 08 '19 at 04:25

1 Answers1

3

There are many alternatives for overcoming the limitations of uniform storage:

UBOs, for example; they usually have a larger storage capacity than non-block uniforms. Now in your case, that probably won't work, since storing 200,000 vec3svec4s will require more storage than most implementations allow UBOs to provide. What you need is unbounded storage.

Instanced Arrays

Instanced arrays use the instanced rendering mechanism to automatically fetch vertex attributes based on the instance index. This requires that your VAO setup work change a bit.

Your shader would look like this:

layout(location = 0) in vec4 vertex;
layout(location = 1) in vec3 normal;
layout(location = 2) in vec3 position;
out vec3 vert;
out vec3 vertNormal;
out vec3 color;
uniform mat4 projMatrix;
uniform mat4 camMatrix;
uniform mat4 worldMatrix;
uniform mat4 myMatrix;
uniform sampler2D sampler;

void main() {
  vec3 t = position;
  /*etc*/
}

Here, the shader itself never uses gl_InstanceID. That happens automatically based on your VAO.

That setup code would have to include the following:

glBindBuffer(GL_ARRAY_BUFFER, buffer_containing_instance_data);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), 0);
glVertexAttribDivisor(2, 1);

This code assumes that the instance data is at the start of the buffer and is 3 floats-per-value (tightly packed). Since you're using vertex attributes, you can use the usual vertex attribute compression techniques on them.

The last call, to glVertexAttribDivisor is what tells OpenGL that it will only move to the next value in the array once per instance, rather than based on the vertex's index.

Note that by using instanced arrays, you also gain the ability to use the baseInstance glDraw* calls. The baseInstance in OpenGL is only respected by instanced arrays; gl_InstanceID never is affected by it.

Buffer Textures

Buffer textures are linear, one-dimensional textures that get their data from a buffer object's storage.

Your shader logic would look like this:

layout(location = 0) in vec4 vertex;
layout(location = 1) in vec3 normal;
out vec3 vert;
out vec3 vertNormal;
out vec3 color;
uniform mat4 projMatrix;
uniform mat4 camMatrix;
uniform mat4 worldMatrix;
uniform mat4 myMatrix;
uniform sampler2D sampler;
uniform samplerBuffer positions;

void main() {
  vec3 t = texelFetch(positions, gl_InstanceID).xyz;
  /*etc*/
}

Buffer textures can only be accessed via the direct texel fetching functions like texelFetch.

Buffer textures in GL 4.x can use a few 3-channel formats, but earlier GL versions don't give you that option (not without an extension). So you may want to expand your data to a 4-channel value rather than 3 channel.

Another problem is that buffer textures do have a maximum size limitation. And the required minimum is only 64KB of size, so the instanced array method will probably be more reliable (since it has no size restriction). However, all non-Intel OpenGL implementations give a huge size for buffer textures.

SSBOs

Shader storage buffer objects are like UBOs, only you can both read and write to them. That latter tool isn't important for you. The main advantage here is that the minimum required OpenGL size for them is have a minimum required size of 16MB (and implementations generally return a size limit on the order of available video memory). So size limits aren't a problem.

Your shader code would look like this:

layout(location = 0) in vec4 vertex;
layout(location = 1) in vec3 normal;
out vec3 vert;
out vec3 vertNormal;
out vec3 color;
uniform mat4 projMatrix;
uniform mat4 camMatrix;
uniform mat4 worldMatrix;
uniform mat4 myMatrix;
uniform sampler2D sampler;

buffer PositionSSBO
{
  vec4 positions[];
};

void main() {
  vec3 t = positions[gl_InstanceID].xyz;
  /*etc*/
}

Note that we explicitly use a vec4 here. That's because you should never use vec3 in a buffer-backed interface block (ie: UBO/SSBO).

In code, SSBOs work much like UBOs. You bind them for use with glBindBufferRange.

Community
  • 1
  • 1
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982