Sampler (GLSL)

From OpenGL Wiki
(Redirected from GLSL Samplers)
Jump to: navigation, search

A sampler is a set of GLSL variable types. Variables of one of the sampler types must be uniforms or as function parameters. Each sampler in a program represents a single texture of a particular texture type. The type of the sampler corresponds to the type of the texture that can be used by that sampler.

Sampler types

There are a number of sampler types. The various sampler types are separated into 3 categories, based on the basic data type of the Image Format of the texture that they sample from. These are floating-point, signed integer, and unsigned integer. Floating-point also covers normalized integer formats.

The name of the sampler type in GLSL reflects this grouping. The names are very similar to the names of the vector types in GLSL. Floating-point vectors do not have a prefix; they are just "vec". Signed integer vectors are "ivec", and unsigned integer vectors are "uvec".

So for samplers, floating-point samplers begin with "sampler". Signed integer samplers begin with "isampler", and unsigned integer samplers begin with "usampler". If you attempt to read from a sampler where the texture's Image Format doesn't match the sampler's basic format (usampler2D with a GL_R8I, or sampler1D with GL_R8UI, for example), all reads will produce undefined values.

Depth-component textures are treated as one-component floating-point textures. Stencil-component textures are treated as one-component unsigned integer textures.

For the sake of clarity, when you see a g preceding "sampler" in a sampler name, it represents any of the 3 possible prefixes (nothing for float, i for signed integer, and u for unsigned integer).

The rest of the sampler's name refers to the texture type that it is a sampler of. The names map as follows:

GLSL sampler OpenGL texture enum Texture type
gsampler1D GL_TEXTURE_1D 1D texture
gsampler2D GL_TEXTURE_2D 2D texture
gsampler3D GL_TEXTURE_3D 3D texture
gsamplerCube GL_TEXTURE_CUBE_MAP Cubemap Texture
gsampler2DRect GL_TEXTURE_RECTANGLE Rectangle Texture
gsampler1DArray GL_TEXTURE_1D_ARRAY 1D Array Texture
gsampler2DArray GL_TEXTURE_2D_ARRAY 2D Array Texture
gsamplerCubeArray GL_TEXTURE_CUBE_MAP_ARRAY Cubemap Array Texture
(requires GL 4.0 or ARB_texture_cube_map_array)
gsamplerBuffer GL_TEXTURE_BUFFER Buffer Texture
gsampler2DMS GL_TEXTURE_2D_MULTISAMPLE Multisample Texture
gsampler2DMSArray GL_TEXTURE_2D_MULTISAMPLE_ARRAY Multisample Array Texture

Shadow samplers

If a texture has a depth or depth-stencil image format and has the depth comparison activated, it cannot be used with a normal sampler. Attempting to do so results in undefined behavior. Such textures must be used with a shadow sampler. This type changes the texture lookup functions (see below), adding an additional component to the textures' usual texture coordinate vector. This extra value is used to compare against the value sampled from the texture.

Because cubemap arrays normally take 4D texture coordinates, the texture lookup function overloads that take a cubemap array take an additional parameter, instead of expanding the vector texture coordinate size.

The result of accessing a shadow texture is always a single float value. This value is on the range [0, 1], which is proportional to the number of samples in the shadow texture that pass the comparison. Therefore, if the resulting value is 0.25, then only 1 out of the 4 values sampled by the comparison operation passed.

Notice that none of these types have the g prefix. This is because shadow samplers can only be used with textures with depth components. And those are all floating-point (actual floats or unsigned Normalized Integers) image formats. Furthermore, the result of the comparison is always a single float value, since depth formats only provide one component of data.

GLSL sampler OpenGL texture enum
sampler1DShadow GL_TEXTURE_1D
sampler2DShadow GL_TEXTURE_2D
samplerCubeShadow GL_TEXTURE_CUBE_MAP
sampler1DArrayShadow GL_TEXTURE_1D_ARRAY
sampler2DArrayShadow GL_TEXTURE_2D_ARRAY
samplerCubeArrayShadow GL_TEXTURE_CUBE_MAP_ARRAY

Language Definition

A variable of sampler can only be defined in one of two ways. It can be defined as a function parameter or as a uniform variable.

uniform sampler2D texture1;

void Function(in sampler2D myTexture);

