Instancing sucks ?

Edit: since I wasn’t sure about that 8192 logic, I rechecked docs and tested the GLSL limits. The 8192 on G80 isn’t 8kB, but 8192 32-bit registers (32kB). On GTX2xx, it’s 16k registers, 64kB.

GL_MAX_VERTEX_UNIFORM_COMPONENTS = 4096 // 16kB
GL_MAX_FRAGMENT_UNIFORM_COMPONENTS = 2048 // 8kB
So, the driver reserves at least 40kB on the GTX for the thread-data of warps.

Furthermore, I checked if I raise the limit of instances to use the whole 16kB, and there was no performance penalty. (raising it further makes the program fail to link).

Thus, maybe simply we need to use-up the GL_MAX_VERTEX_UNIFORM_COMPONENTS instead of tuning.

4096/ 12 = 341 instances max, if only mat4x3 per instance.
4096/4 = 1024 instances if you use only “vec3 pos; float rotateY;”
4096/3 = 1365 instances if you use only “vec3 pos”;
4096/1 = 4096 instances if you use “int StaticID;” in combination with truly-constant UBOs, which can contain nice 800kB constant data with slightly slower access.

:slight_smile:

Not sure about this statement. Whether instancing or not, the system still has to draw 4000+ instances - the only difference is whether the CPU is locked into a loop whist doing so. There is no extra work for the rendering loop to do - 12 Million triangles is 12 million triangles whenther rendered one at a time in CPU loop, or as an instanced batch.

Normally, your shader would simply fetch its state data from a uniform. If you’re using instancing, you have to get the gl_InstanceID uniform and use that to index either another uniform (either a direct array or a UBO) or access a texture in order to get its state data. Depending on the performance of UBOs or texture accesses, and the number of vertices being rendered, this could be a greater performance loss than just doing it manually.

How does instancing improve the situation if the app is cpu state change limited? Maybe I’ve missed something here, but I thought the point of instancing was to avoid state changes by rendering the same object over and over again. This does not usually involve any state changes by definition.

If you draw the same thing with the exact same state, nothing will change. You will get the exact same vertices input and output from your shader, and the exact same fragment data written every time. In order for instancing to work, you must have some mechanism in your shader to know what instance you are. At which point, you can then decide where to render the object based on that.

The field of trees is an obvious example. Each tree has a position and orientation; that is its state. In order to render this normally, you will have to perform at least one glUniform call between each glDraw call. If you use instancing, you build a list of state data, put it where the shader can get it, and call glDrawInstanced once.

This can only help if your application’s performance is limited by state changes and draw calls. If the app is limited by something else, instancing buys you nothing.

To your question Bionic Bytes, I think the underlying limit here is the amount of shared memory on a GPU SM (i.e. streaming multiprocessor, in NVidia lingo, which is a cluster of 32 shader cores). This SM shared memory is likely being used to store uniform values for shading threads (makes sense). This memory is “super fast” relative to texture because it’s local to the cores.

On G80/GT200, that’s 16kB per SM. On the new GF100/Fermi (GTX480), it’s 64kB.

Ilian’s insight/discovery here is excellent, and when you stop and think about it it clicks and just makes good sense (assuming that uploading uniforms to shared memory is fast)!

And thanks for passing on your thoughts and experience, Ilian. Very interesting stuff.

Except there should never be a need to use less efficient means with instancing.

Ilian’s insight/discovery here is excellent, and when you stop and think about it it clicks and just makes good sense (assuming that uploading uniforms to shared memory is fast)!

Actually, looking over the original post, I realized something. He never actually got the UBO-based instancing working. Something about not being able to track the uniform offsets. I’d be curious to see what would happen if use got UBOs working.

Except there should never be a need to use less efficient means with instancing.

Instancing requires that, on some level, the shader will fetch information of some kind based on what instance it is. Instance arrays does this via one or more vertex attributes. Or you can index an array/texture based on gl_InstanceID. Both of these are slower than reading from a single, scalar uniform; even accessing a uniform array requires a bit of indirection, so it is (slightly) slower than just reading from an array.

Thus, you have to make sure that you’re rendering enough instances so that the losses due to this inefficiency can be made up by gains due to less state change overhead.

No, I stated that I found them slower to update and use, in the first beta drivers they were exposed in. And months ago I had stated that nVidia’s compilers at that time were calculating and using offsets in an inefficient way. (I was using the nv-asm generated from cgc back then, the asm was obviously inefficient).

I’m working on adding all methods to the engine - so I can finally answer the question - which is faster (for rendering n instances of models of ~10,000 tris).

Currently I have added Texture Buffer Objects, Instanced arrays, Uniform arrays and, still in progress,Uniform Buffer Objects (they are added - just need to do it properly).

I’ll post a table of the results on my laptop Gefore 8600GT and Radeon 4850 desktop. Should be interesting reading!..

One of the annoyances is that the nVidia (laptop driver) is not supporting IA, and the Radeon is not running TBO very well(it just locks up when the objects are rendered). Making a direct comparioson is never easy!

I found the doubling of matrix mults per vertex were a big cost with instancing. The state fetches didn’t seem to be as significant with any of the methods. So my conclusion was to only instance meshes with low vertex counts. Of course, this is with basic shading - obviously with more complicated shaders it becomes less of a factor.

HELP!
Anyone know how to use these commands…I’m having issues with UBO - it’s drawing spikey hedgehog soup at the moment.

glBindBufferBase(GL_UNIFORM_BUFFER,0, UBO);
glUniformBlockBinding(glslProgram, blockindex, 0);

Ideally they should be issued after compiling the shader(s) and issued when creating the Uniform Buffer Object storage. However, my engine is a little more complex…I have multiple shaders and the instance data for the scene objects is read quite independantly from shaders - hence there can never be any chance of knowing what the glslProgram_ID is at the time I issue the glGenBuffer command.

