<p>Short answer: Yes I have to check the coordinates.</p>
<p>If I set a 16x16x1 local size then use it to process a 17x17 image I have to use glDispatchCompute(2,2,1).</p>
<p>This generates four workgoups with size 16x16 even though the blocks to be processed have sizes 16x16, 16x1, 1x16 and 1x1</p>
<p>Inside the shader I have to check the range of the xy coordinates to make sure I don't do an imageStore() outside the image.</p>

CC BY-SA 4.0
2023-05-05T12:56:53.243Z
in case they do not communicate with each other, a smaller workgroup size could be better: For example when setting the workgroup size to 8x8x1 and execute glDispatchCompute(3,3,1) you generate 9 workgroups with 64 invocations each. where the used invocations are: 8x8, 8x8, 8x1, 8x8, 8x8, 8x1, 1x8, 1x8, 1x1.  total invocations: 576, used invocations: 289. And the 16x16x1 solution: total invocations: 1024. Used invocations: 289
Thomas
CC BY-SA 4.0
2023-05-05T13:03:23.777Z
Or the best option for a 17x17 image: use a workgroup size of 17x17... generated invocations: 289 blocked work units: 31 (Nvidia) 31(AMD). You only should stick to a workgroup size of 32/64 in case of using multiple workgroups... in this case we only need 1 workgroup.
Thomas
CC BY-SA 4.0
2023-05-06T14:06:52.833Z
Sure, but the question isn't about work optimal group sizes. I wasn't sure if the driver would call the shader outside the area of the image or not. In hindsight it makes sense that the driver shouldn't assume that I want exactly one shader invocation per pixel in the output image. The available documentation is a bit opaque though.
Chifti Saidi
CC BY-SA 4.0
2023-05-05T09:54:17.233Z
2023-05-05T09:54:17.233Z
Chifti Saidi
<p>First of all, let me explain how <code>local_size_x local_size_y local_size_z</code> and <code>glDispatchCompute(x, y, z)</code> are related to each other:</p>
<p>The workgroup size can be defined within the shader with the following command: <code>layout (local_size_x = ..., local_size_y = ..., local_size_z = ...) in;</code>
The workgroup size is defined by the multiplication of all 3 local_sizes: <code>workgroupSize = local_size_x * local_size_y * local_size_z</code></p>
<p>The <code>glDispatchCompute(x, y, z)</code> command executes the defined workgroup x * y * z times.</p>
<p>For example when saying: <code>layout (local_size_x = 2, local_size_y = 3, local_size_z = 4) in;</code> with <code>glDispatchCompute(5, 6, 7)</code> you will have 5 * 6 * 7 = 210 workgroups where workgroup has 2 * 3 * 4 = 24 invocations each.</p>
<p>Within the compute shader you have the following built-in input variables:
The following information is copied from <a href="https://www.khronos.org/opengl/wiki/Compute_Shader" rel="nofollow noreferrer">khronos website</a></p>
<pre><code>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.
</code></pre>
<p>With these built-in variables you can figue out, which invocation is the current.</p>
<p>Each workgroup shares memory where each invocation within that workgroup can access to. This memory can be defined in the following way:</p>
<pre><code>shared vec3 variableName;
</code></pre>
<p>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 same workgroup can access them. This is faster than telling each invocation to load multiple pixels directly from the image... Have in mind, this shared memory is very limited!</p>
<p>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 <code>glDispatchCompute()</code>command are related to each other. But this workgroup size is choosen badly! And following reason tells you why:
On GPUs the work units (each executing one invocation) are grouped to so called <code>warps</code> (Nvidia) and <code>waves</code> (AMD). The vendor (Nvidia and AMD) have different sizes for these <code>warps | waves</code>. 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 <code>warp</code>(Nvidia), <code>wave</code>(AMD) size. Because when for example defining the workgroup size to be 1, the other 31(Nvidia) 63(AMD) work units will be blocked...</p>
<p>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 each invocation within the workgroup goes to the same branch (all should go to the <code>if</code> or all should go to the <code>else</code> branch). Because otherwise, when some invocations within the workgroup go to the <code>if</code> branch, all other invocation which would go to the <code>else</code> branch will wait until the <code>if</code> branch has finished their work. Then the <code>else</code> branch will start its work while the invocations from the <code>if</code> branch will wait till it is finished. So your shader will be very slow.</p>
<p>As you can see, when defining the workgroup size several aspects have to be taken care of.</p>

CC BY-SA 4.0
2023-05-03T20:00:10.777Z
So I have to do "if ((gl_GlobalInvocationID.x < 17) && (gl_GlobalInvocationID.y < 17))" in my shader?
Chifti Saidi
CC BY-SA 4.0
2023-05-04T04:50:48.350Z
Yes. You check if the global invocation ID hits a pixel within your texture. And if it does, you do your calculations
Thomas
CC BY-SA 4.0
2023-05-05T12:50:14.787Z
Thanks for the explanation.
Chifti Saidi
CC BY-SA 4.0
2023-05-02T21:05:02.350Z
2023-05-04T09:19:48.503Z
2023-05-04T09:19:48.503Z
Thomas
<p>I've been going round in circles trying to understand OpenGL compute shader &quot;work groups&quot; and it's doing my head in. It's all about as clear as mud.</p>
<p>In my compute program I have to set a local size with something like this (for example):</p>
<pre><code>layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
</code></pre>
<p>So what happens if I need to use this shader on an image that's 17x17 pixels? There doesn't seem to be any way to tell OpenGL the actual size of the computation. glDispatchCompute() accepts 3 numbers but none of them are the number &quot;17&quot;?</p>
<p>Do I have to check the coordinates returned by gl_GlobalInvocationID in the shader to see if they're in range?</p>

