3

In the example page at https://www.lighthouse3d.com/tutorials/glsl-tutorial/uniform-blocks/ has this:

  1. uniformBlockBinding()
  2. bindBuffer()
  3. bufferData()
  4. bindBufferBase()

But conceptually, wouldn't this be more correct?

  1. bindBuffer()
  2. bufferData()
  3. uniformBlockBinding()
  4. bindBufferBase()

The idea being that uploading to a buffer (bindBuffer+bufferData) should be agnostic about what the buffer will be used for - and then, separately, uniformBlockBinding()+bindBufferBase() would be used to update those uniforms, per shader, when the relevant buffer has changed?

davidkomer
  • 3,020
  • 2
  • 23
  • 58
  • 2
    You should choose to tag either OpenGL OR WebGL. The answers will be different. OpenGL supports mapping buffers, WebGL does not (because it's not safe) so how you update uniform block buffers will likely be very different. Also OpenGL is a C api and WebGL is a JavaScript API so the means to update the data is vastly different. In OpenGL you can declare a struct that matches your uniform block data. In JavaScript you can't, you have to do it very differently. Also WebGL doesn't support setting binding indices in GLSL as your accepted answer suggests. – gman Jun 26 '19 at 05:24

2 Answers2

4

Adding answer since the accepted answer has lots of info irrelevant to WebGL2

At init time you call uniformBlockBinding. For the given program it sets up which uniform buffer index bind point that particular program will get a particular uniform buffer from.

At render time you call bindBufferRange or bindBufferBase to bind a specific buffer to a specific uniform buffer index bind point

If you also need to upload new data to that buffer you can then call bufferData

In pseudo code

// at init time

for each uniform block
   gl.uniformBlockBinding(program, indexOfBlock, indexOfBindPoint)

// at render time

for each uniform block
   gl.bindBufferRange(gl.UNIFORM_BUFFER, indexOfBindPoint, buffer, offset, size)
   if (need to update data in buffer)
      gl.bufferData/gl.bufferSubData(gl.UNIFORM_BUFFER, data, ...)

Note that there is no “correct” sequence. The issue here is that how you update your buffers is really up to you. Since you might store multiple uniform buffer datas in a single buffer at different offsets then calling gl.bufferData/gl.bufferSubData like above is really not “correct”, it’s just one way of 100s.

WebGL2 (GLES 3.0 ES) does not support the layout(binding = x) mentioned in the accepted answer. There is also no such thing as glGenBuffers in WebGL2

gman
  • 100,619
  • 31
  • 269
  • 393
  • Thanks - changed this to the accepted answer especially in light of the removed "opengl" tag – davidkomer Jun 27 '19 at 08:37
  • Followup question - but not sure if it should be its own SO post... can/should sampler locations also be bound at init time? e.g. gl.uniform1i(u_sampler, bindPoint); – davidkomer Jun 27 '19 at 17:56
  • 1
    Yea, that should be its own question but it's a matter of opinion. gl.uniform sets program state so if you want to decide up front which sampler units or texture units will get used for a particular program then sure, doing that up front would make sense. I don't use samplers. I've rarely had the case where I needed to use a texture with different sampler parameters. Maybe when I run into that case I'll start using them. I'm not saying they don't exist, just that at least for the projects I've been on it's been rare. – gman Jun 28 '19 at 00:08
2

Neither is "more correct" than the other; they all work. But if you're talking about separation of concerns, the first one better emphasizes correct separation.

glUniformBlockBinding modifies the program; it doesn't affect the nature of the buffer object or context buffer state. Indeed, by all rights, that call shouldn't even be in the same function; it's part of program object setup. In a modern GL tutorial, they would use layout(binding=X) to set the binding, so the function wouldn't even appear. For older code, it should be set to a known, constant value after creating the program and then left alone.

So calling the function between allocating storage for the buffer and binding it to an indexed bind point for use creates the impression that they should be calling glUniformBlockBinding every frame, which is the wrong impression.

And speaking of wrong impressions, glBindBufferBase shouldn't even be called there. The rest of that code is buffer setup code; it should only be done once, at the beginning of the application. glBindBufferBase should be called as part of the rendering process, not the setup process. In a good application, that call shouldn't be anywhere near the glGenBuffers call.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Thanks! And `bindBufferBase()` is only needed to be called if the buffer was updated, right? (and updating the buffer requires both `bindBuffer()+bufferData()`, but not genBuffer() since we just re-use one that was created ?) – davidkomer Jun 25 '19 at 22:38
  • Also - for the sake of convenience, is it so bad to query and get the index+bind point if that's just cached once at shader compilation, as opposed to hardcoded constants? (I get that it would be bad to lookup every time - but if just once...) – davidkomer Jun 25 '19 at 22:40
  • 1
    WebGL2 / OpenGL ES 3.0 / GLSL ES 3.0 doesn't have layout binding settings for uniform blocks in GLSL so you have to call `uniformBlockBinding`. – gman Jun 26 '19 at 05:19
  • 1
    As for setting them in GLSL or setting them using `glUniformBlockBinding` it's a matter of opinion. I'm someone who tries to keep things DRY (don't repeat yourself) so I'd choose either to generate my shaders in which case the code that decides the binding locations can be used when generating them, or I'd use some shared constants preprocessor file, or I'd choose to do it with `glUniformBlockBinding` since then in any case I could centrally manage locations by name and not have to manually re-write a bunch of shaders when I ran into a conflict. – gman Jun 26 '19 at 05:21