Therefore, during the drawmodels function, I issue those two commands just before I enable the shader and upload uniform values. IS this the correct method?

Ideally they should be issued after compiling the shader(s) and issued when creating the Uniform Buffer Object storage.

Um, no; that’s not how it works at all.

These commands should be used when, and only when, you are intending to render something with the UBO right then. There is no other purpose in issuing these commands.

So your rendering would look like:

1: Bind program.
2: Use glUniform to set uniforms on that program, as needed.
3: Use glBindTexture/glActiveTexture to bind textures, as needed.
4: Use glBindBufferRange/Base and glUniformBlockBinding to bind UBOs, as needed.
5: Bind “VAO” (vertex array state), if needed.
6: Call glDraw*.

To create the storage for a buffer object, use the standard syntax. To upload data to it, again use the same commands you’ve always used to upload data (glBufferSubData/glMapBufferRange).

Just because you use a buffer object for uniforms doesn’t make it special. All buffer objects are interchangeable. Though you should use the GL_UNIFORM_BUFFER target when you bind the object and call glBufferData on it.

OK, thanks for the clarification.

Just to be more clear: I don’t need to call glBindBuffer (GL_UNIFORM_BUFFER, UBO) during rendering (assuming no updates to buffer object) - I just ‘bind’ the UBO to the active shader via:

glBindBufferRange/Base and
glUniformBlockBinding

This is what I’m actually doing - but I have spikey soup hell at the moment (may be something wrong with the contents of the buffer objects/wrong UBO id) or wrong commands?

One more question - I can’t seem to define unsized uniform arrays for the uniform block.
Now that the uniform is backed by a buffer object, I had expected that unsized arrays would be allowed and that they would not take up more ‘slots’ in the vertex shader.
What seems to happen is the compiler compains and wants the array to be sized and accessed with a const integer, and the size of the array is limited to the MAX_VERTEX_UNIFORM_COMPONENTS size - just like regular unifrom arrays. Is this right?

One more question - I can’t seem to define unsized uniform arrays for the uniform block.

I wasn’t aware that you could define unsized uniform arrays for anything, whether in a uniform block or not. It’s simply not allowed.

What seems to happen is the compiler compains and wants the array to be sized and accessed with a const integer, and the size of the array is limited to the MAX_VERTEX_UNIFORM_COMPONENTS size - just like regular unifrom arrays. Is this right?

All arrays must be of a fixed, compile-time size.

Every element of a uniform block contributes to that block’s overall size. The number of components in a uniform block may not exceed MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS. This is usually the same size or larger than MAX_VERTEX_UNIFORM_COMPONENTS. For example, ATI’s pre-5xxx line advertises 1024 vertex components, but 4096 combined vertex components.

Upon reading the GL spec on Uniform Buffers again in more detail, it seems there are two implementaton limits we need to be aware of - both of which affect how many instances can be catered for in the solution.

MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS (max floats in all uniform blocks) = MAX_VERTEX_UNIFORM_BLOCKS * (MAX_UNIFORM_BLOCK_SIZE/4) + MAX_VERTEX_UNIFORM_BLOCKS

on, on nVidia G8600GT we have,

200704 = 12 * (65536/4) + 4096; where MAX_UNIFORM_BLOCK_SIZE is the max size in bytes of the Buffer Object store.
This equates to 16,384 components per uniform block (or put another way, 16384 floats @ 4 bytes = 65536 bytes)

Therefore a solution looking to utilise UBO for instancing needs to factor in these limits with regard to the unmber of per-instance attributes to pack into the Buffer Object (64Kb limit) and the number of array element ‘slots’ in the shader uniform block (16384).

It does say in the spec, that the MAX_VERTEX_UNIFORM_COMPONENTS only applies to the default uniform block, therefore by definition, we should be able to use upto a maximum of 16,384 uniform buffer array elements in a UBO block for instancing (nVidia example). For my engine, I use a 16-float modelmatrix per instance - so this allows for 1024 instances.

I have found the cause of my UBO buffer overrun problem, so this is what I’ll be trying tomorrow…and hopefully post some results on nVidia and ATI hardware.

After much testing and stress! Here are some results of implementing instancing with my
Deferred Rendering engine.

Unfortunately, the ATI radeon 4850 Opengl 4.0/3.3 beta drivers are exhibiting strange behaviour
(Texture Buffer Objects - hang application, Uniform Buffer Objects - The shader compiler generates an ‘unknown link error’ when
the Buffer object size is at it’s maximum (GL_UNIFORM_BLOCK_SIZE)) and Secondly, GL is unable to ‘find’ the uniform blocks.

Geforce 8600GT (laptop)
2410 visible instances (from 4096 in total). Each instance ~3000 triangles

FPS	drawtime

TBO 2 300
UBO 2 2400
UA 2 3260
IA - -
none 2 6000

371 visible instances (from 400 in total). Each instance ~3000 triangles

FPS	drawtime

TBO 8 55
UBO 8 290
UA 9 400
IA - -
none 9 900

Key

None = no instancing technique - draw each object one at a time
IA = ARB Instanced Arrays
UA = Uniform Arrays - ie using glUniform*v
UBO = Uniform buffer Object
TBO = Texture Buffer Object

Summary:
Inconclusive due to lack of numbers from the IA technique and the laptop is not fast enough to use high instance numbers.
very disapointed not to get the Radeon to work - as this machine has the horsepower to really instancing.

Inconclusive due to lack of numbers from the IA technique and the laptop is not fast enough to use high instance numbers.

Remember: instancing is intended to deal with CPU-based performance issues. If you’re not CPU performance bound, then you’re not going to get anything from it.