Difference between revisions of "Image Load Store"

From OpenGL.org
Jump to: navigation, search
(Memory coherency: First pass at memory coherency.)
(Image size: section for query image levels.)
(15 intermediate revisions by the same user not shown)
Line 13: Line 13:
 
This can allow for a number of powerful features, including relatively cheap order-independent transparency.
 
This can allow for a number of powerful features, including relatively cheap order-independent transparency.
  
If you think that this is a great feature, remember that there is no such thing as a free lunch. The cost of using image load/store is in [[#Memory coherency|user-specified memory coherency.]] By using image load/store, you take up the responsibility to manage what OpenGL would manage for you using regular texture reads/[[Framebuffer Object|FBO]] writes.
+
If you think that this is a great feature, remember that there is no such thing as a free lunch. The cost of using image load/store is that all of its write operations are [[Memory Model#Incoherent memory access|not automatically coherent]]. By using image load/store, you take on the responsibility to manage what OpenGL would normally manage for you using regular texture reads/[[Framebuffer Object|FBO]] writes.
  
 
== Image variables ==
 
== Image variables ==
  
=== Formats and compatibility ===
+
Image variables in [[GLSL]] are variables that have one of the following {{code|image}} types. The image types are based on the type of the source [[Texture]] for the image. Not all texture types have a corresponding image type. Image variables must be declared with the [[Uniform (GLSL)|{{code|uniform}} storage qualifier]] (or as [[GLSL_Core_Language#Parameters|function parameter inputs]]).
  
== Basic load store ==
+
Like samplers, image variables represent either floating-point, signed integer, or unsigned integer [[Image Format]]s. The prefix used for the image variable name denotes which, using standard GLSL conventions. No prefix means floating-point, a prefix of {{code|i}} means signed integer, and {{code|u}} means unsigned integer.
  
== Atomic operations ==
+
For the sake of clarity, when you see a ''g'' preceding "image" in an image name, it represents any of the 3 possible prefixes. The image variables are:
  
== Memory coherency ==
+
{| class="wikitable"
 +
! Image Type
 +
! Corresponding Texture Type
 +
|-
 +
| {{code|''g''image1D}}
 +
| {{enum|GL_TEXTURE_1D}}
 +
single layer from
 +
* {{enum|GL_TEXTURE_1D_ARRAY}}
 +
|-
 +
| {{code|''g''image2D}}
 +
| {{enum|GL_TEXTURE_2D}}
 +
single layer from:
 +
* {{enum|GL_TEXTURE_2D_ARRAY}}
 +
* {{enum|GL_TEXTURE_CUBE_MAP}}
 +
* {{enum|GL_TEXTURE_CUBE_MAP_ARRAY}}
 +
* {{enum|GL_TEXTURE_3D}}
 +
|-
 +
| {{code|''g''image3D}}
 +
| {{enum|GL_TEXTURE_3D}}
 +
|-
 +
| {{code|''g''imageCube}}
 +
| [[Cubemap Texture|{{enum|GL_TEXTURE_CUBE_MAP}}]]
 +
|-
 +
| {{code|''g''image2DRect}}
 +
| [[Rectangle Texture|{{enum|GL_TEXTURE_RECTANGLE}}]]
 +
|-
 +
| {{code|''g''image1DArray}}
 +
| [[Array Texture|{{enum|GL_TEXTURE_1D_ARRAY}}]]
 +
|-
 +
| {{code|''g''image2DArray}}
 +
| [[Array Texture|{{enum|GL_TEXTURE_2D_ARRAY}}]]
 +
|-
 +
| {{code|''g''imageCubeArray}}
 +
| [[Cubemap Texture#Cubemap array textures|{{enum|GL_TEXTURE_CUBE_MAP_ARRAY}}]] (requires GL 4.0 or {{extref|texture_cube_map_array}})
 +
|-
 +
| {{code|''g''imageBuffer}}
 +
| [[Buffer Texture|{{enum|GL_TEXTURE_BUFFER}}]]
 +
|-
 +
| {{code|''g''image2DMS}}
 +
| [[Multisample Texture|{{enum|GL_TEXTURE_2D_MULTISAMPLE}}]]
 +
single layer from:
 +
* {{enum|GL_TEXTURE_2D_MULTISAMPLE_ARRAY}}
 +
|-
 +
| {{code|''g''image2DMSArray}}
 +
| {{enum|GL_TEXTURE_2D_MULTISAMPLE_ARRAY}}
 +
|-
 +
|}
  
Rendering is a [[Synchronization|very asynchronous process, but the OpenGL specification defines all of these operations to happen sequentially]]. Therefore, hardware and drivers jump through a few hoops in order to ensure sanity for the user.
+
There are no "shadow" variants.
  
For example, uploading data to an image will often be deferred to whenever the driver wants to get around to it. If you then render with that texture, the rendering operation itself will be deferred to when the texture upload is done. Similarly, if you write to images bound to a [[Framebuffer Object]] in one rendering operation, you can immediately issue a rendering operation that reads from those images (and writes to some ''other'' images, of course). The OpenGL implementation will automatically delay the second rendering command until the first has completed and flush internal caches so that texture reads will see the written data properly. And so forth.
+
You will notice several "single layer from" entries in the above table. It is possible to bind a specific layer from certain texture types to an image. When you do so, you must use a different image variable compared to the source texture's actual type, as shown above.
  
This is brought up here because, by using image load/store, you are ''signing away your right to all of this.'' You must now manage this all yourself.
+
=== Memory qualifiers ===
  
When you write to an image via an image store operation, any subsequent reads from almost anywhere are ''not'' guaranteed to see them. And by "almost anywhere", this includes:
+
Image variables can be declared with a [[Type_Qualifier_(GLSL)#Memory_qualifiers|number of qualifiers]] that have different meanings for how the variable is accessed.
  
* Reads from the texture via {{apifunc|glGetTexImage}}, or if it is bound to an FBO, {{apifunc|glReadPixels}}.
+
{{memory qualifiers}}
* Texture reads via [[Sampler (GLSL)|samplers]].
+
* If the image was a buffer texture, ''any'' form of reading from that buffer, such as using it for a [[Vertex Buffer Object]] or [[Uniform Buffer Object]].
+
  
In short, you get almost nothing. Everything is asynchronous, and OpenGL will not protect you from this fact. All of these can be alleviated, but [[#Ensuring visibility|only ''specifically'' at the request of the user]]. It will not happen automatically.
+
Multiple qualifiers can be used, but they must make sense together (a variable cannot be both {{code|readonly}} and {{code|writeonly}}). You are encouraged to use {{code|restrict}} whenever possible.
  
=== Guarantees ===
+
=== Format qualifiers ===
  
Despite the above, there are some protections that OpenGL provides. What follows is a list of things that the specification does require image load/store operations to guarantee about when data will be accessible.
+
Image variables can be declared with a [[Type_Qualifier_(GLSL)#Image formats|format qualifier]]; this specifies the format for any read operations done on the image. Therefore, a format qualifier is required if you do not declare the variable with the {{code|writeonly}} memory qualifier. Write-only variables cannot be used as in any reading operations; this includes calling load ''and'' atomic (read/modify/write) functions. So if you want to read from an image, you must declare the format.
  
First, within a single shader invocation, if you write something to an image variable, it will always be visible to that variable for reading. You need not do anything special to make this happen. However, it is possible that, between writing and reading, another invocation may have stomped on that value.
+
The format defines how the shader interprets the bits of data that it reads from the image. It also defines how it converts the data passed for write operations when it writes it into the image. This allows the actual [[Image Format]] of the image to [[#Format conversion|differ between what the shader sees and what is stored in the image]], sometimes substantially.
  
Second, if a shader invocation is being executed, then the shader invocations necessary to execute it must have taken place. For example, in a fragment shader, you can assume that the vertex shaders to compute the vertices for the primitive being rasterized have completed. So you may read any image values written by them.
+
The format are divided into three categories, representing the three types of image variables:
  
{{warning|This only applies to the shader invocations ''directly responsible'' for this shader invocation. Being in a fragment shader does not mean that ''all'' vertex shaders in a rendering command have completed. Only the ones needed for this particular fragment shader invocation have been executed.}}
+
{{div col}}
 +
{{layout image formats}}
 +
{{div col end}}
  
{{note|Geometry shaders have a caveat here. A GS may write multiple vertices and primitives. Therefore, you may only assume that the GS executed ''just far enough'' to write enough vertices needed to render the fragment shader's primitive.}}
+
== Image operations ==
  
Third, sometimes a fragment shader is executed for the sole purpose of computing derivatives for other shaders. All image store and atomic operations will be ignored by that invocation.
+
OpenGL provides a number of functions for accessing images through image variables.
  
=== Invocation order and count ===
+
Image operations have "image coordinates", which serve the purpose of specifying where in an image that an access should take place. Image coordinates are different from texture coordinates in that image coordinates are ''always'' signed integers in texel space.
  
One problem with the above is what defines "subsequent invocations". OpenGL allows implementations a ''lot'' of leeway on the ordering of shader invocations, as well as the number of invocations. Here is a list of the rules:
+
Each image variable has a specific dimensionality for their image coordinates, which represents the dimensionality of the underlying image. An {{code|image1D}} variable takes an {{code|int}} as a coordinate. {{code|image2DArray}} takes an {{code|ivec3}}; the third component is the array layer.
  
# You may not assume that a vertex shader will be executed only once for every vertex you pass it. It may be executed multiple times for the same vertex. In indexed rendering scenarios, it is very possible for re-used indices to not execute the vertex shader a second or third time.
+
Cube maps (and cube map arrays) are accessed very differently from texture accesses. The image coordinate for cube map and cube map arrays are both {{code|ivec3}}. The coordinate is ''not a direction''; it is a texel coordinate within the space of the cube. The third component is the face index, [[Template:cubemap layer face ordering|as this is usually defined]]. Image variables effectively treat cube maps as simply a form of array texture; cube map arrays are just bigger arrays, as their third component is the layer-face index.
  
# The same applies to tessellation evaluation shaders.
+
When accessing multisample textures, the accessing function has another parameter, an {{code|int}} that defines the sample index to read from or write to.
  
# The number of fragment shader invocations generated from rasterizing a primitive depends on the pixel ownership test, whether early depth test is enabled, and whether the rendering is to a multisample buffer. When not using per-sample shading, the number of fragment shader invocations is undefined within a pixel area, but it must be between 1 and the number of samples in the buffer.
+
Let us collectively refer to the image coordinate parameters as "IMAGE_COORD". When you see this in a function definition below, this means that the function takes an image coordinate, which may include a sample index parameter if it is a multisample image.
  
# Invocations of the same shader stage may be executed in ''any'' order. Even within the same draw call. This includes fragment shaders; writes to the framebuffer are ordered, but the actual fragment shader execution is not.
+
=== Image size ===
 +
{{infobox feature
 +
| name = Image Size
 +
| core = 4.3
 +
| core_extension = {{extref|shader_image_size}}
 +
}}
  
# Outside of the above case of a shader which depends on the outputs of another, invocations between stages may be executed in any order. This ''includes'' invocations launched by different rendering commands. While it is technically unlikely that two vertex shaders from different rendering operations could be running at the same time, it is also very ''possible'', so OpenGL provides no guarantees.
+
The size of the image for an image variable can be queries with this function:
  
=== Ensuring visibility ===
+
  '''ivec''' imageSize('''gimage''' {{param|image}});
  
The term "visibility" represents when someone can safely access the value written to an image from a shader invocation. There are two tools to ensure visibility; they are used to ensure visibility from two different contexts. There is the {{code|coherent}} qualifier and there is the {{apifunc|glMemoryBarrier}} function.
+
The size of the returned vector will be the size of the image coordinate, ''except in the case of cube maps''. For cube maps, the size will be {{code|ivec2}}; the third dimension would always be 6, so it is not returned. Cube map arrays will return {{code|ivec3}}, with the third component being the number of layer-faces.
  
{{code|coherent}} is used on image variables, such that writes to {{code|coherent}} qualified variables will be read correctly by {{code|coherent}} qualified variables in another invocation. Note that this requires the {{code|coherent}} qualifier on ''both'' the writer and the reader; if one of them doesn't have it, then nothing is guaranteed.
+
Accessing any texels outside of this size results in invalid accesses, as defined below.
  
Note that {{code|coherent}} does not ''ignore'' all of the prior rules. In order for a write to become visible to an invocation, it must first ''have happened''. Therefore, {{code|coherent}} can only really work if you know that the writing invocation has executed. As stated above, if invocation B's existence depends on output from invocation A, then you know that the write has happened.
+
=== Image mipmap ===
 +
{{infobox feature
 +
| name = Image Query Levels
 +
| core = 4.3
 +
| core_extension = {{extref|texture_query_levels}}
 +
}}
 +
{{missing|section}}
 +
{{clear float}}
  
There are other times you can know that a write has happened. In [[Compute Shader]]s, the {{code|barrier}} function ensures that all other invocations in a work group have reached that point in the computation. This works for [[Tessellation Control Shader]]s as well, for all of the invocations in a patch. So you know that all invocations in a work group/patch have reached that point, so all prior writes have been written. You still need the {{code|coherent}} qualifier on both the reading and writing variable, but it works.
+
=== Image load ===
  
{{code|coherent}} alone is not enough however. You also need to use a memory barrier, to effectively let OpenGL know that you're finished writing a batch of things and want to make them visible to someone else. The functions for this are of the form {{code|memoryBarrier*}} (no relation to the {{apifunc|glMemoryBarrier}} API function). This is a small suite of functions, which represent different barrier cases. For image load/store, {{code|memoryBarrierImage}} is used to order image writes. Or you can use {{code|memoryBarrier}} to order all of these special writes.
+
Image load functions read a specific location from the image into the shader.
  
Note that {{code|memoryBarrierImage}} requires GL 4.3/{{extref|compute_shader}}.
+
{{note|Load operations from any texel that is outside of the boundaries of the bound image will return all zeros.}}
  
Atomic operations are always effectively {{code|coherent}}, due to their atomic nature (nothing can interfere with the read/modify/write operation). Memory barriers can still be employed if you wish to ensure the ordering between two separate atomic operations, but it is not necessary.
+
Reading from an image is done with this function:
  
{{code|coherent}} is ''only'' useful in cases of shader-to-shader reading/writing where you can enforce invocation order. If you want to establish visibility between two different rendering commands, you must use a much more powerful mechanism:
+
  '''gvec4''' imageLoad('''gimage''' {{param|image}}, IMAGE_COORD);
  
void {{apifunc|glMemoryBarrier}}}(GLbitfield {{param|barriers}});
+
The return value is the data from the image, [[#Format conversion|converted]] as per the [[#Format qualifiers|format specified by the format layout qualifier]].
  
This function is a way of ensuring the visibility of image load/store operations with a wide variety of OpenGL operations, as listed on the documentation page. The thing to keep in mind about the various bits in the bitfield is this: they represent the operation you want to be able to do ''after'' making the image load/store visible. This is the operation you want to ''see'' the load/store results.
+
=== Image store ===
  
For example, if you do some image load/store operations on a texture, and then want to read it back onto the CPU, you would use the {{enum|GL_TEXTURE_UPDATE_BARRIER_BIT​}}. If you did image load/store to a buffer, and then want to use it for vertex array data, you would use {{enum|GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT​}}. That's the idea.
+
Image store operations write a value to a specific location in the image.
  
Note that if you want image load/store operations from one command to be visible to image load/store operations from another command, you use {{enum|GL_SHADER_IMAGE_ACCESS_BARRIER_BIT​}}.
+
{{note|Store operations to any texel that is outside the boundaries of the bound image will do nothing.}}
  
=== Guidelines and usecases ===
+
Writing to an image is done with this function:
  
Here are some basic use cases and how to synchronize them properly.
+
  '''void''' imageStore('''gimage''' {{param|image}}, IMAGE_COORD, '''gvec4''' {{param|data}});
  
; Read-only image variables
+
The value in {{param|data}} will be written into the image at the given coordinate, using [[#Format conversion|format conversion]] based on the {{param|format}} parameter given to {{apifunc|glBindImageTexture}}.
: If a shader only reads images, then it does not need any form of synchronization for visibility. Even if you modify objects via OpenGL commands ({{apifunc|glTexSubImage2D}}, for example) or whatever, OpenGL requires that image reads remain properly synchronized.
+
; {{code|barrier}} invocation write/read
+
: Use {{code|coherent}} if you use a mechanism like {{code|barrier}} to synchronize between invocations.
+
; Dependent invocation write/read
+
: If you have one invocation which is dependent on another (the vertex shaders used to generate a primitive used for a fragment shader), then you need to use {{code|coherent}} on the variables and invoke a {{code|memoryBarrier/Image}} as appropriate after you finish writing to the images of interest.
+
; Shader image write/read between rendering commands
+
: There is no need for {{code|coherent}} here. Just use {{apifunc|glMemoryBarrier|(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT​)}} between the two rendering/dispatch commands.
+
; Shader image writes, read by other OpenGL operations
+
: Again, {{code|coherent}} is not necessary. You must use a {{apifunc|glMemoryBarrier}} appropriate to the operation of interest.
+
  
== Limitations ==
+
=== Atomic operations ===
  
{{stub}}
+
Atomic operations perform read/modify/write operations on a location in an image. These operations are guaranteed to be "atomic": if two shaders issue the same atomic operation on the same location in the same image, one will go first, followed by the other.
 +
 
 +
Consider a shader that reads from a location, adds 1 to it, and then writes to it. It is theoretically possible for two such shaders to read from and and write to the same location in the same image at the same time. Because of the way [[#Memory coherency|memory accesses are handled]], it is entirely possible that this sequence of events works like this:
 +
 
 +
* Shader A reads the image value, say 0.
 +
* Shader B reads the image value, also 0.
 +
* Shader B adds 1 to its ''local'' value of 0, becoming 1.
 +
* Shader B writes its local value to the image. The image now has 1.
 +
* Shader A adds 1 to its ''local'' value of 0, becoming 1.
 +
* Shader A writes its local value to the image. The image now has 1.
 +
 
 +
Atomic operations prevent this possibility entirely. Each shader's independent atomic operation will fully complete before the next one starts.
 +
 
 +
The return value of all atomic operations is the original value before modifications. The value written will be the modified value.
 +
 
 +
Atomic operations to any texel that is outside of the boundaries of the bound image will return 0 and do nothing.
 +
 
 +
==== Atomic limitations ====
 +
 
 +
There are some severe limitations on image atomic operations. First, atomics can only be used on integer images, either signed or unsigned. Second, they can only be used on images with the {{code|GL_R32I/r32i}} or {{code|GL_R32UI/r32ui}} formats.
 +
 
 +
It is very possible to use [[#Format conversion|format conversions]] to make image operations work with other formats. But the GLSL code logic will be operating on 32-bit integers.
 +
 
 +
Below, the term "'''gint'''" means either {{code|int}} or {{code|uint}}, as is appropriate for the '''gimage''' type.
 +
 
 +
==== Atomic set value ====
 +
 
 +
The value at the location in an image can be directly set via this function:
 +
 
 +
  '''gint''' imageAtomicExchange('''gimage''' {{param|image}}, IMAGE_COORDS, '''gint''' {{param|data}});
 +
 
 +
This function is called "exchange" because it effectively exchanges {{param|data}} with the value in the image. The return value of all atomic functions is the original value from the image, so it is exchanging {{param|data}} with that value.
 +
 
 +
==== Atomic conditional set ====
 +
 
 +
A very powerful operation is the conditional modify operation. This operation will write a new value ''only'' if the current value in the image is equal to the given value.
 +
 
 +
  '''gint''' imageAtomicCompSwap('''gimage''' {{param|image}}, IMAGE_COORDS, '''gint''' {{param|compare}}, '''gint''' {{param|data}});
 +
 
 +
If the current value of {{param|image}} at the image coordinate is exactly equal to {{param|compare}}, then {{param|data}} will be stored into the image at that location. Otherwise, the location will retain the original value.
 +
 
 +
While the function doesn't provide a direct way to tell if it actually wrote the value, it always returns the original value. So if you need to know, you can test the return value against {{param|compare}} yourself.
 +
 
 +
==== Atomic math ====
 +
 
 +
GLSL only provides a single math operation: additions:
 +
 
 +
  '''gint''' imageAtomicAdd('''gimage''' {{param|image}}, IMAGE_COORDS, '''gint''' {{param|data}});
 +
 
 +
This will read the value from the image, add {{param|data}} to it, and then write it to the image.
 +
 
 +
Obviously if you need subtract, simply negate {{param|data}}. This will only work directly with signed integers. However, because GLSL 4.30 mandates two's complement, you can get the same effect with unsigned integers, since {{code|int(uint)}} and {{code|uint(int)}} conversion constructors will preserve the bit pattern.
 +
 
 +
So if you need unsigned integer subtraction, you can do this:
 +
 
 +
  imageAtomicAdd(..., uint(-data));
 +
 
 +
Obviously if this produces a negative value, you'll get a very positive value back instead, since it thinks you're doing unsigned math.
 +
 
 +
==== Atomic bitwise ====
 +
 
 +
GLSL provides 3 atomic bitwise operations: and, or, and xor:
 +
 
 +
  '''gint''' imageAtomicAnd('''gimage''' {{param|image}}, IMAGE_COORDS, '''gint''' {{param|data}});
 +
  '''gint''' imageAtomicOr('''gimage''' {{param|image}}, IMAGE_COORDS, '''gint''' {{param|data}});
 +
  '''gint''' imageAtomicXor('''gimage''' {{param|image}}, IMAGE_COORDS, '''gint''' {{param|data}});
 +
 
 +
These read the value from the image, perform the operation given {{param|data}}, and writes the results back to the image.
 +
 
 +
==== Atomic minmax ====
 +
 
 +
GLSL provides a pair of functions that ensure that the value in the image is no larger than the given value or no smaller than it.
 +
 
 +
  '''gint''' imageAtomicMin('''gimage''' {{param|image}}, IMAGE_COORDS, '''gint''' {{param|data}});
 +
  '''gint''' imageAtomicMax('''gimage''' {{param|image}}, IMAGE_COORDS, '''gint''' {{param|data}});
 +
 
 +
For the "Min" function, it will ensure that the image value is no smaller than {{param|data}}. The "Max" function will ensure that the iamge value is no larger than {{param|data}}.
 +
 
 +
== Images in the context ==
 +
 
 +
The way to associate an image variable in GLSL works very similar to the way of associating [[Sampler (GLSL) #Binding textures to samplers|samplers with textures]].
 +
 
 +
For each shader stage, there is some number of available ''image units'' (not to be confused with ''texture'' image units). The number of image units can be [[Shader#Resource limitations|queried per-stage, using {{enum|GL_MAX_*_IMAGE_UNIFORMS}}]], where * is filled in with the appropriate shader stage. Note that OpenGL 4.3 only requires [[Fragment Shader]]s and [[Compute Shader]]s to have non-zero numbers of image units; the minimum required in those cases is 8.
 +
 
 +
The total number of image units available is queried via {{enum|GL_MAX_IMAGE_UNITS}}; this represents the total number of images you can bind at one time.
 +
 
 +
Just as with samplers, image variables reference image unit indices in the context. These are usually set with a [[Type_Qualifier_(GLSL)#Binding_points|binding layout qualifier]], but they can also be set with {{apifunc|glUniform|1i}} or {{apifunc|glProgramUniform|1i}}.
 +
 
 +
After associating the image variable with its image unit, you then bind an image to the context. This is done via this function:
 +
 
 +
  void {{apifunc|glBindImageTexture}}(GLuint {{param|unit}}, GLuint {{param|texture}}, GLint {{param|level}}, GLboolean {{param|layered}}, GLint {{param|layer}}, GLenum {{param|access}}, GLenum {{param|format}})
 +
 
 +
This binds an image from {{param|texture}} to the given image {{param|unit}}, using the given mipmap {{param|level}} and array {{param|layer}}.
 +
 
 +
Image bindings can be layered or non-layered, which is determined by {{param|layered}}. If {{param|layered}} is {{enum|GL_TRUE}}, then {{param|texture}} must be an [[Array Texture]] (of some type), a [[Cubemap Texture]], or a [[3D Texture]]. If a layered image is being bound, then the entire mipmap level specified by {{param|level}} is bound.
 +
 
 +
If the image is not layered, then the user must use the {{param|layer}} to select which array layer will be bound. If the texture does not have array layers, then this parameter must be 0. As with other functions, if this is a cubemap array texture, then {{param|layer}} is the layer-face to select.
 +
 
 +
If an array or cubemap texture is bound and is not layered, then the bound image is not an array or cubemap image. So if you bind a single array layer from a {{enum|GL_TEXTURE_1D_ARRAY}} texture, it should be used with the {{code|image1D}} image variable type. Simiarly, layers from a 2D array texture, cubemap, 3D texture, or cubemap array should be {{code|image2D}}, and a layer from a 2D multisample array should use {{code|image2DMS}}.
 +
 
 +
The {{param|access}} specifies how the shader may access the image through this image unit. This can be {{enum|GL_READ_ONLY}}, {{enum|GL_WRITE_ONLY}}, or {{enum|GL_READ_WRITE}}. If the shader violates this restriction, then all manor of bad things can happen, including program termination. It is a good idea to use memory qualifiers in the shader itself to catch this at shader compile-time.
 +
 
 +
The {{param|format}} parameter is an [[Image Format]] which defines the format that will be used for ''writes'' to the image. If a [[#Format qualifiers|format qualifier]] is specified in the shader, this format must match it. The format must be [#Format compatibility|compatible with the texture's image format]]. The {{param|format}} parameter may only use formats from the following table:
 +
 
 +
{{bind image format table}}
 +
 
 +
Also, note that these are the ''only'' image formats you can use for images in image load/store operations. You must use exactly these image formats and no others.
 +
 
 +
== Format conversion ==
 +
 
 +
The [[Image Format]] of the image may be different from the format specified to the image binding function and in the shader. Values read and written are converted in the following way, assuming that the formats are [[#Format compatibility|compatible]].
 +
 
 +
The term "source format" represents the image format of whatever the source of the operation is. Similarly, the "destination format" is the image format of whatever the destination of the operation is. Therefore:
 +
 
 +
* Read operation:
 +
** Source format: The image's actual format.
 +
** Dest format: The format specified by the image binding function {{apifunc|glBindImageTexture}} (which must match the [[#Format qualifiers|format declared in the shader qualifier]]).
 +
* Write operation:
 +
** Source format: The format specified by the image binding function {{apifunc|glBindImageTexture}}.
 +
** Dest format: The image's actual format.
 +
 
 +
All operations, whether read or write, function as though they were copying data from/to images with those formats. The first step of a write operation would be taking the value provided by the shader and writing that into a texture in the source image format. Then the format conversion takes place, copying the value into the destination. Similarly, the last step of read operations is reading from the destination image into the value in the shader.
 +
 
 +
{{note|What follows is a description of how reading and writing ''appears'' to behave. Obviously, no hardware does it this way; this is just a convenient way of describing the behavior in terms of bits and bytes.}}
 +
 
 +
The conversion works based on memory copies using existing API functions. The source format values are read into memory as though calling {{apifunc|glGetTexImage}}. The destination format values are written into their image as though they were uploaded via a call to one of the {{code|glTexSubImage}} functions.
 +
 
 +
Both of these functions take [[Pixel Transfer|pixel transfer formats and types]]. The two effective calls will use {{param|format}}s and {{param|type}}s that exactly match the source/destination image format.
 +
 
 +
For example, if the source image format was {{enum|GL_RGBA8UI}}, then the {{param|format}} and {{param|type}} passed to {{apifunc|glGetTexImage}} would be {{enum|GL_RGBA_INTEGER}} and {{enum|GL_UNSIGNED_BYTE}}. If the destination image format for a copy is {{enum|GL_RGB10_A2}} (which may or may not be compatible with {{enum|GL_RGBA8UI}}),
 +
 
 +
The destination format values are written into "memory", using values pulled from the source, as though written with one of the {{code|glTexSubImage}} calls. These calls again use pixel transfer formats and types that exactly match the ''destination'' format.
 +
 
 +
Note that (compatibility willing) we are perfectly capable of switching between floating-point and integral formats. However, converting between {{enum|GL_R32F}} and {{enum|GL_RGBA8}} is not well-defined, in terms of the endian conversion. The reason is that {{enum|GL_R32F}} will be read using a type of {{enum|GL_FLOAT}}, but the writing will write 4 bytes in RGBA order. The R byte will be the first byte read from the {{enum|GL_FLOAT}}, but the endian storage of {{enum|GL_FLOAT}} is not defined. So the first byte may be the most significant or the least significant.
 +
 
 +
For any particular platform, you could assume a specific endian. But OpenGL itself provides no guarantees. If you want some guarantees on being able to play with the bits of a 32-bit floating-point texture, convert it to {{enum|GL_R32UI}} instead and do your bit manipulation in the shader.
 +
 
 +
=== Format compatibility ===
 +
 
 +
The various image format compatibility matrix for image load/store operations is very similar to the compatiblity for [[Texture_Storage#Texture_views|texture views]], though there are some differences. The first difference is that the list of image formats that can be used for images in load/store operations is more limited: only the formats mentioned above may be used.
 +
 
 +
Each of these formats has two properties: a size and a class. The size represents the bit-size of each texel. For example, {{enum|GL_R32F}} has a size of 32; {{enum|GL_RGBA32UI}} has a size of 128. The class represents the number of components and the bit-depth of each component. The class of {{enum|GL_R32F}} is 1x32, while the class of {{enum|GL_RGBA8}} is 4x8.
 +
 
 +
The class for formats with oddball bitdepths ({{enum|GL_R11F_G11F_B10F}}, for example) is the arrangement of components. So {{enum|GL_R11F_G11F_B10F}}'s class is 11/11/10, while {{enum|GL_RGB10_A2UI}}'s class is 10/10/10/2. This has a class match with {{enum|GL_RGB10_A2}}.
 +
 
 +
If the texture was allocated by OpenGL (it is possible for OpenCL or other interop layers to allocate textures), then the only thing that matters for compatibility is overall texel size. So it is perfectly valid to map an {{enum|GL_R32F}} image to an {{enum|GL_RGBA8UI}} format and back, though again endian conversions may make this unusable in platform-neutral code.
 +
 
 +
If a texture was allocated from outside of OpenGL, then how compatibility is determined may not be by texel size; it may be by class. You must use glGetTexParameter with {{enum|GL_IMAGE_FORMAT_COMPATIBILITY_TYPE}} to detect which. It will return either {{enum|GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE}} or {{enum|GL_IMAGE_FORMAT_COMPATIBILITY_BY_CLASS}}, specifying how compatibility is determined.
 +
 
 +
You can also detect this at the image format level with an image format query using the same parameter; this will be true for all (foreign) textures using that image format and texture type.
 +
 
 +
As an alternative to querying with foreign textures, you could just stick to formats that match on class. If the classes match, the sizes also match.
 +
 
 +
== Memory coherency ==
 +
{{main|Memory Model#Incoherent memory access}}
 +
 
 +
Writes and atomic operations via image variables are not automatically coherent. Therefore, you must do things to ensure that writes have occurred before you can read those values.
 +
 
 +
== Limitations ==
  
 
[[Category:OpenGL Shading Language]]
 
[[Category:OpenGL Shading Language]]
 
[[Category:Shaders]]
 
[[Category:Shaders]]

Revision as of 03:11, 13 February 2013

Image Load Store
Core in version 4.5
Core since version 4.2
Core ARB extension ARB_shader_image_load_store
EXT extension EXT_shader_image_load_store

Image load/store is the ability of Shaders to more-or-less arbitrarily read from and write to images.

Overview

The idea with image load/store is that the user can bind one of the images in a Texture to a number of image binding points (which are separate from texture image units). Shaders can read information from these images and write information to them, in ways that they cannot with textures.

This can allow for a number of powerful features, including relatively cheap order-independent transparency.

If you think that this is a great feature, remember that there is no such thing as a free lunch. The cost of using image load/store is that all of its write operations are not automatically coherent. By using image load/store, you take on the responsibility to manage what OpenGL would normally manage for you using regular texture reads/FBO writes.

Image variables

Image variables in GLSL are variables that have one of the following image​ types. The image types are based on the type of the source Texture for the image. Not all texture types have a corresponding image type. Image variables must be declared with the uniform​ storage qualifier (or as function parameter inputs).

Like samplers, image variables represent either floating-point, signed integer, or unsigned integer Image Formats. The prefix used for the image variable name denotes which, using standard GLSL conventions. No prefix means floating-point, a prefix of i​ means signed integer, and u​ means unsigned integer.

For the sake of clarity, when you see a g preceding "image" in an image name, it represents any of the 3 possible prefixes. The image variables are:

Image Type Corresponding Texture Type
gimage1D​ GL_TEXTURE_1D

single layer from

  • GL_TEXTURE_1D_ARRAY
gimage2D​ GL_TEXTURE_2D

single layer from:

  • GL_TEXTURE_2D_ARRAY
  • GL_TEXTURE_CUBE_MAP
  • GL_TEXTURE_CUBE_MAP_ARRAY
  • GL_TEXTURE_3D
gimage3D​ GL_TEXTURE_3D
gimageCube​ GL_TEXTURE_CUBE_MAP
gimage2DRect​ GL_TEXTURE_RECTANGLE
gimage1DArray​ GL_TEXTURE_1D_ARRAY
gimage2DArray​ GL_TEXTURE_2D_ARRAY
gimageCubeArray​ GL_TEXTURE_CUBE_MAP_ARRAY (requires GL 4.0 or ARB_texture_cube_map_array)
gimageBuffer​ GL_TEXTURE_BUFFER
gimage2DMS​ GL_TEXTURE_2D_MULTISAMPLE

single layer from:

  • GL_TEXTURE_2D_MULTISAMPLE_ARRAY
gimage2DMSArray​ GL_TEXTURE_2D_MULTISAMPLE_ARRAY

There are no "shadow" variants.

You will notice several "single layer from" entries in the above table. It is possible to bind a specific layer from certain texture types to an image. When you do so, you must use a different image variable compared to the source texture's actual type, as shown above.

Memory qualifiers

Image variables can be declared with a number of qualifiers that have different meanings for how the variable is accessed.

coherent​
Normally, the compiler is free to assume that this shader invocation is the only invocation that modifies values read through this variable. It also can freely assume that other shader invocations may not see values written through this variable.
Using this qualifier is required to allow dependent shader invocations to communicate with one another, as it enforces the coherency of memory accesses. Using this requires the appropriate memory barriers to be executed, so that visibility can be achieved.
When communicating between shader invocations for different rendering commands, glMemoryBarrier should be used instead of this qualifier.
volatile​
The compiler normally is free to assume that values accessed through variables will only change after memory barriers or other synchronization. With this qualifier, the compiler assumes that the contents of the storage represented by the variable could be changed at any time.
restrict​
Normally, the compiler must assume that you could access the same image/buffer object separate variables in the same shader. Therefore, if you write to one variable, and read from a second, the compiler assumes that it is possible that you could be reading the value you just wrote. With this qualifier, you are telling the compiler that this particular variable is the only variable that can modify the memory visible through that variable within this shader invocation (other shader stages don't count here). This allows the compiler to optimize reads/writes better.
You should use this wherever possible.
readonly​
Normally, the compiler allows you to read and write from variables as you wish. If you use this, the variable can only be used for reading operations.
writeonly​
Normally, the compiler allows you to read and write from variables as you wish. If you use this, the variable can only be used for writing operations (atomic writes are forbidden because they also count as reads).

Multiple qualifiers can be used, but they must make sense together (a variable cannot be both readonly​ and writeonly​). You are encouraged to use restrict​ whenever possible.

Format qualifiers

Image variables can be declared with a format qualifier; this specifies the format for any read operations done on the image. Therefore, a format qualifier is required if you do not declare the variable with the writeonly​ memory qualifier. Write-only variables cannot be used as in any reading operations; this includes calling load and atomic (read/modify/write) functions. So if you want to read from an image, you must declare the format.

The format defines how the shader interprets the bits of data that it reads from the image. It also defines how it converts the data passed for write operations when it writes it into the image. This allows the actual Image Format of the image to differ between what the shader sees and what is stored in the image, sometimes substantially.

The format are divided into three categories, representing the three types of image variables:

  • Floating-point layout image formats:
    • rgba32f
    • rgba16f
    • rg32f
    • rg16f
    • r11f_g11f_b10f
    • r32f
    • r16f
    • rgba16
    • rgb10_a2
    • rgba8
    • rg16
    • rg8
    • r16
    • r8
    • rgba16_snorm
    • rgba8_snorm
    • rg16_snorm
    • rg8_snorm
    • r16_snorm
    • r8_snorm
  • Signed integer layout image formats:
    • rgba32i
    • rgba16i
    • rgba8i
    • rg32i
    • rg16i
    • rg8i
    • r32i
    • r16i
    • r8i
  • Unsigned integer layout image formats:
    • rgba32ui
    • rgba16ui
    • rgb10_a2ui
    • rgba8ui
    • rg32ui
    • rg16ui
    • rg8ui
    • r32ui
    • r16ui
    • r8ui

Image operations

OpenGL provides a number of functions for accessing images through image variables.

Image operations have "image coordinates", which serve the purpose of specifying where in an image that an access should take place. Image coordinates are different from texture coordinates in that image coordinates are always signed integers in texel space.

Each image variable has a specific dimensionality for their image coordinates, which represents the dimensionality of the underlying image. An image1D​ variable takes an int​ as a coordinate. image2DArray​ takes an ivec3​; the third component is the array layer.

Cube maps (and cube map arrays) are accessed very differently from texture accesses. The image coordinate for cube map and cube map arrays are both ivec3​. The coordinate is not a direction; it is a texel coordinate within the space of the cube. The third component is the face index, as this is usually defined. Image variables effectively treat cube maps as simply a form of array texture; cube map arrays are just bigger arrays, as their third component is the layer-face index.

When accessing multisample textures, the accessing function has another parameter, an int​ that defines the sample index to read from or write to.

Let us collectively refer to the image coordinate parameters as "IMAGE_COORD". When you see this in a function definition below, this means that the function takes an image coordinate, which may include a sample index parameter if it is a multisample image.

Image size

Image Size
Core in version 4.5
Core since version 4.3
Core ARB extension ARB_shader_image_size

The size of the image for an image variable can be queries with this function:

 ivec imageSize(gimage image​);

The size of the returned vector will be the size of the image coordinate, except in the case of cube maps. For cube maps, the size will be ivec2​; the third dimension would always be 6, so it is not returned. Cube map arrays will return ivec3​, with the third component being the number of layer-faces.

Accessing any texels outside of this size results in invalid accesses, as defined below.

Image mipmap

Image Query Levels
Core in version 4.5
Core since version 4.3
Core ARB extension ARB_texture_query_levels


Image load

Image load functions read a specific location from the image into the shader.

Note: Load operations from any texel that is outside of the boundaries of the bound image will return all zeros.

Reading from an image is done with this function:

 gvec4 imageLoad(gimage image​, IMAGE_COORD);

The return value is the data from the image, converted as per the format specified by the format layout qualifier.

Image store

Image store operations write a value to a specific location in the image.

Note: Store operations to any texel that is outside the boundaries of the bound image will do nothing.

Writing to an image is done with this function:

 void imageStore(gimage image​, IMAGE_COORD, gvec4 data​);

The value in data​ will be written into the image at the given coordinate, using format conversion based on the format​ parameter given to glBindImageTexture.

Atomic operations

Atomic operations perform read/modify/write operations on a location in an image. These operations are guaranteed to be "atomic": if two shaders issue the same atomic operation on the same location in the same image, one will go first, followed by the other.

Consider a shader that reads from a location, adds 1 to it, and then writes to it. It is theoretically possible for two such shaders to read from and and write to the same location in the same image at the same time. Because of the way memory accesses are handled, it is entirely possible that this sequence of events works like this:

  • Shader A reads the image value, say 0.
  • Shader B reads the image value, also 0.
  • Shader B adds 1 to its local value of 0, becoming 1.
  • Shader B writes its local value to the image. The image now has 1.
  • Shader A adds 1 to its local value of 0, becoming 1.
  • Shader A writes its local value to the image. The image now has 1.

Atomic operations prevent this possibility entirely. Each shader's independent atomic operation will fully complete before the next one starts.

The return value of all atomic operations is the original value before modifications. The value written will be the modified value.

Atomic operations to any texel that is outside of the boundaries of the bound image will return 0 and do nothing.

Atomic limitations

There are some severe limitations on image atomic operations. First, atomics can only be used on integer images, either signed or unsigned. Second, they can only be used on images with the GL_R32I/r32i​ or GL_R32UI/r32ui​ formats.

It is very possible to use format conversions to make image operations work with other formats. But the GLSL code logic will be operating on 32-bit integers.

Below, the term "gint" means either int​ or uint​, as is appropriate for the gimage type.

Atomic set value

The value at the location in an image can be directly set via this function:

 gint imageAtomicExchange(gimage image​, IMAGE_COORDS, gint data​);

This function is called "exchange" because it effectively exchanges data​ with the value in the image. The return value of all atomic functions is the original value from the image, so it is exchanging data​ with that value.

Atomic conditional set

A very powerful operation is the conditional modify operation. This operation will write a new value only if the current value in the image is equal to the given value.

 gint imageAtomicCompSwap(gimage image​, IMAGE_COORDS, gint compare​, gint data​);

If the current value of image​ at the image coordinate is exactly equal to compare​, then data​ will be stored into the image at that location. Otherwise, the location will retain the original value.

While the function doesn't provide a direct way to tell if it actually wrote the value, it always returns the original value. So if you need to know, you can test the return value against compare​ yourself.

Atomic math

GLSL only provides a single math operation: additions:

 gint imageAtomicAdd(gimage image​, IMAGE_COORDS, gint data​);

This will read the value from the image, add data​ to it, and then write it to the image.

Obviously if you need subtract, simply negate data​. This will only work directly with signed integers. However, because GLSL 4.30 mandates two's complement, you can get the same effect with unsigned integers, since int(uint)​ and uint(int)​ conversion constructors will preserve the bit pattern.

So if you need unsigned integer subtraction, you can do this:

 imageAtomicAdd(..., uint(-data));

Obviously if this produces a negative value, you'll get a very positive value back instead, since it thinks you're doing unsigned math.

Atomic bitwise

GLSL provides 3 atomic bitwise operations: and, or, and xor:

 gint imageAtomicAnd(gimage image​, IMAGE_COORDS, gint data​);
 gint imageAtomicOr(gimage image​, IMAGE_COORDS, gint data​);
 gint imageAtomicXor(gimage image​, IMAGE_COORDS, gint data​);

These read the value from the image, perform the operation given data​, and writes the results back to the image.

Atomic minmax

GLSL provides a pair of functions that ensure that the value in the image is no larger than the given value or no smaller than it.

 gint imageAtomicMin(gimage image​, IMAGE_COORDS, gint data​);
 gint imageAtomicMax(gimage image​, IMAGE_COORDS, gint data​);

For the "Min" function, it will ensure that the image value is no smaller than data​. The "Max" function will ensure that the iamge value is no larger than data​.

Images in the context

The way to associate an image variable in GLSL works very similar to the way of associating samplers with textures.

For each shader stage, there is some number of available image units (not to be confused with texture image units). The number of image units can be queried per-stage, using GL_MAX_*_IMAGE_UNIFORMS, where * is filled in with the appropriate shader stage. Note that OpenGL 4.3 only requires Fragment Shaders and Compute Shaders to have non-zero numbers of image units; the minimum required in those cases is 8.

The total number of image units available is queried via GL_MAX_IMAGE_UNITS; this represents the total number of images you can bind at one time.

Just as with samplers, image variables reference image unit indices in the context. These are usually set with a binding layout qualifier, but they can also be set with glUniform1i or glProgramUniform1i.

After associating the image variable with its image unit, you then bind an image to the context. This is done via this function:

 void glBindImageTexture(GLuint unit​, GLuint texture​, GLint level​, GLboolean layered​, GLint layer​, GLenum access​, GLenum format​)

This binds an image from texture​ to the given image unit​, using the given mipmap level​ and array layer​.

Image bindings can be layered or non-layered, which is determined by layered​. If layered​ is GL_TRUE, then texture​ must be an Array Texture (of some type), a Cubemap Texture, or a 3D Texture. If a layered image is being bound, then the entire mipmap level specified by level​ is bound.

If the image is not layered, then the user must use the layer​ to select which array layer will be bound. If the texture does not have array layers, then this parameter must be 0. As with other functions, if this is a cubemap array texture, then layer​ is the layer-face to select.

If an array or cubemap texture is bound and is not layered, then the bound image is not an array or cubemap image. So if you bind a single array layer from a GL_TEXTURE_1D_ARRAY texture, it should be used with the image1D​ image variable type. Simiarly, layers from a 2D array texture, cubemap, 3D texture, or cubemap array should be image2D​, and a layer from a 2D multisample array should use image2DMS​.

The access​ specifies how the shader may access the image through this image unit. This can be GL_READ_ONLY, GL_WRITE_ONLY, or GL_READ_WRITE. If the shader violates this restriction, then all manor of bad things can happen, including program termination. It is a good idea to use memory qualifiers in the shader itself to catch this at shader compile-time.

The format​ parameter is an Image Format which defines the format that will be used for writes to the image. If a format qualifier is specified in the shader, this format must match it. The format must be [#Format compatibility|compatible with the texture's image format]]. The format​ parameter may only use formats from the following table:

Image Unit Format Format Qualifier Image Unit Format Format Qualifier
GL_RGBA32F rgba32f​ GL_RGBA32UI rgba32ui​
GL_RGBA16F rgba16f​ GL_RGBA16UI rgba16ui​
GL_RG32F rg32f​ GL_RGB10_A2UI rgb10_a2ui​
GL_RG16F rg16f​ GL_RGBA8UI rgba8ui​
GL_R11F_G11F_B10F r11f_g11f_b10f​ GL_RG32UI rg32ui​
GL_R32F r32f​ GL_RG16UI rg16ui​
GL_R16F r16f​ GL_RG8UI rg8ui​
GL_RGBA16 rgba16​ GL_R32UI r32ui​
GL_RGB10_A2 rgb10_a2​ GL_R16UI r16ui​
GL_RGBA8 rgba8​ GL_R8UI r8ui​
GL_RG16 rg16​ GL_RGBA32I rgba32i​
GL_RG8 rg8​ GL_RGBA16I rgba16i​
GL_R16 r16​ GL_RGBA8I rgba8i​
GL_R8 r8​ GL_RG32I rg32i​
GL_RGBA16_SNORM rgba16_snorm​ GL_RG16I rg16i​
GL_RGBA8_SNORM rgba8_snorm​ GL_RG8I rg8i​
GL_RG16_SNORM rg16_snorm​ GL_R32I r32i​
GL_RG8_SNORM rg8_snorm​ GL_R16I r16i​
GL_R16_SNORM r16_snorm​ GL_R8I r8i​


Also, note that these are the only image formats you can use for images in image load/store operations. You must use exactly these image formats and no others.

Format conversion

The Image Format of the image may be different from the format specified to the image binding function and in the shader. Values read and written are converted in the following way, assuming that the formats are compatible.

The term "source format" represents the image format of whatever the source of the operation is. Similarly, the "destination format" is the image format of whatever the destination of the operation is. Therefore:

All operations, whether read or write, function as though they were copying data from/to images with those formats. The first step of a write operation would be taking the value provided by the shader and writing that into a texture in the source image format. Then the format conversion takes place, copying the value into the destination. Similarly, the last step of read operations is reading from the destination image into the value in the shader.

Note: What follows is a description of how reading and writing appears to behave. Obviously, no hardware does it this way; this is just a convenient way of describing the behavior in terms of bits and bytes.

The conversion works based on memory copies using existing API functions. The source format values are read into memory as though calling glGetTexImage. The destination format values are written into their image as though they were uploaded via a call to one of the glTexSubImage​ functions.

Both of these functions take pixel transfer formats and types. The two effective calls will use format​s and type​s that exactly match the source/destination image format.

For example, if the source image format was GL_RGBA8UI, then the format​ and type​ passed to glGetTexImage would be GL_RGBA_INTEGER and GL_UNSIGNED_BYTE. If the destination image format for a copy is GL_RGB10_A2 (which may or may not be compatible with GL_RGBA8UI),

The destination format values are written into "memory", using values pulled from the source, as though written with one of the glTexSubImage​ calls. These calls again use pixel transfer formats and types that exactly match the destination format.

Note that (compatibility willing) we are perfectly capable of switching between floating-point and integral formats. However, converting between GL_R32F and GL_RGBA8 is not well-defined, in terms of the endian conversion. The reason is that GL_R32F will be read using a type of GL_FLOAT, but the writing will write 4 bytes in RGBA order. The R byte will be the first byte read from the GL_FLOAT, but the endian storage of GL_FLOAT is not defined. So the first byte may be the most significant or the least significant.

For any particular platform, you could assume a specific endian. But OpenGL itself provides no guarantees. If you want some guarantees on being able to play with the bits of a 32-bit floating-point texture, convert it to GL_R32UI instead and do your bit manipulation in the shader.

Format compatibility

The various image format compatibility matrix for image load/store operations is very similar to the compatiblity for texture views, though there are some differences. The first difference is that the list of image formats that can be used for images in load/store operations is more limited: only the formats mentioned above may be used.

Each of these formats has two properties: a size and a class. The size represents the bit-size of each texel. For example, GL_R32F has a size of 32; GL_RGBA32UI has a size of 128. The class represents the number of components and the bit-depth of each component. The class of GL_R32F is 1x32, while the class of GL_RGBA8 is 4x8.

The class for formats with oddball bitdepths (GL_R11F_G11F_B10F, for example) is the arrangement of components. So GL_R11F_G11F_B10F's class is 11/11/10, while GL_RGB10_A2UI's class is 10/10/10/2. This has a class match with GL_RGB10_A2.

If the texture was allocated by OpenGL (it is possible for OpenCL or other interop layers to allocate textures), then the only thing that matters for compatibility is overall texel size. So it is perfectly valid to map an GL_R32F image to an GL_RGBA8UI format and back, though again endian conversions may make this unusable in platform-neutral code.

If a texture was allocated from outside of OpenGL, then how compatibility is determined may not be by texel size; it may be by class. You must use glGetTexParameter with GL_IMAGE_FORMAT_COMPATIBILITY_TYPE to detect which. It will return either GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE or GL_IMAGE_FORMAT_COMPATIBILITY_BY_CLASS, specifying how compatibility is determined.

You can also detect this at the image format level with an image format query using the same parameter; this will be true for all (foreign) textures using that image format and texture type.

As an alternative to querying with foreign textures, you could just stick to formats that match on class. If the classes match, the sizes also match.

Memory coherency

Writes and atomic operations via image variables are not automatically coherent. Therefore, you must do things to ensure that writes have occurred before you can read those values.

Limitations