Расширение ARB_get_program_binary

Данное расширение, появившееся в спецификациях OpenGL 4.1, (но поддерживаемое на GPU класса OpenGL 3.x) дает давно ожидаемую возможность. Оно позволяет сохранить уже откомпилированную и слинкованную шейдерную программу (включающую в себя вершинный, фрагментный и другие шейдеры). Сохраненная программа (точнее ее бинарный образ) может быть потом загружена, избегая компиляции и линковки, и сразу же использована. Тем самым данное расширение позволяет фактически построить кэш откомпилированных программ заметно увеличив скорость их загрузки.

Данное расширение вводит несколько новых функций и констант. При помощи функции GetProgramBinary можно получить бинарное представление откомпилированной и слинкованной шейдерной программы.

void glGetProgramBinary ( GLuint program, GLsizei bufSize, GLsizei * length, GLenum * binaryFormat, void * binary );

Параметр program задает шейдерную программу, чей бинарный образ запрашивается. Параметр bufSize задает размер буфера, выделенного под бинарное представление в байтах. Через параметр length возвращается число байт бинарного представления, записанных в буфер, задаваемый параметром binary. С бинарным образом связывается внутренний формат, который возвращается через параметр binaryFormat. Значение внутреннего формата должно быть использовано при загрузке бинарного образа.

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

GLint   binaryLength;

glGetProgramiv ( program, GL_PROGRAM_BINARY_LENGTH, &binaryLength );

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

void glProgramBinary ( GLuint program, GLenum binaryFormat, const void * binary, GLsizei length );

Параметр program задает шейдерную программу для которой будет осуществляться загрузка ее бинарного представления, параметр binaryFormat (полученный при вызове glGetProgramBinary) задает внутренний формат, параметры binary и length задает буфер с бинарным образом программы и его длину в байтах.

Обратите внимание, что вызов glProgramBinary заменяет собой вызов glLinkProgram и выставляет признак успешности/неуспешности линковки.

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

Также после вызова glProgramBinary все вызовы glBindAttribLocation не будут иметь никакого эффекта (точно также, как и после вызова glLinkProgram).

Также этим расширением вводится еще одна функция -

void glProgramParameteri ( GLuint program, GLenum pname, GLint value );

Для того, чтобы сообщить OpenGL, что Вы собираетесь в дальнейшем запросить бинарный образ шейдерной программы, перед вызовом glLinkProgram следует поставить следующий вызов:

glProgramParameteri ( program, PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE );

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

#define	GL_PROGRAM_BINARY_RETRIEVABLE_HINT             0x8257
#define	GL_PROGRAM_BINARY_LENGTH                       0x8741
#define	GL_NUM_PROGRAM_BINARY_FORMATS                  0x87FE
#define	GL_PROGRAM_BINARY_FORMATS                      0x87FF

typedef void (APIENTRYP PFNGLGETPROGRAMBINARYPROC)  ( GLuint program, GLsizei bufSize, GLsizei * length, GLenum *binaryFormat, GLvoid * binary );
typedef void (APIENTRYP PFNGLPROGRAMBINARYPROC)     ( GLuint program, GLenum binaryFormat, const GLvoid * binary, GLsizei length );
typedef void (APIENTRYP PFNGLPROGRAMPARAMETERIPROC) ( GLuint program, GLenum pname, GLint value );

Соответствующий код следует добавить для доступа к вводимым функциям (он добавлен в библиотеку libExt).

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

bool	GlslProgram :: getLinkStatus () const
{
    GLint   linked;

    glGetProgramiv ( program, GL_OBJECT_LINK_STATUS_ARB, &linked );

    if ( !linked )
        return false;
		
    return true;
}

Data  * GlslProgram :: getBinary ( GLenum * binaryFormat ) const
{
    if ( program == 0 || !getLinkStatus () )
        return NULL;

    if ( glGetProgramBinary == NULL || glProgramBinary == NULL || glProgramParameteri == NULL )
        return NULL;

    GLint   binaryLength;
    void  * binary;

    glGetProgramiv ( program, GL_PROGRAM_BINARY_LENGTH, &binaryLength );

    if ( binaryLength < 1 )
        return NULL;

    binary = (GLvoid * )malloc ( binaryLength );

    if ( binary == NULL )
        return NULL;

    glGetProgramBinary ( program, binaryLength, NULL, binaryFormat, binary );

    return new Data ( binary, binaryLength );
}

