steps3D - Tutorials - Расширение ARB_bindless_texture

Расширение ARB_bindless_texture

Традиционная работа с текстурами (и изображениями в OpenGL) основана на том, что есть некоторое (ограниченное) количество текстурных блоков (texture unit). Для того, чтобы заданная текстура была доступна из шейдера, ее необходимо поместить (привязать, bind) к одному из этих текстурных блоков и задать значение соответствующей переменной типа sampler* в шейдере равной номеру этого блока (при помощи вызова glUniform1i*).

К сожалению данный способ несет в себе целый ряд ограничений и неудобств. С одной стороны необходимо явно выбирать (и помещать в текстурный блок) каждую текстуру, к которой может быть выполнено обращение из шейдера. С другой стороны количество этих текстурных блоков ограничено, что ограничивает максимальное число текстур, доступных данному шейдеру. Кроме того, операция выбора текстуры в текстурный блок также обладает определенной ценой.

Также существует целый ряд ограничений на использование переменных типа sampler* в GLSL. Так это могут быть только глобальные uniform-переменные, их нельзя запоминать, передавать в функции, индексироваться при помощи неконстантных значений и т.п.

Расширение ARB_bindless_texture предлагает совершенно новый способ работы с текстурами в OpenGL. При этом способе текстурные блоки вообще не используются, нет никакой необходимости привязывать текстуры к ним и шейдер может одновременно обращаться к миллиону текстур.

При этом способе работа с текстурами идет через так называемые дескрипторы (handle). Каждый такой дескриптор является 64-битовым целым числом без знака(GLuint64). Дескриптор может быть построен как по самой текстуре, так и по паре - текстура и объект-сэмплер. Для этого служат следующие функции:


GLuint64 glGetTextureHandleARB        ( GLuint texture );
GLuint64 glGetTextureSamplerHandleARB ( GLuint texture, GLuint sampler );

Обратите внимание, что каждой паре (texture, sampler) соответствует ровно один дескриптор. При попытке повторно получить дескриптор для такой пары будет возвращен уже существующий дескриптор.

Для того, чтобы к такой текстуре можно было обратится из шейдера, текстура должны быть резидентной. Для управления резидентностью текстуры и запроса ее статуса служат следующие функции:


void glMakeTextureHandleResidentARB    ( GLuint64 handle );
void glMakeTextureHandleNonResidentARB ( GLuint64 handle );
void glIsTextureHandleResidentARB      ( GLuint64 handle );

Также обратите внимание, что параметры текстуры, по которой был создан дескриптор нельзя изменять. Содержимое самой текстуры может быть изменено только при помощи рендеринга в нее или через вызовы функций glTexSubImage*, glCopyTexSubImage* и glCompressedTexSubImage*.

Внутри шейдера переменная типа sampler* может быть как целочисленным номером текстурного блока (если ее значение задается через вызов glUniform1i*) или дескриптором (в случае, когда его значение задается через glUniformHandleui64*ARB).

При работе с сэмплерами-дескрипторами в GLSL можно переводить в целые числа (точнее uvec2) или строить по паре целых числе (uvec2) при помощи следующих встроенных в GLSL функций:


uvec2 ( sampler* s )
sampler* ( uvec2 v )

Сами сэмплера такого типа могут выступать как uniform-переменные, временные переменные и параметры функций. Также они могут образовывать массивы, которые можно индексировать при помощи неконстантных выражений. Также они могут выступать как l-value.

Для задания требования обязательной поддержки данного расширения в GLSL служит следующая директива:


#extension GL_ARB_bindless_testure : require

При помощи описателя layout(bindless_sampler) можно явно задать, что заданный сэмплер (или же все сэмплеры в шейдере) являются дескрипторами.


layout(bindless_sampler) uniform sampler2D mySampler     // mySampler is bindless
layout(bindless_sampler) uniform;           		 // all samplers are bindless

