You can define youFirst of all, let me explain how local_size_x local_size_y local_size_z
and glDispatchCompute(x, y, z)
are related to each other:
The workgroup size as 8x8x1can be defined within the shader. Outside with the shader you can executefollowing command: layout (local_size_x = ..., local_size_y = ..., local_size_z = ...) in;
The workgroup size is defined by the glDispatchCompute(3,3,1)multiplication of all 3 local_sizes: workgroupSize = local_size_x * local_size_y * local_size_z
The glDispatchCompute(x, y, z)
command executes the defined workgroup x * y * z times.
NowFor example when saying: layout (local_size_x = 2, local_size_y = 3, local_size_z = 4) in;
with glDispatchCompute(5, 6, 7)
you will have 24x245 * 6 * 7 = 210 workgroups where workgroup has 2 * 3 * 4 = 24 invocations whicheach.
Within the compute shader you have the following built-in input variables: The following information is larger thancopied from khronos website
in uvec3 gl_NumWorkGroups; // This variable contains the number of work groups passed to the dispatch function.
in uvec3 gl_WorkGroupID; // This is the current work group for this shader invocation. Each of the XYZ components will be on the half-open range [0, gl_NumWorkGroups.XYZ).
in uvec3 gl_LocalInvocationID; // This is the current invocation of the shader within the work group. Each of the XYZ components will be on the half-open range [0, gl_WorkGroupSize.XYZ).
in uvec3 gl_GlobalInvocationID; // This value uniquely identifies this particular invocation of the compute shader among all invocations of this compute dispatch call. It's a short-hand for the math computation: gl_WorkGroupID * gl_WorkGroupSize + gl_LocalInvocationID;
in uint gl_LocalInvocationIndex; // This is a 1D version of gl_LocalInvocationID. It identifies this invocation's index within the work group. It is short-hand for this math computation:
// gl_LocalInvocationIndex =
// gl_LocalInvocationID.z * gl_WorkGroupSize.x * gl_WorkGroupSize.y +
// gl_LocalInvocationID.y * gl_WorkGroupSize.x +
// gl_LocalInvocationID.x;
const uvec3 gl_WorkGroupSize; // GLSL ≥ 4.30. The gl_WorkGroupSize variable is a constant that contains the local work-group size of the shader, in 3 dimensions. It is defined by the layout qualifiers local_size_x/y/z. This is a compile-time constant.
With these built-in variables you 17x17 imagecan figue out, which invocation is the current.
ThenEach workgroup shares memory where each invocation within that workgroup can access to. This memory can be defined in the following way:
shared vec3 variableName;
The variable can NOT be initialized by declaration! The shared memory is faster in access than the global memory (ShaderStorageBufferObjects [SSBO]). So after identifying which invocation belongs to which pixel within an image, each invocation can load the one pixel data from the image (global memory (slow)) and stores the values to shared memory, so that other invocations within the shader yousame workgroup can queryaccess them. This is faster than telling each invocation to load multiple pixels directly from the gl_GlobalInvocationID ifimage... Have in mind, this shared memory is very limited!
Lets go back to the workgroup size. The values of the example from above were choosen to let you understand how the workgroup size and the glDispatchCompute()
command are still insiderelated to each other. But this workgroup size is choosen badly! And following reason tells you why:
On GPUs the image rangework units (each executing one invocation) are grouped to so called warps
(Nvidia) and waves
(AMD). The vendor (Nvidia and AMD) have different sizes for these warps | waves
. Nvidia group 32 work units to one warp, where AMD group 64 work units to one wave. Your workgroup size should be a multiple of your vendors warp
(Nvidia), wave
(AMD) size. Because when for example defining the workgroup size to be 1, the other 31(Nvidia) 63(AMD) work units will be blocked...
Thats not all... The next importent thing which you need to know when defining the workgroup size is: Each work unit within a workgroup calculate the same arithmetic operation. So when you have a conditional state within your shader (if(...){...} else {...}) then it would be good, if soeach invocation within the workgroup goes to the same branch (all should go to the if
or all should go to the else
branch). Because otherwise, dowhen some invocations within the workgroup go to the if
branch, all other invocation which would go to the else
branch will wait until the if
branch has finished their work. Then the else
branch will start its work while the invocations from the if
branch will wait till it is finished. So your shader will be very slow.
As you can see, when defining the workgroup size several aspects have to be taken care of.