Texture Storage

From OpenGL.org
Revision as of 01:47, 9 October 2012 by Alfonse (Talk | contribs) (Texture views)

Jump to: navigation, search

Texture objects contain 3 kinds of information. One of these kinds of information is Texture Storage: the actual pixel data stored in the texture. This page describes the many ways to create storage for textures, modify the contents of storage, and otherwise manipulate the storage of a texture object.


There are three kinds of storage for textures: mutable storage, immutable storage, and buffer storage. Only Buffer Textures can use buffer storage, where the texture gets its storage from a Buffer Object. And similarly, buffer textures cannot use mutable or immutable storage. As such, buffer storage is a special case. Any other kind of texture can use either mutable or immutable storage.

The difference between mutable storage and immutable storage is this: immutable storage allocates all of the images for the texture all at once. Every mipmap level, array layers, cubemap face, etc is all allocated with a single call, giving it a specific Image Format. It is called "immutable" because one the storage is allocated, the storage cannot be changed. The texture can be deleted as normal, but the storage cannot be altered. A 256x256 2D texture with 5 mipmap layers that uses the GL_RGBA8 image format will *always* be a 256x256 2D texture with 5 mipmap layers that uses the GL_RGBA8 image format.

Note that what immutable storage refers to is the allocation of the memory, not the contents of that memory. You can upload different pixel data to immutable storage all you want. You simply can't change a 256x256 texture into a 1024x1024 texture the way you can with mutable storage.

Recommendation: If your implementation supports creating textures with immutable storage, you should use it wherever possible. It will save you from innumerable mistakes.

Immutable storage

Immutable Storage
Core in version 4.5
Core since version 4.2, 4.3
Core ARB extension ARB_texture_storage, ARB_texture_storage_multisample

Allocating immutable storage for a texture requires binding the texture to its target, then calling a function of the form glTexStorage*​. Which function you call depends on which texture type you are trying to allocate storage for. Each function only works on a specific set of targets.

 void glTexStorage1D( GLenum target​, GLint levels​, GLint internalformat​, GLsizei width​ );
Valid target​: GL_TEXTURE_1D
 void glTexStorage2D( GLenum target​, GLint levels​, GLint internalformat​, GLsizei width​, GLsizei height​ );
For 1D array textures, the number of 1D images that each mipmap level has is the height​ value.
 void glTexStorage3D( GLenum target​, GLint levels​, GLint internalformat​, GLsizei width​, GLsizei height​, GLsizei depth​ );
Valid target​: GL_TEXTURE_3D, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_CUBE_ARRAY (this requires GL 4.0 or ARB_texture_cube_map_array)
For 2D array textures, the number of 2D images that each mipmap level has is the depth​ value.
For 2D cubemap array textures, the number of cubemap layer-faces is the depth​, which must be a multiple of 6. Therefore, the number of individual cubemaps in the array is given by depth​ / 6.
 void glTexStorage2DMultisample( GLenum target​, GLsizei samples​​, GLint internalformat​, GLsizei width​, GLsizei height​, GLboolean fixedsamplelocations​​ );
 void glTexStorage3DMultisample( GLenum target​, GLsizei samples​​, GLint internalformat​, GLsizei width​, GLsizei height​, GLsizei depth​, GLboolean fixedsamplelocations​​ );

These functions allocate images with the given size (width​, height​, and depth​, where appropriate), with the number of mipmaps given by levels​. The storage is created here, but the contents of that storage is undefined. It's a lot like calling malloc​; you get memory, but there's nothing in it yet.

The internalformat​ parameter defines the Image Format to use for the texture. For the most part, any texture type can use any image format. Including the compressed formats. Note that these functions explicitly require the use of sized image formats. So GL_RGBA is not sufficient; you have to ask for a size, like GL_RGBA8.

For the multisample functions, samples​ defines the number of samples that will be used per-texel in the texture. If you set fixedsamplelocations​ is GL_TRUE, then the following is assured:

  1. The texels in the image will all use the same sample locations.
  2. The texels in the image will all use the same number of sample locations (normally, the implementation could give some texels fewer than samples​, while other texels get more).
  3. All textures with fixed sample locations will use the same set of sample locations, regardless of Image Format.
Note: The glTexStorage*​ functions again create immutable storage. This means that you cannot call them twice for the same texture object. Once you have given a texture object immutable storage, the only way to undo that is to delete the texture.

Texture views

Texture View
Core in version 4.5
Core since version 4.3
Core ARB extension ARB_texture_view

Besides the infinitely cleaner texture specification syntax and the general reduction in the chance for mistakes, creating immutable storage for textures has one other advantage: immutable storage can be shared between texture objects.

Mutable storage is bound to a single texture object. Immutable storage can be shared among several objects, such that they are all referring to the same memory. Think of it like passing a reference-counted smart pointer around. Each object has its own smart pointer, and the memory doesn't go away until all objects that reference the shared memory are destroyed.

The glTexStorage*​ functions all create new immutable storage, ala malloc​. In order to share previously-created immutable storage, we must use a different function:

