Работа с целочисленными текстурами и целыми числами в шейдерах

С самого начала программируемые GPU работали исключительно с вещественными числами (32-bit float) - для этого достаточно взглянуть в спецификации расширений ARB_vertex_program и ARB_fragment_program.

Хотя в языке GLSL и есть поддержка типов bool и int, но фактически эти типы реализовывались через тип float, что в частности выражалось в отсутствии поддержки побитовых операций (для которых нет floating point аналогов).

Однако начиная с GPU Nvidia GeForce 8xxx появилась наконец полноценная поддержка целочисленных типов данных. При этом эта поддержка осуществляется как на уровне шейдеров, так и на уровне текстур - добавлены новые форматы текстур с целочисленными компонентами.

Эта поддержка опирается на два расширения OpenGL - EXT_texture_integer и EXT_gpu_shader4.

Первое расширение (EXT_texture_integer) вводит понятие "ненормализованных" целых. Дело в том, что OpenGL ранее использовал целые числа для ряда целей (например, представления цветов), но при этом эти числа считались представлением вещественных чисел из отрезка [0,1] и приводились к нему - т.е. эти числа при требовали нормализации и в исходном виде были недоступны (так обычно все компоненты текстуры представлены в виде 8-битовых беззнаковых значений, однако как для OpenGL, так и для шейдеров эти компоненты передаются уже как нормализованные вещественные числа).

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

Также расширение EXT_texture_integer вводит целый ряд форматов текстур с ненормализованными целочисленными знаковыми и беззнаковыми компонентами и различным количеством бит на компоненту (8/16/32).

Таблица 1. Целочисленные форматы текстур, вводимые расширением EXT_texture_integer.

Формат Число компонент Число бит на компоненту Знаковая/беззнаковая
GL_ALPHA8I_EXT 1 8 знаковая
GL_ALPHA8UI_EXT 1 8 беззнаковая
GL_ALPHA16I_EXT 1 16 знаковая
GL_ALPHA16UI_EXT 1 16 беззнаковая
GL_ALPHA32I_EXT 1 32 знаковая
GL_ALPHA32UI_EXT 1 32 беззнаковая
GL_LUMINANCE_ALPHA8I_EXT 2 8 знаковая
GL_LUMINANCE_ALPHA8UI_EXT 2 8 беззнаковая
GL_LUMINANCE_ALPHA16I_EXT 2 16 знаковая
GL_LUMINANCE_ALPHA16UI_EXT 2 16 беззнаковая
GL_LUMINANCE_ALPHA32I_EXT 2 32 знаковая
GL_LUMINANCE_ALPHA32UI_EXT 2 32 беззнаковая
GL_RGB8I_EXT 3 8 знаковая
GL_RGB8UI_EXT 3 8 беззнаковая
GL_RGB16I_EXT 3 16 знаковая
GL_RGB16UI_EXT 3 16 беззнаковая
GL_RGB32I_EXT 3 32 знаковая
GL_RGB32UI_EXT 3 32 беззнаковая
GL_RGBA8I_EXT 4 8 знаковая
GL_RGBA8UI_EXT 4 8 беззнаковая
GL_RGBA16I_EXT 4 16 знаковая
GL_RGBA16UI_EXT 4 16 беззнаковая
GL_RGBA32I_EXT 4 32 знаковая
GL_RGBA32UI_EXT 4 32 беззнаковая

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

void glClearColorIiEXT  ( GLint r,  GLint g,  GLint b,  GLint a );
void glClearColorIuiEXT ( GLuint r, GLuint g, GLuint b, GLuint a );

Данные функции служат для задание целочисленного (знакового и беззнакового) RGBA-значения, используемого для очистки фреймбуфера.

Расширение EXT_gpu_shader4 вводит необходимую поддержку целых чисел в шейдеры на GLSL.

Помимо уже имеющихся типов данных добавлены новые типы для беззнаковых целых - unsigned int, uvec2, uvec3 и uvec4.

Для всех целочисленных форматов введена полная поддержка всех побитовых операций, включая битовые сдвиги.

Для доступа к текстурам с целочисленными форматами ведены новые типы сэмплеров - isampler1D, isampler2D, isampler3D, isamplerCube, isampler2DRect, isampler1DArray, isampler2DArray, isamplerBuffer, usampler1D, usampler2D, usampler3D, usamplerCube, usampler2DRect, usampler1DArray, usampler2DArray и usamplerBuffer.

