TextureType::TEX_TYPE_3D - A three-dimensional texture, can be imagined as an array of 2D textures, but with support for interpolating between the texture slices. Used primarily when working with the low-level rendering API for special purposes.

TextureType::TEX_TYPE_CUBE_MAP - A cubemap texture that consists out of six 2D textures, each representing one face of the cube. Used primarily when working with the low-level rendering API for special purposes.

You may also set these optional properties:

TEXTURE_DESC::numMips - A texture with mip-maps will contain a set of scaled down versions of itself that are used by the GPU for anti-aliasing. Specify zero to use no mip maps. You can use the helper function PixelUtil::getMaxMipmaps() to return the maximum possible mip-map count for a specific set of dimensions.

TEXTURE_DESC::numArraySlices - Specify number higher than 1 in order to create an array of textures. This is primarily used for low-level rendering purposes. Texture arrays are not supported for 3D textures.

TEXTURE_DESC::hwGamma - When true, it specifies if the data in the texture is gamma corrected. When performing reads on such texture in a shader the GPU will transform the texture data back to linear space before returning the value. When a texture is used as a render target, the GPU will automatically convert from linear space into gamma space when rendering to the texture. Only relevant for 2D textures.

TEXTURE_DESC::numSamples - Specifies the number of samples per pixel. This is used primarily for multi-sample antialiasing. This is only relevant for 2D textures, and only for textures used as render targets. You cannot read or write from/to multi-sample textures manually.

TextureUsage::TU_DEPTHSTENCIL - Specify for textures that will be used as a depth/stencil attachment for render target. Texture's pixel format must be one of the depth-stencil formats.

TextureUsage::TU_LOADSTORE - Specify that the texture can be used for random read/write by shaders (e.g. for use in a compute shader).

TextureUsage::TU_CPUCACHED - Specify that any data written to the texture (from the CPU) will be cached internally, allowing it to be accessed through Texture::readCachedData(). Uses extra memory as data needs to be stored in both normal and GPU memory.

TextureUsage::TU_CPUREADABLE - Specify that the CPU should be allowed to read texture data that was written by the GPU (e.g. resulting from rendering or a compute shader writes).

Most of these options are only useful when using the low-level rendering API and your own shaders, in which case you might require advanced texture types and options. In majority of cases however you will be using 2D textures with mip-maps, potentially with gamma-corrected data, used for normal rendering (e.g. assigning them to materials).

Low level rendering API is explained as a part of the developer manuals.

Writing data

Once a texture has been created you might want to write some data to it. This is accomplished by calling Texture::writeData(). The method accepts a PixelData object, as well as a mip-map level and a face to write to.

PixelData object is just a container for all the pixels of a single mip-level & face of a texture. It is created by calling PixelData::create() and providing dimensions as well as the pixel format.

Writing to sub-resources

If a texture contains mip levels, or more than one face then we say it has multiple sub-resources. Each such sub-resource must be written to with a separate call to Texture::writeData().

A texture has multiple mip-levels if its TEXTURE_DESC::numMips property is larger than zero. A texture has multiple faces if its TEXTURE_DESC::numArraySlices property is larger than one, or if texture type is TextureType::TEX_TYPE_CUBE_MAP.

If texture type is TextureType::TEX_TYPE_CUBE_MAP then the texture will have six faces by default. If such texture has multiple array slices the total number of faces is 6 * number of array slices. Each face has its own set of mip-map levels (if mip-maps count is larger than zero).

To write to different sub-resources simply provide the mip-level and face index when calling Texture::writeData(). Note that when setting mip-levels your PixelData object must be of valid size for the mip-level. You can use the helper method PixelUtil::getSizeForMipLevel() to calculate dimensions of a specific mip level.

// Creates a 2D texture, 128x128 with an 8-bit RGBA format, with maximum number of mipmaps, and 4 faces

Note when writing we access a total of desc.numMips + 1 mip-levels. This is because the 0th mip level is the main texture.

As a shortcut, you can retrieve texture properties by calling Texture::getProperties() and then call the TextureProperties::allocBuffer() to automatically create a PixelData object of a valid size and format, depending on provided face and mip level.

auto& texProps = texture->getProperties();

// Get buffer with enough space and valid format for 0th face and 2nd mip-level

SPtr<PixelData> pixelData = texProps.allocBuffer(0, 2);

// ... populate the buffer and write

Discard on write

When you are sure you will overwrite all the contents of a texture, make sure to set the last parameter of Texture::writeData() to true. This ensures the system can more optimally execute the transfer, without requiring the GPU to finish its current action (which can be considerably slow if it is currently using that particular texture).

Generating mip-maps

Mip-maps are generally created automatically from a source texture, rather than by manually setting their pixels. Therefore bs::f provides PixelUtil::genMipmaps() method that accepts a PixelData object containing pixels to generate mip levels from. A maximum number of mip-maps levels is then generated and output. You can optionally customize mip-map generation by providing a MipMapGenOptions object.

Writing compressed data

If a PixelFormat chosen for your texture uses one of the compressed pixel formats, you will need to compress the data before writing it to the texture. For this purpose you can use the PixelUtil::compress() method. The method accepts a source PixelData and a destination PixelData, as well as a CompressionOptions object that contains the pixel format to compress to, among other options.

Creating textures with data

If you're creating a texture you wish to immediately populate with data, you can use the overload for Texture::create() that accepts a PixelData object directly, allowing you to skip the call to Texture::writeData().

Reading texture data

Reading data from a texture is done similarly to writing, using PixelData object as well. There are two ways to read texture data:

Reading cached CPU data

Reading GPU data

Reading cached CPU data

Reading cached CPU data allows you to read-back any data you have written to the texture by calling Texture::writeData(). It is particularily useful when importing textures from external files and wish to access their pixels. Note that texture must be created with the TextureUsage::TU_CPUCACHED usage flag in order for CPU cached data to be available. When importing textures this flag will automatically be set if the relevant property is enabled in TextureImportOptions.

Cached CPU data can be read by calling Texture::readCachedData(). It accepts a PixelData parameter to which to output the pixel colors, as well as indices to the face & mip-level to read.

Note that cached data reads will not contain any data written by the GPU (e.g. in case the texture is used as a render target or written to by GPU in some other way).

Reading GPU data

In case cached CPU reads are not enough, you can perform GPU reads, which always read the most recent data which includes both the data written by the CPU and the GPU. Unlike CPU caching this also does not require additional memory to be used to store texture data. Note that texture must be created with the TextureUsage::TU_CPUREADABLE usage flag in order for such reads to be available.

To perform GPU reads call Texture::readData() which has the same interface as Texture::readCachedData().

SPtr<PixelData> pixelData = texProps.allocBuffer(0, 0);

texture->readData(*pixelData);

Note that performing GPU reads will almost certainly cause a GPU pipeline stall, requiring all GPU operations to finish before the read completes. Such stalls can severely impact performance and should generally be avoided.

Also note that this operation is asynchronous. This means the function will return immediately, but the actual contents of the provided PixelData object will not be populated until the async operation finishes. Read the mini-manual for async operations for more information.

Other

Take a look at PixelUtil class for a variety of helper methods for manipulating pixel data and colors.