bool	GlslProgram :: saveBinary ( const char * fileName, GLenum * binaryFormat ) const
{
    Data * data = getBinary ( binaryFormat );

    if ( data == NULL )
        return false;

    data -> saveToFile ( fileName );

    delete data;

    return true;
}

bool	GlslProgram :: loadBinary ( Data * data, GLenum binaryFormat )
{
    if ( program == 0 )
        return false;

    if ( glGetProgramBinary == NULL || glProgramBinary == NULL || glProgramParameteri == NULL )
        return false;

    if ( data == NULL || data -> getLength () < 1 )
        return false;

    glProgramBinary ( program, binaryFormat, data -> getPtr (), data -> getLength () );

    return getLinkStatus ();
}

bool	GlslProgram :: loadBinary ( const char * fileName, GLenum binaryFormat )
{
    Data * data = new Data ( fileName );
    bool   res  = loadBinary ( data, binaryFormat );

    delete data;

    return res;
}

Для демонстрации использования введенных методов возьмем уже готовую программу (демонстрирующую попиксельное освещение методом Фонга) и следующим образом изменим функцию main:

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

                                // create window
    glutCreateWindow ( "Blinn Lighting Model" );

                                // register handlers
    glutDisplayFunc  ( display );
    glutReshapeFunc  ( reshape );
    glutKeyboardFunc ( key     );
    glutMouseFunc    ( mouse   );
    glutMotionFunc   ( motion  );
    glutIdleFunc     ( animate );

    init           ();
    initExtensions ();

    assertExtensionsSupported ( "GL_ARB_shading_language_100 GL_ARB_shader_objects" );

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

        return 3;
    }

    if ( isExtensionSupported ( "GL_ARB_get_program_binary" ) )
    {
        GLenum fmt;

        if ( argc == 4 ) 
        {
            if ( strcmp ( argv [1], "-load" ) == 0 )
            {
                GLenum fmt;

                sscanf ( argv [3], "%x", &fmt );			
                printf ( "Trying to load \"%s\" with format %x\n", argv [2], fmt );

                if ( !program.loadBinary ( argv [2], fmt ) )
                {
                    printf ( "Failure loading binary image.\n" );
                    exit   ( 1 );
                }
                else
                    printf ( "Successfully loaded binary.\n" );
            }
        }
        else
        if ( program.saveBinary ( "blinn.bin", &fmt ) )
            printf ( "Program is saved, format = %x\n", fmt );
        else
            printf ( "Error saving binary.\n" );
    }

    glutMainLoop ();

    return 0;
}

Теперь при запуске выполнимого файла blinn.exe будет проверена поддержка расширения ARB_get_program_binary. Если расширение поддерживается и количество аргументов командной строки не равно 4 (включая имя самой программы), то произойдет сохранение бинарного образа шейдерной программы в файл blinn.bin. Также будет напечатан в шестнадцатеричном виде внутренний формат, соответствующий данному бинарному образу.

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

blinn.exe -load blinn.bin xxxx

Через xxxx обозначен внутренний формат бинарного образа в шестнадцатеричном виде.

Здесь можно найти частичный разбор бинарника.

Обратите внимание, что этот подход обладает и некоторыми слабыми сторонами, а именно в бинарнике сохраняется результат компиляции и линковки программы, сделанный конкретным драйвером под конкретное GPU. Изменение хотя бы одного из них (например установка новых драйверов) может дать другой (более эффективный) код. Поэтому сохранение бинарника имеет смысл именно как кэширование, которое имеет смысл сбрасывать при изменении драйверов и GPU.

Некоторые реальные данные о времени загрузки и размерах можно найти в этом обсуждении на форуме gamedev.ru.

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

Обратите внимание, что вам понадобятся свежие версии библиотек libExt, libTexture и program.