Для чтения из таких текстур используются стандартные функции доступа к текстурам (такие как texture2D, texture2DRect и т.п.), только с соответствующим типом сэмплера. Обратите внимание, что результаты чтения из целочисленных текстур также являются целочисленными (знакового или беззнакового типа в зависимости от типа сэмплера), т.е. тип возвращаемого значения зависит от типа сэмплера.

Также расширение вводит целочисленные вершинные атрибуты и целочисленные uniform и varying переменные. Для доступа к ним введены следующие новые функции:

void glVertexAttribI1iEXT  ( GLuint index, GLint  x );
void glVertexAttribI1uiEXT ( GLuint index, GLuint x );
void glVertexAttribI2iEXT  ( GLuint index, GLint  x, GLint  y );
void glVertexAttribI2uiEXT ( GLuint index, GLuint x, GLuint y );
void glVertexAttribI3iEXT  ( GLuint index, GLint  x, GLint  y, GLint  z );
void glVertexAttribI3uiEXT ( GLuint index, GLuint x, GLuint y, GLuint z );
void glVertexAttribI4iEXT  ( GLuint index, GLint  x, GLint  y, GLint  z, GLint  w );
void glVertexAttribI4uiEXT ( GLuint index, GLuint x, GLuint y, GLuint z, GLuint w );

void glVertexI4ivEXT ( GLuint inrex,  const GLint  * ptr );
void glVertexI4uivEXT ( GLuint inrex, const GLuint * ptr );

void glVertexAttribIPointerEXT ( GLenum index, int size, GLenum type, GLsizei stride, const void * ptr );

void glUniform1uiEXT ( GLint loc, GLuint x );
void glUniform2uiEXT ( GLint loc, GLuint x, GLuint y );
void glUniform3uiEXT ( GLint loc, GLuint x, GLuint y, GLuint z );
void glUniform4uiEXT ( GLint loc, GLuint x, GLuint y, GLuint z,GLuint z );

Еще одно важное изменение - у фрагментного шейдера появился новый способ возвращения значения - для этой цели можно использовать переменные, объявленные как varying out. Именно этот способ следует использовать при рендеринге в текстуру с целочисленными компонентами ( использовать gl_Color и gl_FragData нельзя , так как они floating point).

Расширение EXT_gpu_shader4 вводит некоторые ограничения - нельзя читать из текстур с целочисленными компонентами во floating point переменные, точно так же нельзя использовать для записи в целочисленный фреймбуфер floating point переменные. Поэтому основной способ для записи в такой фреймбуфер - это использовать ivec4 varying out переменные.

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

#extension GL_EXT_gpu_shader4 : enable    // turn off warning

uniform isampler2DRect      srcMap;
varying out     ivec4       result;

void    main ()
{
    ivec4   tex = texture2DRect ( srcMap, gl_TexCoord [0].xy );
    
    tex += ivec4 ( 1 );
    
    result = ivec4 ( tex.x ^ tex.y, tex.x & tex.y, tex.x | tex.y, 777 );
}

Данный шейдер читает знаковое целочисленное значение из входной текстуры и возвращает результат применение нескольких побитовых операций над прочтенными значениями.

Обратите внимание на директиву #extension в начале шейдер - она сообщает компилятору о требуемом расширение и выключает выдачу предупредительных сообщений в лог компиляции.

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

void glBindFragDataLocationEXT ( GLuint program, GLuint colorNumber, const char * name );

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

#include    "libExt.h"

#include    <glut.h>
#include    <stdio.h>
#include    <stdlib.h>
#include    <assert.h>

#include    "libTexture.h"
#include    "Vector3D.h"
#include    "Vector4D.h"
#include    "Framebuffer.h"
#include    "GlslProgram.h"
#include    "utils.h"

unsigned    srcMap;                 
GLenum      format  = GL_RGBA32UI_EXT;
        
FrameBuffer buffer   ( 512, 512, 0 );
GlslProgram program;

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

    glutSwapBuffers ();
}

void reshape ( int w, int h )
{
    glViewport ( 0, 0, (GLsizei)w, (GLsizei)h );
    startOrtho ( w, h );
}

void key ( unsigned char key, int x, int y )
{
    if ( key == 27 || key == 'q' || key == 'Q' )        // quit requested
        exit ( 0 );
}

