Главная Статьи Ссылки Скачать Скриншоты Юмор Почитать Tools Проекты Обо мне Гостевая Форум |
Традиционная работа с текстурами (и изображениями в 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 ();
}