CC BY-SA 4.0
2023-05-03T07:19:46.150Z
`glDispatchCompute` tells how many workgroups you want to launch in each dimension. `local_size` sets the size of those workgroups in each dimension. If you launch only 1 workgroup in X,Y with the size `16x16` Than whatever you do will happen for only that part of the image. The 17th row/column will be left blank. If you launch more than 1 workgroup than it'll cover that area as well though it's not going to be optimized as you'd be assigning a new `16x16` workgroup just for that last row/column
gallickgunner
CC BY-SA 4.0
2023-05-03T07:30:20.727Z
The general rule used to be to set `local_size` anywhere from 32 (NVIDIA) to 64 (AMD) or it's multiple. Preferably 32/64 due to the way architecture was designed. See what works for your gpu now or read the specification on what the wavefront/SM size is. Then use the `glDispatchCompute` parameters such that they either match your number of pixels/ data items or exceed them.
gallickgunner
<opengl>
OpenGL compute for sizes that aren't a multiple of "local_size"
Chifti Saidi
# OpenGL compute for sizes that aren't a multiple of "local_size"

(Asked by: username_0 on 2023-05-02)

I've been going round in circles trying to understand OpenGL compute shader "work groups" and it's doing my head in. It's all about as clear as mud.

In my compute program I have to set a local size with something like this (for example):

```
layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in;

```

So what happens if I need to use this shader on an image that's 17x17 pixels? There doesn't seem to be any way to tell OpenGL the actual size of the computation. glDispatchCompute() accepts 3 numbers but none of them are the number "17"?

Do I have to check the coordinates returned by gl\_GlobalInvocationID in the shader to see if they're in range?

**Tags:** opengl

> **Comments:**
> **username_1**: `glDispatchCompute` tells how many workgroups you want to launch in each dimension. `local_size` sets the size of those workgroups in each dimension. If you launch only 1 workgroup in X,Y with the size `16x16` Than whatever you do will happen for only that part of the image. The 17th row/column will be left blank. If you launch more than 1 workgroup than it'll cover that area as well though it's not going to be optimized as you'd be assigning a new `16x16` workgroup just for that last row/column  
> **username_1**: The general rule used to be to set `local_size` anywhere from 32 (NVIDIA) to 64 (AMD) or it's multiple. Preferably 32/64 due to the way architecture was designed. See what works for your gpu now or read the specification on what the wavefront/SM size is. Then use the `glDispatchCompute` parameters such that they either match your number of pixels/ data items or exceed them.  

---

## 2 answers

### Answer by username_0 on 2023-05-05. Score: -1 | Accepted answer

Short answer: Yes I have to check the coordinates.

If I set a 16x16x1 local size then use it to process a 17x17 image I have to use glDispatchCompute(2,2,1).

This generates four workgoups with size 16x16 even though the blocks to be processed have sizes 16x16, 16x1, 1x16 and 1x1

Inside the shader I have to check the range of the xy coordinates to make sure I don't do an imageStore() outside the image.

> **Comments:**
> **username_2**: in case they do not communicate with each other, a smaller workgroup size could be better: For example when setting the workgroup size to 8x8x1 and execute glDispatchCompute(3,3,1) you generate 9 workgroups with 64 invocations each. where the used invocations are: 8x8, 8x8, 8x1, 8x8, 8x8, 8x1, 1x8, 1x8, 1x1.  total invocations: 576, used invocations: 289. And the 16x16x1 solution: total invocations: 1024. Used invocations: 289  
> **username_2**: Or the best option for a 17x17 image: use a workgroup size of 17x17... generated invocations: 289 blocked work units: 31 (Nvidia) 31(AMD). You only should stick to a workgroup size of 32/64 in case of using multiple workgroups... in this case we only need 1 workgroup.  
> **username_0**: Sure, but the question isn't about work optimal group sizes. I wasn't sure if the driver would call the shader outside the area of the image or not. In hindsight it makes sense that the driver shouldn't assume that I want exactly one shader invocation per pixel in the output image. The available documentation is a bit opaque though.  

---

### Answer by username_2 on 2023-05-02. Score: 1

First 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 can be defined within the shader with the following command: `layout (local_size_x = ..., local_size_y = ..., local_size_z = ...) in;`
The workgroup size is defined by the 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.

For 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 5 \* 6 \* 7 = 210 workgroups where workgroup has 2 \* 3 \* 4 = 24 invocations each.

Within the compute shader you have the following built-in input variables:
The following information is copied from [khronos website](https://www.khronos.org/opengl/wiki/Compute_Shader)

```
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 can figue out, which invocation is the current.

Each 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 same workgroup can access them. This is faster than telling each invocation to load multiple pixels directly from the image... 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 related to each other. But this workgroup size is choosen badly! And following reason tells you why:
On GPUs the work 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 each 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, when 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.

> **Comments:**
> **username_0**: So I have to do "if ((gl_GlobalInvocationID.x < 17) && (gl_GlobalInvocationID.y < 17))" in my shader?  
> **username_2**: Yes. You check if the global invocation ID hits a pixel within your texture. And if it does, you do your calculations  
> **username_0**: Thanks for the explanation.  

---