Samplers do not have a value. They can not be set by expressions and the only expression they can be used in is as the direct argument to a function call which takes an in sampler of that type.

Texture lookup functions

The only place where you can use a sampler is in one of the GLSL standard library's texture lookup functions. These functions access the texture referred to by the sampler. They take a texture coordinate as parameters.

There are several kinds of texture functions. Some texture functions do not take certain kinds of samplers. Some of them are not available on all shader stages.

The use of g has the same meaning as previously. Types in bold face represent a range of types, as explained in the description beneath the function.

Texture coordinates

Texture coordinates may be normalized or in texel space. A normalized texture coordinate means that the size of the texture maps to the coordinates on the range [0, 1] in each dimension. This allows the texture coordinate to be independent of any particular texture's size. A texel-space texture coordinate means that the coordinates are on the range [0, size], where size​ is the size of the texture in that dimension.

Rectangle Textures always take texture coordinates in texel space. Unless otherwise noted, all other texture coordinates will be normalized.

Components of a texture coordinate that reference an array layer are not normalized to the number of layers. They specify a layer by index.

Texture lookup in shader stages

Texture accessing is not limited to just Fragment Shaders, though this is the primary place where textures are accessed. Any GLSL shader stage may access textures (and OpenGL does not define any limitations on the format for those textures). However, non-fragment shader stages have certain limitations.

Mipmapping works based off of the angle and size of the rendered primitive relative to the window and its texture mapping. This is computed internally by taking the derivative of the texture coordinate passed to the texture sampling function. However, this is only possible within a Fragment Shader. In any other shader stage, there is no rendered primitive yet; there may be a vertex or a primitive, but the space of the primitive relative to the window is unknown. So derivatives cannot be implicitly computed.

Because of this, non-fragment shader stages cannot use any texture sampling function that requires implicit derivatives. The description of the functions below will state if they need implicit derivatives.