int main ( int argc, char * argv [] )
{
                                // initialize glut
    glutInit            ( &argc, argv );
    glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL );
    glutInitWindowSize  ( 512, 512 );

                                // create window
    glutCreateWindow ( "OpenGL integer textures demo" );

                                // register handlers
    glutDisplayFunc  ( display );
    glutReshapeFunc  ( reshape );

    init           ();
    initExtensions ();

    assertExtensionsSupported ( "GL_EXT_texture_integer GL_EXT_framebuffer_object GL_EXT_gpu_shader4" );

    unsigned screenMap1 = buffer.createColorRectTexture ( GL_RGBA_INTEGER_EXT, format );
    unsigned screenMap2 = buffer.createColorRectTexture ( GL_RGBA_INTEGER_EXT, format );
    
    buffer.create ();
    buffer.bind   ();
    if ( !buffer.attachColorTexture ( GL_TEXTURE_RECTANGLE_ARB, screenMap1, 0 ) )
        printf ( "buffer error with color attachment\n");

    if ( !buffer.attachColorTexture ( GL_TEXTURE_RECTANGLE_ARB, screenMap2, 1 ) )
        printf ( "buffer error with color attachment\n");

    if ( !buffer.isOk () )
        printf ( "Error with framebuffer\n" );
                    
    buffer.unbind ();

    if ( !program.loadShaders ( "vertex.vsh", "fragment-2.fsh" ) )
    {
        printf ( "Error loading shaders:\n%s\n", program.getLog ().c_str () );

        return 3;
    }

    int * buf = new int [4*256*256];
    int * ptr = buf;
    
    for ( int i = 0; i < 256; i++ )
        for ( int j = 0; j < 256; j++ )
        {
            *ptr++ = i;
            *ptr++ = j;
            *ptr++ = 0;
            *ptr++ = 0;
        }

    glEnable        ( GL_TEXTURE_RECTANGLE_ARB );
    glGenTextures   ( 1, &srcMap );
    glPixelStorei   ( GL_UNPACK_ALIGNMENT, 1 );                         // set 1-byte alignment
    glBindTexture   ( GL_TEXTURE_RECTANGLE_ARB, srcMap );
    glTexImage2D    ( GL_TEXTURE_RECTANGLE_ARB, 0, format, 256, 256, 0, GL_RGBA_INTEGER_EXT, GL_UNSIGNED_INT, buf );

    glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
    glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
    glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP );
    glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP );
    
    GLenum  buffers [] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
    
    program.bindFragOut ( "result1", 0 );
    program.bindFragOut ( "result2", 1 );
    program.bind ();
    program.setTexture  ( "srcMap", 0 );
    program.unbind      ();
    
    reshape ( buffer.getWidth (), buffer.getHeight () );
    buffer.bind ();
    glDrawBuffers ( 2, buffers );
    
    program.bind ();
    
    drawQuad ( 256, 256 );

    program.unbind ();
    buffer.unbind ();
    
    memset ( buf, '\0', 4*256*245*sizeof (int) );

    buffer.bind   ();
    glReadBuffer  ( GL_COLOR_ATTACHMENT0_EXT );
    glReadPixels  ( 0, 0, 255, 256, GL_RGBA_INTEGER_EXT, GL_UNSIGNED_INT, buf );
    glReadBuffer  ( GL_COLOR_ATTACHMENT1_EXT );
    glReadPixels  ( 0, 0, 255, 256, GL_RGBA_INTEGER_EXT, GL_UNSIGNED_INT, buf );
    buffer.unbind ();

    return 0;
}

Соответствующие вершинный и фрагментный шейдеры приводится ниже.

void main(void)
{
    gl_Position     = gl_ModelViewProjectionMatrix * gl_Vertex;
    gl_TexCoord [0] = gl_MultiTexCoord0;
}

#extension GL_EXT_gpu_shader4 : enable

uniform isampler2DRect      srcMap;
varying out     ivec4       result1;
varying out     ivec4       result2;

void    main ()
{
    ivec4   tex = texture2DRect ( srcMap, gl_TexCoord [0].xy );
    
    tex += ivec4 ( 1 );
    
    result1 = ivec4 ( tex.x ^ tex.y, tex.x & tex.y, tex.x | tex.y, 777 );
    result2 = ivec4 ( 1, 2, 3, 4 );
}

По этой ссылке можно скачать весь исходный код к этой статье,а также откомпилированные примеры для M$ Windows и Linux.

Обратите внимание, что для компиляции примера вам понадобится новые версии библиотек libExt и классов FrameBuffer и GlslProgram.