void glTextureView(GLuint texture​, GLenum target​, GLuint origtexture​, GLenum internalformat​, GLuint minlevel​, GLuint numlevels​, GLuint minlayer​, GLuint numlayers​)

This function takes two textures. origtexture​ is the texture that currently has immutable storage. texture​ is a new texture that doesn't have immutable storage. target​ is the type of texture​. Then this function completes, it will now share storage with origtexture​. This is called a "view texture", because the new texture represents a "view" into the original texture's storage.

The view texture does not have to look at the exact same size of storage. It can reference only a portion of the original texture. For example, if you have an immutable texture with 6 mipmap levels, you can create a view that only uses 3 mipmap levels.

This is the responsibility of the minlevel​ and numlevels​ parameters. The minlevel​ specifies the mipmap level in the origtexture​ that will become the base level of the view texture. numlevels​ specifies how many mipmaps are to be viewed. If the origtexture​ is not a texture type that has mipmaps (multisample or rectangle textures), then minlevel​ must be 0 and numlevels​ must be 1. For textures that could have mipmaps, then minlevel​ and numlevels​ will be clamped to the actual available number of mipmaps in the source texture (though it is an error if minlevel​ is outside of the range of mipmaps).

For textures that have layers (GL_TEXTURE_1D_ARRAY, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_CUBE_MAP, or GL_TEXTURE_CUBE_MAP_ARRAY), a range of layers to take can be specified with minlayer​ and numlayers​. As with the mipmap level range parameters, the layer ranges are clamped to the available range of layers, and minlayer​ must be an available layer in the image.

There are two special tricks you can do with view textures. The texture type of origtexture​ does not have to match the target​. For example, you can have a 1D array texture and create a view of it as a 1D texture, which represents a specific array layer of the texture. To do this, you must use minlayer​ to define the layer you want to select, and pass 1 to numlayers​.

You can only perform this kind of conversion between very specific sets of texture types. Here is a table defining where the conversion is allowed:

Original Target Compatible New Targets
GL_TEXTURE_BUFFER none. Cannot be used with this function.

The number of mipmaps levels and array layers you fetch are not allowed to violate the constraints of the destination target​. So if you want to get a rectangle texture view of a 2D texture, you must pick exactly one mipmap level.

The other trick you can do with view textures is change the Image Format. internalformat​ is not restricted to the exact image format that origtexture​ uses. It simply must be compatible with it. Here is a chart explaining which formats are compatible with which other formats:

Class Internal Formats
96-bit GL_RGB32F, GL_RGB32UI, GL_RGB32I
16-bit GL_R16F, GL_RG8UI, GL_R16UI, GL_RG8I, GL_R16I, GL_RG8, GL_R16, GL_RG8_SNORM, GL_R16_SNORM
8-bit GL_R8UI, GL_R8I, GL_R8, GL_R8_SNORM
S3TC texture view compatibility
Class Internal formats

Any formats not on this chart are only compatible with themselves; you cannot create a view with a different format.

Because view textures reference immutable storage, this also means that view textures can be used as origtexture​. So you can create a view of a view.

The mipmap levels, number and indices of layers, base level texture size, and similar parameters for a view are defined by the particular view, not the original block of storage. As an example, let's say we create a 2D array texture with immutable storage as follows:

glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 10, GL_RGBA8, 1024, 1024, 6);

We can create a view of tex​:

glTextureView(texView1, GL_TEXTURE_2D_ARRAY, tex, GL_RGBA8, 2, 5, 1, 3);

texView1​ is a 2D array texture. As far as texView1​ is concerned, the size of its base level is 256x256, because it starts with the third mipmap of tex​. It has only 5 mipmaps. And though it is an array, it has only 3 array layers.

We can create view from the new texture:

glTextureView(texView2, GL_TEXTURE_2D, texView1, GL_RGBA8, 2, 1, 1, 1);

texView2​ is a 2D texture. It has 1 mipmap level, and the size of that level is 64x64, because it picked the third mipmap from texView1​. It has 1 array layer, which is taken from the second layer in texView1​.

What is texView2​ in relation to tex​? Exactly what it sounds like. texView2​ takes the fifth mipmap level and the third array layer from tex​.

A view texture cannot view more mipmap levels and/or array layers than the origtexture​ advertises, even if the original texture is a view texture and those extra levels/layers exist. Views can only view the same information that the original does or a subset of it. If you want to view more of the storage, you need to use a texture that can access that storage.

Thus, it is technically possible to completely lose access to some levels/layers of a texture, if you delete the original texture created with glTexStorage*​.

Mutable storage

OpenGL functions of the form gl*TexImage*​ are used to create mutable storage for images within a texture. Calling any of these on a texture that had immutable storage created for it is an error.

The immutable storage calls are the equivalent of a C malloc​: they allocate memory, but they don't put anything in it. All of the mutable storage calls are capable of both allocating memory and transferring pixel data into that memory.

Texture completeness

These functions allocate one mipmap layer of the texture at a time (and in some cases, only part of a mipmap layer at a time). As such, if you intend to use mipmap filtering, you must allocate all of the mipmap layers you intend to use, then set texture parameters that confine the mipmap range to the range you have allocated. Only after you have done this can you actually read from the texture in a shader.