The result of using any of these functions functions outside of a fragment shader and on a mipmapped texture (aka: the texture's minimum mipmap level is greater than the base level and mipmap filtering is active) is undefined.

Also, some of the following functions allow for an optional bias​ parameter. This optional parameter is only available in the fragment shader.

Non-uniform flow control

In Fragment Shaders, there is one other circumstance that can remove implicit derivatives: non-uniform flow control.

Uniform flow control for a particular location in code means that, no matter how a shader is executed, every invocation will follow the same path to get to that location of code. Consider the following GLSL code for a fragment shader:

flat in int parameterValue;

void main()
  vec4 firstData = texture(someSampler, textureCoords);

  if(parameterValue < 20)
      firstData += texture(someOtherSampler, otherTexCoords);

  vec4 moreData = texture(thirdSampler, thirdTexCoords);

The first texture access happens in uniform flow control. Thus, the texture access produces definite results. However, the access to someOtherSampler is not in uniform flow. This is because the condition is based on an input value to the fragment shader; if parameterValue were a uniform instead of an input, then it would be in uniform flow.

Uniform control flow is associated with Dynamically Uniform Expressions. Control flow where the condition is based only on dynamically uniform expressions will always be uniform. If the condition is not dynamically uniform, then control flow will not be dynamically uniform. If using OpenGL versions that do not define dynamically uniform expressions, then the expression must be a uniform, a constant value, or an expression based on these.

Note: SPIR-V provides has a more generous definition of uniform control flow. Normally, what is considered dynamically uniform is an expression which has the same value in all invocations within a rendering command. However, for the purposes of defining when control flow is uniform, dynamically uniform matters within a Primitive being rendered, which is a much smaller scope. This would mean that the gl_PrimitiveID input variable would be considered uniform for the purposes of determining uniform control flow, even though it is not normally dynamically uniform. Note that the GLSL 4.50 specification does not state this. However, hardware that can run OpenGL 4.5 can also run Vulkan, and therefore it may use the more generous definition

If the texture associated with someOtherSampler uses mipmapping or anisotropic filtering of any kind, then any texture function that requires implicit derivatives will retrieve undefined results. The third texture access happens in uniform flow control again and will produce definite results.

Note: The GLSL compiler will not give you an error for this. It is perfectly legal GLSL code, and it only produces undefined behavior based on the texture and sampler objects associated with someOtherSampler.

There are two ways to solve this. The simplest is to restructure the code to always do the second texture access, but only add it based on the condition:

void main()
  vec4 firstData = texture(someSampler, textureCoords);

  vec4 addingData = texture(someOtherSampler, otherTexCoords);
  vec4 moreData = texture(thirdSampler, thirdTexCoords);

  if(parameterValue < 20)
      firstData += addingData;

The other alternative, which must be used in cases that are not so simple to resolve, is to get gradients for each pair of texture coordinates the texture coordinates with the dFdx and dFdy functions. Then use those gradients when fetching the textures.

void main()
  vec4 firstData = texture(someSampler, textureCoords);

  vec2 otherTexDx = dFdx(otherTexCoords);
  vec2 otherTexDy = dFdy(otherTexCoords);
  vec2 thirdTexDx = dFdx(thirdTexCoords);
  vec2 thirdTexDy = dFdy(thirdTexCoords);

  if(parameterValue < 20)
      firstData += textureGrad(someOtherSampler, otherTexCoords, otherTexDx, otherTexDy);

  vec4 moreData = textureGrad(thirdSampler, thirdTexCoords, thirdTexDx, thirdTexDy);

It is important to get the gradients before going into non-uniform flow code. The dFdx and dFdy become just as useless as the texture calls within non-uniform flow control code.

Texture size retrieval

The size of a texture can be retrieved by calling this function:

 ivec textureSize(gsampler sampler​, int lod​);

This function retrieves the size of the given LOD of the texture bound to sampler​. The sampler can be of any type. For sampler types that refer to textures without mipmaps (eg: samplerRect), no lod​ value is needed.

The dimensionality of the return value, ivec, depends on the type of sampler​. For images that are 1-dimensional, the function returns an int. For 2D images, the function returns ivec2, and for 3D ivec3. The array types return one extra coordinate (eg: 1D arrays return ivec2), with the last coordinate being the number of images in the array of images.

This function does not require implicit derivatives.

Texture mipmap retrieval

Texture Query Levels
Core in version 4.6
Core since version 4.3
Core ARB extension ARB_texture_query_levels

It is often useful to fetch the number of mipmap levels that are available in the particular texture associated with a sampler. This function can be used to do that:

 int textureQueryLevels(gsampler sampler​);

Texture sampling functions like textureLod which take an explicit mipmap level will clamp their inputs to the range [0, X), where X is the return value from this function.

Sampler types that forbid mipmaps (rectangle, buffer, etc), multisample samplers, and shadow cubemap array samplers cannot be used with this function.

This function does not require implicit derivatives.

Basic texture access

To sample the texture with normalized texture coordinates, this function is used:

 gvec texture(gsampler sampler​, vec texCoord​[, float bias​]);

This samples the texture given by sampler​, at the location texCoord​, with an optional LOD bias value of bias​. For sampler types that cannot have LODs, the bias​ parameter cannot be used.

Sampling from shadow samplers return a "float", representing the result of the comparison. Sampling from other kinds of samplers returns a gvec4, matching the type of gsampler. This function does not work with multisample or buffer samplers.

The size of the vec type of texCoord​ depends on the dimensionality of sampler​. A 1D sampler takes a "float", 2D samplers take "vec2", etc. Array samplers add one additional coordinate for the array level to sample. Shadow samplers add one additional coordinate for the sampler comparison value. Array and shadow samplers add two coordinates: the array level followed by the comparison value. So vec when used with a sampler1DArrayShadow is a "vec3".

This function requires implicit derivatives.

Offset texture access

You can add a texel offset to texture coordinates sampled with texture functions. This is useful for sampling from a collection of images all on one texture. This is done with this function:

 gvec textureOffset(gsampler sampler​, vec texCoord​, ivec offset​[, float bias​]);

The texCoord​ will have its value offset by offset​ texels before performing the lookup. Cubemap, multisample, and buffer samplers are not allowed as types for gsampler.

The dimensionality for offset​ depends on the sampler's type. For non-array textures, the dimensionality is the same as for the texture coordinate. So sampling from a 1D sampler, texCoord​ will be a float, while offset​ will be an int. For array textures, the dimensionality of offset​ is that of the non-array form of that texture. So for a 2D array texture, while texCoord​ will be a vec3, offset​ will be a vec2

The value of offset​ must be a Constant Expression.

There are minimum and maximum values for the coordinates of offset​. These are queried through GL_MIN_PROGRAM_TEXEL_OFFSET and GL_MAX_PROGRAM_TEXEL_OFFSET.

This function requires implicit derivatives.

Projective texture access

Projecting a texture onto a surface is often a useful technique. To perform this kind of texture access, this function is available:

 gvec textureProj(gsampler sampler​, vec projTexCoord​[, float bias​]);

The difference between this function and texture is that vec is one component larger than it would be for texture. The previous components are divided by the last components, and this value is used to access the texture.

Array, cubemap, multisample, and buffer samplers cannot be used with this function.

An important note on shadow samplers. The comparison value is still part of the texture coordinate, stored in the next to last component. Because it is part of the texture coordinate, it is also divided by the last coordinate before the comparison. This means that you must pre-multiply the actual value by the projection value.

This function requires implicit derivatives.

Lod texture access

If you want to compute the mipmap LOD parameter entirely on your own (instead of biasing the pre-computed LOD), you may use this function:

 gvec textureLod(gsampler sampler​, vec texCoord​, float lod​);

This will sample the texture at the mipmap LOD lod​. 0 means the base level (as set by the appropriate texture parameter). Sampler types that forbid mipmaps (rectangle, buffer, etc), multisample samplers, and shadow cubemap array samplers cannot be used with texture LOD functions.

Mipmap filtering can still be used by selecting fractional levels. A level of 0.5 would be 50% of mipmap 0 and 50% of mipmap 1.

Note that gradients when using Lod functions (see textureGrad below) are effectively zero. So anisotropic filtering is useless when using this function.

This function does not require implicit derivatives, since it samples explicitly from the given mipmap level(s).

Gradient texture access

Being able to bias the mipmap LOD or specify the specific mipmap to use is useful, but it is also useful to change the mipmap lod computation in more subtle ways. This function allows you to specify the two gradients for how the texture coordinates change locally:

 gvec textureGrad(gsampler sampler​, vec texCoord​, gradvec dTdx​, gradvec dTdy​);

The type of gradvec​ is a float-vector with a number of components equal to the dimensionality of the sampler type. A 1D sampler uses a "float", etc. Note that shadow sampler types do not add an additional coordinate to gradvec​, so a sampler2DShadow is still "vec2".

This function works for sampler types that are not multisample, buffer texture, or cubemap array samplers, including those that do not have mipmaps (like sampler2DRect).

The values of dTdx​ and dTdy​ are vectors that represent the change of each texture coordinate per pixel of the window's X and Y coordinates.

This function does not require implicit derivatives, since it provides them.

Texture gather accesses

Texture gather
Core in version 4.6
Core since version 4.0
ARB extension ARB_texture_gather

While OpenGL's standard filtering abilities are useful, sometimes it is useful to be able to bypass filtering altogether. In order to do that for textures that are two-dimensional, you must fetch values from the 4 texels nearest to a given texture coordinate. OpenGL provides the following functions to do this.

 gvec4 textureGather(gsampler sampler​​, vec texCoord​​, int comp​);
 gvec4 textureGatherOffset(gsampler sampler​​, vec texCoord​​, ivec2 offset​, int comp​);
 gvec4 textureGatherOffsets(gsampler sampler​​, vec texCoord​​, ivec2 offsets​[4], int comp​);

These functions only fetch a single component from the texture, specified by comp​, which defaults to 0. All filtering is ignored for these functions, and they only access texels from the texture's base mipmap layer.

These functions fetch just one component at a time, but they return 4 values, as the components of a 4-element vector. These values represent the nearest four texels, in the following order:

  • X = top-left
  • Y = top-right
  • Z = bottom-right
  • W = bottom-left

For shadow sampler fetches, the functions do not take the comp​ parameter. Their texture coordinates are also not expanded to include the reference value, the way they usually are for shadow samplers.

The 4 return values for shadow samplers are the result of the comparison for each texel location.

The offset versions apply a pixel offset to the texture coordinate before doing the fetch, in the same way as the standard offset texture functions. The textureGatherOffsets is different, in that each of the four texel fetch locations has its own offset, defined by the offsets array.

The sampler types taken for the non-offset textureGather calls are non-multisample texture types which are not 1D or 3D. For the offset gather functions, only 2D, 2D array, and rectangle samplers (along with the shadow variations).

This function does not require implicit derivatives, as it only samples from the base mipmap level.

Mixed texture accesses

There are texture functions for different combination of the above. These are:

  • textureProjOffset
  • textureProjLod
  • textureProjLodOffset
  • textureProjGrad
  • textureProjGradOffset
  • textureLodOffset
  • textureGradOffset

The variations that add parameters to the functions, everything except "Proj", add them in the order that the names appear in the function. So textureProjLodOffset has this signature:

 gvec textureProjLodOffset(gsampler sampler​, vec projTexCoord​, float lod​, ivec offset​);

The "Grad" and "Lod" functions do not have a bias​ parameter.

The accepted sampler types is the intersection of all sampler types that each function accepts. "Proj" cannot take cubemaps and "Lod" cannot take rectangles, so textureProjLod cannot take cubemaps or rectangles.

The "Grad" and "Lod" versions of functions do not require implicit derivatives. If either "Grad" or "Lod" is not present, then the function does require implicit derivatives.

Direct texel fetches

The above texture functions all use filtering and normalized texture coordinates (except when using rectangle samplers). It is occassionally useful to forgo this and directly access a texel value with non-normalized coordinates. This is done through one of these functions:

 gvec texelFetch(gsampler sampler​, ivec texCoord​[, int lod​] [, int sample​]);
 gvec texelFetchOffset(gsampler sampler​, ivec texCoord​, int lod​, ivec offset​);

The texCoord​ parameter is an unnormalized texture coordinate. The lod​ specifies which mipmap to sample from; if the sampler type does not have mipmaps, this parameter will not be present. The offset version applies an integer texel offset to the texture coordinate before doing the texture access. As before, the offset​ must be a constant expression. The sample​ specifies the sample number to fetch from for multisample sampler types.

texelFetch is the only texturing function (besides textureSize) that takes multisample and buffer samplers. When using multisample samplers, the user must pass a sample​ parameter. This tells the system which sample to retrieve.

texelFetch does not take cubemap, cubemap array, or shadow samplers of any kind. texelFetchOffset forbids everything except 1D, 2D, 3D, rectangle, 1D, and 2D arrays.

This function does not require implicit derivatives, as it only samples from the given mipmap level.

Binding textures to samplers

Uniforms of sampler types are used in GLSL to represent a texture of a particular kind. Therefore, sampler types represent textures. The way a program is associated with textures is somewhat unintuitive. The mapping is done with the rendering context.

The OpenGL rendering context has multiple independent bind locations for textures called texture image units. To bind a texture to a particular texture image unit, you use the following functions:

void glActiveTexture(GLenum textureUnit​);

void glBindTexture(GLenum target​, GLuint texture​);

void glBindTextureUnit(GLuint unit​, GLuint texture​); //Requires GL 4.5 or ARB_direct_state_access

The context has the concept of a current texture image unit; glActiveTexture sets the currently active texture image unit. The bind locations for this function are enumerators named GL_TEXTURE0, GL_TEXTURE1, etc. Alternatively, you can use bind locations of the form GL_TEXTURE0 + i, where i​ is a texture unit number between 0 and the implementation-defined limit GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS.

Calls to glBindTexture will bind the texture​ to the target​ binding point in the current texture image unit. So to bind a texture to a specific image unit, you use both functions.

If you have OpenGL 4.5 or ARB_direct_state_access, you can call glBindTextureUnit, which binds the texture to the specified texture unit​ (an integer index, not an enum), using its natural texture target. With this function, if texture​ is 0, then all targets for that unit are unbound.

Sampler Objects (no direct relation to samplers in GLSL) can be bound to texture image units as well:

void glBindSampler(GLuint unit​, GLuint sampler​);

Here, unit​ is an integer, not an enum the way it is for glActiveTexture.

Sampler uniform variables are like regular Uniforms in that they you set "values" into them. The value you provide to a sampler uniform is the texture image unit to which you will bind the texture that the sampler will access. This value is a texture image unit index, not the texture object itself and not an OpenGL enumerator.

The value of sampler variables is set using glUniform1i or glUniform1iv (for arrays of samplers). You may also use the glProgramUniform equivalent if OpenGL 4.2 or ARB_separate_shader_objects is available.

V · E

This example shows how to associate textures with shader variables when rendering.

//Initial program setup.
glLinkProgram(program); //Initial link

GLint baseImageLoc = glGetUniformLocation(program, "baseImage");
GLint normalMapLoc = glGetUniformLocation(program, "normalMap");
GLint shadowMapLoc = glGetUniformLocation(program, "shadowMap");

glUniform1i(baseImageLoc, 0); //Texture unit 0 is for base images.
glUniform1i(normalMapLoc, 2); //Texture unit 2 is for normal maps.
glUniform1i(shadowMapLoc, 4); //Texture unit 4 is for shadow maps.

//When rendering an objectwith this program.
glActiveTexture(GL_TEXTURE0 + 0);
glBindTexture(GL_TEXTURE_2D, object1BaseImage);
glBindSampler(0, linearFiltering);
glActiveTexture(GL_TEXTURE0 + 2);
glBindTexture(GL_TEXTURE_2D, object1NormalMap);
glBindSampler(2, linearFiltering); //Same filtering as before
glActiveTexture(GL_TEXTURE0 + 4);
glBindTexture(GL_TEXTURE_2D, shadowMap);
glBindSampler(4, depthComparison); //Special sampler for depth comparisons.

//Render stuff

//Render another object with some different textures.
glActiveTexture(GL_TEXTURE0 + 0);
glBindTexture(GL_TEXTURE_2D, object2BaseImage); //Use the same sampler as before.
glActiveTexture(GL_TEXTURE0 + 2);
glBindTexture(GL_TEXTURE_2D, object2NormalMap); //Use the same sampler as before.

//Use the same shadow map, so no need to unbind/bind.
//Render stuff

Notice that the shadow map does not need to be changed here. This helps minimize the number of state changes. Your program could even have a global convention that texture image unit 4 is always used for shadow maps. Since all objects used the same shadow map, you can bind the shadow map before you start rendering anything, and you only have to bind it once per frame.

If a program doesn't use certain texture image units, it is still fine to have a texture bound to them. They will not affect the rendering in any way.

Version 4.20 binding

In-Shader Specification
Core in version 4.6
Core since version 4.2
Core ARB extension ARB_shading_language_420pack

Sampler uniform variables have to be associated with a texture unit before they can be used. Normally, you do this by setting the "value" of their uniform to the texture image unit. However, you can also set them directly within the shader:

#version 420
//#extension GL_ARB_shading_language_420pack: enable  Use for GLSL versions before 420.

layout(binding=0) uniform sampler2D diffuseTex;

This causes the initial value for the diffuseTex uniform to be the texture image unit 0. You may later change the binding as shown above. The binding​ value must be a Constant Expression.

Multibind and textures

Core in version 4.6
Core since version 4.4
ARB extension ARB_multi_bind

A range of textures and sampler objects can be bound to a range of texture image units with a pair of function calls:

void glBindTextures(GLuint first​, GLsizei count​, const GLuint *textures​)

void glBindSamplers(GLuint first​, GLsizei count​, const GLuint *samplers​);

first​ is the initial texture image unit to start binding the array to. count​ is the number of texture image units that will be bound to. Thus, the texture image units that will be changed are those on the half-open range, [first​​, first​​ + count​).

For glBindSamplers, the function will access the samplers​ array and bind each sampler object in the array to the corresponding texture image unit, as though from a call to glBindSampler.

However, glBindTextures is not the same as a sequence of glActiveTexture and glBindTexture calls. Things are more complex for textures.

The problem is with the way texture image units work. Recall that glBindTexture takes a target​ parameter, even though the texture object knows which target that must be used with (and it will error if you use the wrong one). This is because each texture image unit can actually have multiple textures bound to it. One of each texture type can be bound to the same texture image unit (though you are strongly encouraged to avoid this).

If a texture name in the array is 0, then all texture targets for that texture unit will be unbound, as if you had called glBindTexture for every texture target. If a texture name is non-zero, then it will be bound to its target, and the other targets in that texture unit will be left undistrubed.

Also, the current "active texture image unit", which is set by glActiveTexture, will not be disturbed by this function.

If textures​ or samplers​ is NULL, then these functions will behave as though an array of count​ objects was passed, where each object in that array was zero. So passing NULL means to reset all bindings to texture image units/samplers in the given range to zero.

See also

  • Bindless Texture: An extension that provides a way to send textures (and images to shaders by integers rather than binding them to texture units.

External links