PDA

View Full Version : Is there some way to bind uniform with dynamic size to shader?



nimelord
12-05-2017, 11:31 AM
I have shader with constant size of uniform array looks like next:


#version 430 // maximum version that can be supported by my ATI card with open source mesa driver.

const int MAX_POINT_LIGHTS = 5;
...

uniform PointLight pointLights[MAX_POINT_LIGHTS];

struct Attenuation {
float constant;
float linear;
float exponent;
float distance;
};


struct PointLight {
vec3 colour;
vec3 position;
float intensity;
Attenuation att;
};

...



May I use in some way dynamic size of uniform array with my own structs?
I mean:
1 - On the CPU side I calculate "point light" number as three items.
2 - I bind them those three lights to the shader.
3 - On the shader side I have array with 3 items with known size.

That is so cool if it possible! :)

Tank you for answer!

GClements
12-05-2017, 02:25 PM
The size of a uniform array must be known when the shader is linked. You can declare the array with the maximum required size then use a uniform int variable to indicate the number of valid elements in the array.

An array which is the last element of a shader storage block can have a dynamic size, the size being determined by the size of the buffer associated with the block.

nimelord
12-06-2017, 12:25 AM
An array which is the last element of a shader storage block can have a dynamic size, the size being determined by the size of the buffer associated with the block.

Do you mean this one? https://www.khronos.org/opengl/wiki/Shader_Storage_Buffer_Object

If yes, how can I use my "struct PointLight" in the such way?
I have found only using of primitives in examples, I mean int, float, e.t.c.

john_connor
12-06-2017, 01:28 AM
layout (std430, binding = 1) buffer POINTLIGHTBLOCK {
PointLight mypointlights[]; /* unsized array */
};

/* create your pointlight buffer "mypointlightbuffer" here */
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, mypointlightbuffer);

and read about the memory layout (std430 in case of shader storage blocks)
https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Memory_layout

nimelord
12-06-2017, 02:16 PM
I did it using "Shader Storage Buffer".
It basically works except one thing that confused me.

In shader i have structure with substructure: Attenuation is part of PointLight. (see code with them in the topic body)

When I fill buffer in correct order: colour, position, intensity, att.constant, att.linear, att.exponent, att.distance, I get mess with data on the shader side.
But if I write Attenuation data before intensity, then all is ok.

Could you explain such behavior?

nimelord
12-07-2017, 12:15 PM
I caught the problem!
This was a packing of data in the buffer.

I use two vec3 objects, one float, and substructure with four floats in the order.
I read somewhere that for using vec3 it must be filled as vec4: vec3 + one empty float.
So, I did it in such way.
In fact I was wrong: after last vec3 and before other type empty float is redundant. (I think so. Anyway it works.)
See comment in the code sample where was is redundant empty float:

Thus I should use vec4 instead vec3.


My shader:


#version 430
...

struct Attenuation {
float constant;
float linear;
float exponent;
float distance;
};


struct PointLight {
vec3 colour;
vec3 position;
float intensity;
Attenuation att;
};

...

layout (shared, binding = 3) buffer NewPointLight {
PointLight pls[];
} newPointLights;
...




And binding:



private int pointLights;
...


pointLights = glGenBuffers();
glBindBuffer(GL_SHADER_STORAGE_BUFFER, pointLights);
ByteBuffer pointLightsData = BufferUtils.createByteBuffer(52); // in bytes

for (PointLight pointLight : scene.getSceneLight().getPointLightList()) {

pointLightsData.putFloat(pointLight.getColor().x);
pointLightsData.putFloat(pointLight.getColor().y);
pointLightsData.putFloat(pointLight.getColor().z);
pointLightsData.putFloat(0);

pointLightsData.putFloat(pointLight.getPosition(). x);
pointLightsData.putFloat(pointLight.getPosition(). y);
pointLightsData.putFloat(pointLight.getPosition(). z);
pointLightsData.putFloat(0); // this one is dedundant!

pointLightsData.putFloat(pointLight.getIntensity() );

pointLightsData.putFloat(pointLight.getAttenuation ().getConstant());
pointLightsData.putFloat(pointLight.getAttenuation ().getLinear());
pointLightsData.putFloat(pointLight.getAttenuation ().getExponent());
pointLightsData.putFloat(pointLight.getAttenuation ().getDistance());
}
pointLightsData.flip();

glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, pointLights);
glBufferData(GL_SHADER_STORAGE_BUFFER, pointLightsData, GL_STATIC_DRAW);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);



I have one more question: I must build and fill buffer per each rendered frame, or there is more correct way to use it?

Thanks for answer.

GClements
12-08-2017, 01:38 AM
This was a packing of data in the buffer.

I use two vec3 objects, one float, and substructure with four floats in the order.
I read somewhere that for using vec3 it must be filled as vec4: vec3 + one empty float.
So, I did it in such way.
In fact I was wrong: after last vec3 and before other type empty float is redundant. (I think so. Anyway it works.)
See comment in the code sample where was is redundant empty float:


A vec3 will be aligned to a 4N boundary (where N is the size of a float). So there's a single float of padding between colour and position to ensure that position has the correct alignment. The intensity field doesn't require aligning, nor does the Attenuation structure (as it's comprised entirely of floats).

More generally: vec3 and vec4 are aligned to 4N, vec2 to 2N, and a structure to the largest alignment of any of its members. The std140 layout additionally requires array elements and structures to be aligned to at least 4N.



I have one more question: I must build and fill buffer per each rendered frame, or there is more correct way to use it?

If the data is constant, you'd create and fill the buffer once, at start-up. If you need to change the data, you can either overwrite the portions which change or replace the entire buffer. Note that overwriting a portion of a buffer which is being used by pending commands will stall until those commands have completed.

nimelord
12-08-2017, 06:02 AM
A vec3 will be aligned to a 4N boundary (where N is the size of a float). So there's a single float of padding between colour and position to ensure that position has the correct alignment. The intensity field doesn't require aligning, nor does the Attenuation structure (as it's comprised entirely of floats).

More generally: vec3 and vec4 are aligned to 4N, vec2 to 2N, and a structure to the largest alignment of any of its members. The std140 layout additionally requires array elements and structures to be aligned to at least 4N.


hm.....

Why it don't need empty cell between position and intensity, if last vec3 (position) needs to be aligned to a 4N too, as you wrote?
If I write empty one after position it is not work.


In my case I keep lights on the buffer. These lights can be moved on the scene, so I need to change content in the buffer between renrering of new frame of scene.
I think I should bind already builded buffer, and just rewrite it, am I right?

GClements
12-08-2017, 10:30 AM
Why it don't need empty cell between position and intensity, if last vec3 (position) needs to be aligned to a 4N too, as you wrote?

position needs to start at a multiple of 4N from the beginning of the block, which is why there's padding before it. intensity doesn't have any alignment requirement, so there's no padding inserted between position and intensity.



In my case I keep lights on the buffer. These lights can be moved on the scene, so I need to change content in the buffer between renrering of new frame of scene.
I think I should bind already builded buffer, and just rewrite it, am I right?
I'd suggest replacing the entire buffer contents, with glBufferData() or glMapBufferRange(GL_MAP_INVALIDATE_BUFFER_BIT).

Dark Photon
12-08-2017, 07:08 PM
positionI'd suggest replacing the entire buffer contents, with glBufferData() or glMapBufferRange(GL_MAP_INVALIDATE_BUFFER_BIT).

I second that recommendation. I've fixed quite a few frame stalls where partial updates (via synchronized Map or Sub) triggering implicit synchronization inside the GL driver was the cause.

Also, if you sometimes test on NVidia drivers, glEnable( GL_DEBUG_OUTPUT ) and plug in a glDebugMessageCallback() (part of OpenGL 4.3; see KHR_debug (https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_debug.txt) for details), and the NVidia driver will tell you when your buffer update method is causing synchronization (potential slowdowns) inside the driver. Not sure if anyone else's GL driver does this though. As easy as it is to try, it's worth checking.

nimelord
12-10-2017, 08:58 AM
I implemented it.

I used full rewriting: glBufferData().
I think partial rewriting it is sort of optimization and I will do it only if that point become the greatest bottleneck.

Thank you guys a lot!

regards.