Also, you will notice that you can technically allocate different mipmap levels with different internal format. Yes, you can do that, but you shouldn't. Each mipmap layer should use the exact same internal format.

Failing to follow the above results in a texture that is not "complete" by the rules of the standard. You cannot attempt to sample from such a texture, and it is a very good idea to make sure that a texture is always complete after creation. Make it complete initially, and leave it that way. That's one of the reasons why immutable storage is nice: such textures are always complete.

Note: This is a very abbreviated discussion of texture completeness rules. It is possible to not set the mipmap range and still allow the texture to be complete by turning off mipmap sampling or using a Sampler Object that doesn't do mipmap-based sampling. However, it is never wrong to set the mipmap range correctly, and it is a good idea to get into the habit of always creating textures that are complete rather than not doing so.

Direct creation

There are several ways to allocate mutable storage; the differences are based on where they get their pixel data from. Since mutable storage creation also uploads data, there are many different places the user can get data from.

The only difference between these groups of functions is where they get the pixel data to initialize their images from.

The most direct method performs a regular Pixel Transfer operation from either client memory or a buffer object. These are the functions that allocate and upload in this way:

 void glTexImage1D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, void *data );
 void glTexImage2D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, void *data );
 void glTexImage3D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, void *data );
 void glTexImage2DMultisample( GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations );
 void glTexImage3DMultisample( GLenum target, GLsizei samples, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations );

These functions allocate an image from the texture bound to target​ (note: cubemaps use the target very differently). The level​ parameter specifies the mipmap level for the image to be allocated. You can only allocate one mipmap level at a time.

With the exception of cubemaps, target​s for these functions work as they did for the analogous immutable storage functions. So glTexImage2D is used to allocate a mipmap level of a 1D array texture, where the height​ is the number of elements in the array. Multisample texture types must use the multisample allocation functions. And so forth.

Some of these functions have a border​ parameter. This was old functionality that is no longer supported (and really, never was); always set it to 0.

Warning: Do not forget to make sure that all mipmaps and images in the same texture are allocated with the same internalformat​.

The format​, type​, and data​ parameters are used for performing a Pixel Transfer operation. This allows one to create a texture and fill it with some data in one call. As with any pixel transfer operation, Pixel Buffer Objects can be used to feed OpenGL the data.

You do not need to fill in the texture's data in the same call that you create it in. If data​ is NULL, no pixel transfer will be done, and the texture's data is undefined.

Note: Even if data is NULL, the format and type fields must be legal fields, or the entire call will fail with a GL_INVALID_ENUM error.

The multisample versions of these functions do not offer pixel transfer. This is because the image data of multisample textures cannot be updated from client data. It can only be filled in as a render target, and it can only be sourced as a texture from GLSL.

Compressed format creation

Textures that use compressed image formats need special care. It is perfectly legal to pass an appropriate compressed format to any of the prior functions (except for the multisample ones). However, the Pixel Transfer parameters pose a problem. They are designed for regular image data where each pixel is specified individually.

Most compressed formats store pixels in specially formatted blocks. As such, you cannot perform a direct pixel transfer of previously compressed data. If you use a compressed internalformat​ with a regular pixel transfer call, you are telling OpenGL to take uncompressed data and compress it manually.

There are a number of special functions for allocating images with compressed formats and simultaneously filling them with compressed data:

 void glCompressedTexImage1D( GLenum target​, GLint level​, GLenum internalformat​, GLsizei width​, GLint border​, GLsizei imageSize​, void *data​ );
 void glCompressedTexImage2D( GLenum target​, GLint level​, GLenum internalformat​, GLsizei width​, GLsizei height​, GLint border​, GLsizei imageSize​, void *data​ );
 void glCompressedTexImage3D( GLenum target​, GLint level​, GLenum internalformat​, GLsizei width​, GLsizei height​, GLsizei depth​, GLint border​, GLsizei imageSize​, void *data​ );

With the exception of the last two parameters, these functions work identically to their glTexImage*​ counterparts. They allocate mutable storage of the given size for the texture bound to the given target.

Where they differ is in how they transfer pixel data. OpenGL assumes that the data you are passing has been properly formatted according to whatever internalformat​ the image uses. Therefore, it is just going to copy the data verbatim from your data​. The imageSize​ must match with what OpenGL would compute based on the dimensions of the image and the internalformat​. If it doesn't, you get an GL_INVALID_VALUE error.

Again, Pixel Buffer Objects work with such transfers. The data​ parameter must thus be a byte-offset from the front of the buffer bound to GL_UNPACK_BUFFER.

Warning: data​ cannot be NULL here; if you didn't want to transfer pixel data, you should have used glTexImage*​.

internalformat​ must not be a generic compressed format. It must be a specific compressed format (such as GL_COMPRESSED_RG_RGTC1​ or GL_COMPRESSED_RGB_S3TC_DXT1_EXT​.

Framebuffer copy creation

Storage modification

Buffer storage