Давайте рассмотрим простой пример использования этого способа работы с текстурами в OpenGL. Мы передадим в шейдер массив дескрипторов (для наглядности мы передадим его как массив uvec2). Потом мы по текстурным координатам определим номер текстуры в этом массиве, строим значение типа sampler2D и передаем его в функцию как параметр.


#version 430 core
#extension GL_ARB_bindless_texture : require

in  vec2 tx;
out vec4 color;
uniform uvec2   textures [8];

vec4 foo ( sampler2D tex )
{
    return texture ( tex, tx );
}

void main(void)
{
    vec2  t  = tx * vec2 ( 4.0, 4.0 );
    ivec2 i  = ivec2 ( t );
    int   no = (i.x + 4 * i.y) % 8;
    
    color = foo ( sampler2D ( textures [no] ) );
}

Ниже приводится соответствующий код на С++. Обратите внимание, что после получения дескриптора соответствующая текстура делается резидентной.

class   MeshWindow : public GlutRotateWindow
{
    Program     program;
    BasicMesh * mesh;
    Texture     tex    [8];
    GLuint64    handle [8];
    
public:
    MeshWindow () : GlutRotateWindow ( 200, 100, 900, 900, "Bindless texture" )
    {
        if ( !GLEW_ARB_bindless_texture )
            exit ( "Bindless textures not supported\n" );
        
        std::string texName = "../../Textures/";

        if ( !tex [0].load2D ( texName + "Fieldstone.dds" ) )
            exit ( "Error loading texture\n" );

        if ( !tex [1].load2D ( texName + "16.JPG" ) )
            exit ( "Error loading texture\n" );

        if ( !tex [2].load2D ( texName + "flower.png" ) )
            exit ( "Error loading texture\n" );

        if ( !tex [3].load2D ( texName + "lena.png" ) )
            exit ( "Error loading texture\n" );

        if ( !tex [4].load2D ( texName + "block.jpg" ) )
            exit ( "Error loading texture\n" );

        if ( !tex [5].load2D ( texName + "brick.tga" ) )
            exit ( "Error loading texture\n" );

        if ( !tex [6].load2D ( texName + "oak.jpg" ) )
            exit ( "Error loading texture\n" );

        if ( !tex [7].load2D ( texName + "rockwall.jpg" ) )
            exit ( "Error loading texture\n" );

        for ( int i = 0; i < 8; i++ )
        {
            handle [i] = glGetTextureHandleARB ( tex [i].getId () );
            
            glMakeTextureHandleResidentARB ( handle [i] );
        }
        
        if ( !program.loadProgram ( "bindless.glsl" ) )
            exit ( "Error building program: %s\n", program.getLog ().c_str () );
        
        program.bind  ();

        glUniform2uiv ( program.locForUniformName ( "textures" ), 8*2, (const GLuint *) handle );
        
        mesh = createKnot  ( 1, 4, 120, 30 );
        eye  = glm::vec3   ( 4, 4, 4 );
    }

    void redisplay ()
    {
        glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

        program.setUniformMatrix ( "mv", getRotation () );

        mesh -> render ();
    }

    void reshape ( int w, int h )
    {
        GlutWindow::reshape ( w, h );
        
        glm::mat4 proj = glm::perspective ( glm::radians(60.0f), getAspect(), 0.01f, 20.0f ) * glm::lookAt ( eye, glm::vec3 ( 0, 0, 0 ), glm::vec3 ( 0, 0, 1 ) );

        program.setUniformMatrix ( "proj", proj );
    }

    void mouseWheel ( int wheel, int dir, int x, int y )
    {
        eye += 0.5f * glm::vec3 ( dir, dir, dir );
        
                    // since eye value has changed
        reshape ( getWidth(), getHeight() );
        
        glutPostRedisplay ();
    }
};

int main ( int argc, char * argv [] )
{
    GlutWindow::init ( argc, argv );
    
    MeshWindow  win;
    
    return GlutWindow::run ();
}