OpenGL AZDO : Bindless Textures : batching problem solved

Hello!
After playing with Vulkan, I had to assume that it is not as easy as I wanted to use. Since this thing done, I preferred to come back to OpenGL. However, Vulkan let sme learn a lot of things about how OpenGL works internally. I am going to make a series of tutorials about OpenGL AZDO. The first one will discuss bindless textures!

What is OpenGL AZDO ?

OpenGL Approaching Zero Driver Overhead is an idea which comes from Cass Everitt, Tim Foley, John McDonald, Graham Sellers. The idea buried in it is to reduce the using of CPU by using the last possibilities offered by the new GPUs.
AZDO presents many techniques to eschew to have a low overhead :

  1. Make less binding as possible
  2. Use persistent mapping
  3. Use batching
  4. Use GPU for everything (culling, fill structures).

This series of tutorials will treat about how to implements such things.

Bindless Texture

Bindless texture solved a problem you may notice to implement batching. A naive draw loop could be like that

foreach(render target) { // frame buffer
foreach(pass) { // Depth, geometry, light
    foreach(material) { // textures
    draw();
    }
}

The main issue here is we cannot perform an efficient batch since each drawcall could have different textures.
Now, imagine you could put a texture inside a uniform buffer and just perform one big draw call! You reach to a very very few overhead!

How to do it ?

We are lucky, according to me, bindless texture is the easier of the AZDO feature to implement. However, we will really see them in action in the chapter about the batching. To run into bindless texture, you just have to follow these following steps

  1. Create the texture in the normal way
  2. Get the handle (kind of the address of the texture)
  3. Make the handle resident
  4. Put the handle in an uniform buffer

So there is a function you can use to load an image file using SDL and put it into a texture and enable bindless feature:

std::unique_ptr<Texture> Texture::loadImage2D(const std::string &path) {
    std::unique_ptr<Texture> texture = std::make_unique<Texture>();

    SDL_Surface *surface = IMG_Load(path.c_str());

    if(surface == nullptr)
        throw std::runtime_error(path + " does not opened");

    GLenum format, internalFormat;

    getFormats(surface, internalFormat, format);

    glCreateTextures(GL_TEXTURE_2D, 1, &texture->mId);

    glTextureParameteri(*texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTextureParameteri(*texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    GLsizei numMipmaps = ((GLsizei)log2(std::max(surface->w, surface->h)) + 1);
    glTextureStorage2D(*texture, numMipmaps, internalFormat, surface->w, surface->h);
    glTextureSubImage2D(*texture, 0, 0, 0, surface->w, surface->h,
                        format, GL_UNSIGNED_BYTE, surface->pixels);

    glGenerateTextureMipmap(*texture);

    texture->mHandle = glGetTextureHandleARB(*texture);
    glMakeTextureHandleResidentARB(texture->mHandle);

    SDL_FreeSurface(surface);

    return texture;
}

This code is easy, first you load a surface with SDL_image, you create the texture, you compute the number of possible mipmapping, you allocate them (each mipmapping’s level) and you send the value to the first mipmapping’s level.
After, you generate mipmaps, and you ask the texture to get the handle back, and you make it resident.

To use this “bindless” texture, you just have to put the “handle” (GLuint64) inside one uniform buffer.
After, you can use it like that:

#version 450 core

#extension GL_ARB_bindless_texture : require

layout(std140, binding = 0) uniform frameBuffer {
    // Here are all frameBuffer's renderTarget
    sampler2D gBufferNormal;
    sampler2D gBufferDiffuse;
    sampler2D gBufferDepth;
};

layout(location = 0) in vec2 uv;
layout(location = 0) out vec4 outColor;

void main(void)
{
    outColor = texture(gBufferDiffuse, uv);
}

The next article could be about batching (with multi draw indirect) or persistent mapping.

Reference


Leave a Reply


Scroll Top