|Core in version||4.6|
|Core since version||4.2|
|Core ARB extension||ARB_shader_atomic_counters|
An Atomic Counter is a GLSL variable type who's storage comes from a Buffer Object. Atomic counters, as the name suggests, can have atomic memory operations performed on them. They can be thought of as a very limited form of buffer image variable.
You should use them instead of Image Load/Store whenever you can fit your needs to their limitations. The main limitations compared to image load/store or Shader Storage Buffer Objects are:
- Atomic counters can only be unsigned integers.
- Atomic counters can only be incremented or decremented by 1; no other operations besides reading the value are allowed.
- Atomic counter memory access is not incoherent (for the most part). So it follows almost the same rules as for texture reads, framebuffer writes, and so forth, not the Image Load/Store rules. You do not need to use a glMemoryBarrier to synchronize counter accesses. You will need to ensure internal visibility if you want to use ordering guarantees within a rendering command.
Atomic counter variables
Atomic Counter variables have special layout settings that define where within a buffer object a particular variable comes from. These are required; there are no alternate methods to set these fields.
Atomic counters use two layout qualifier parameters. The binding defines which Buffer Object bound to the given index in the indexed target GL_ATOMIC_COUNTER_BUFFER will provide the storage for this atomic counter. The binding parameter is not optional.
Atomic counters also have an optional offset parameter. The offset is the byte offset from the beginning of the range bound to the target to the location where this variable gets its 32-bits of storage.
The offset parameter is not required. If it is not specified, then the offset will be 4 bytes larger than the offset previously used for that binding, starting at 0 if none were previously specified. For example:
layout(binding = 0, offset = 12) uniform atomic_uint one; layout(binding = 0) uniform atomic_uint two; layout(binding = 0, offset = 4) uniform atomic_uint three; layout(binding = 1) uniform atomic_uint four; layout(binding = 1) uniform atomic_uint five; layout(binding = 1, offset = 20) uniform atomic_uint six; layout(binding = 0) uniform atomic_uint seven;
The offsets for these are as follows:
- one: 12
- two: 16 (12 + 4)
- three: 4 (specified)
- four: 0 (unused bindings offsets always start with a default of 0).
- five: 4
- six: 20
- seven: 8 (the last value used for binding 0 was 4, so this one gets 8).
Atomic counter operations are atomic, as the name suggests. Each read or read/modify/write operation will be completed in its entirety before other operations are permitted.
Atomic integer operations that overflow or underflow will wrap around. Decrementing 0 will result in 232-1.
The following operations can be performed on atomic counters:
Reads the variable, atomicly.
Adds one to the value of c, returning the original value.
Subtracts one from the value of c, returning the modified value (ie: post-decrement).
All of the following operations require OpenGL 4.6 or ARB_shader_atomic_counter_ops:
Adds data to the counter, returning the original value.
Subtracts data from the counter, returning the original value.
If data is smaller than the current value of the counter, then the counter is set to data. The value of the counter before the test is returned.
If data is larger than the current value of the counter, then the counter is set to data. The value of the counter before the test is returned.
Sets the counter to the bitwise AND between itself and data, returning the original value.
Sets the counter to the bitwise OR between itself and data, returning the original value.
Sets the counter to the bitwise XOR between itself and data, returning the original value.
Sets the counter to data, returning the original value.
If the value of the counter is equal to compare, then the counter will be set to data. The value before the test is performed is returned.
Atomic counter storage
Storage for atomic counters is provided by buffer objects. These buffers are bound to indexed binding points in the context, through the GL_ATOMIC_COUNTER_BUFFERS binding target.
All offsets specified in the shader are relative to the range of buffer object memory bound to that target. So if you use an offset of 1024 bytes in your glBindBufferRange call, and the shader has an offset of 256 bytes, the total offset from the beginning of that buffer will be 1280.
The number of buffer object bindings for atomic counter variables is restricted per-stage. The GL_MAX_*_ATOMIC_COUNTER_BUFFERS enum defines these, where * is the usual stage-specific value. All of these have a minimum requirement of 0 in GL 4.3, except for fragment and compute shaders, which have a minimum of 1. So the minimum OpenGL requires of a conforming implementation is to allow 1 buffer in the fragment or compute stage. Thus, without further research into specific hardware, you should only expect to be able to use atomic counters in fragment or compute shaders.
The total number of atomic counter buffers useable for the entire shader pipeline is limited as well. This limit is GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS. Each use of an atomic counter buffer in each stage counts. So even if two or more stages share the same atomic counter buffer binding index, they both count against the limit.
The actual binding parameter specified in GLSL may not exceed GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS. Note that this value could be different from GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS.
There are also limits on the number of atomic counter variables per-stage. These limits are defined by GL_MAX_*_ATOMIC_COUNTERS. The minimum requirements for GL 4.3 are 0 for every stage except fragment and compute shaders, where up to 8 atomic counters are required to be supported. There is also a total limit to the number of atomic counter variables in the entire pipeline, defined by GL_MAX_COMBINED_ATOMIC_COUNTERS.