Главная Статьи Ссылки Скачать Скриншоты Юмор Почитать Tools Проекты Обо мне Гостевая Форум |
Данное расширение (вошедшее в core OpenGL начиная с версии 4.2) добавляет в OpenGL поддержку атомарных счетчиков. Для этих счетчиков поддерживаются операции взятия текущего значения, атомарного увеличения и атомарного уменьшения значения счетчика на единицу.
Атомарность счетчика гарантирует, что если одновренно будет произведено несколько операций по изменению значения данного счетчика, то все эти запросы будут обработаны корректно. Обратите внимание, что для многопоточного приложения (а шейдеры можно рассматривать именно как многопоточное приложения, исполняемое на GPU) обычная операция инкремента или декремента не является атомарной. Это связано с тем, что обычно подобная операция состоит из трех шагов - прочесть значения переменной в регистр, изменить значение регистра на еденицу и записать значение из регистра обратно в переменную.
Если одновременно происходит сразу несколько подобных операций над одним и тем же счетчиком и переключения между нитями произошло после чтения значения в регистр но до записи итогового значения, то в результате записано будет некорректное значение. Атомарные счетчики лишены подобного недостатка - в них операция инкремента и декремента происходят атомарно и внутри такой операции не может быть переключения нитей.
Расширения ARB_atomic_counters вводит в GLSL новый тип данных - atomic_uint, являющийся непрозрачной оберткой (handle) для32-битового беззнакового целочисленного счетчика. Переменные этого типа описываются как uniform, однако память под эти переменные предоставляется вершинными буферами нового типа - GL_ATOMIC_COUNTER_BUFFER.
При этом атомарный счетчик привязывается (при помощи команд glBindBufferBase и glBindBufferRange) к точке привязки и смещению внутри буфера. Один буфер может использоваться для хранения сразу нескольких атомарных счетчиков.
При описании атомарного счетчика в теле шейдера при помощи директивы layout осуществляется привязка к конкретной точке привязки и смещению внутри буфера.
layout ( binding = 2, offset = 4 ) uniform atomic_uint myCounter;
При линковке шейдеров все шейдеры разделяют общее пространство uniform-переменных, таким образом шейдеры разных типов (например вершинный и фрагментный) могут использовать одни и те же атомарные счетчики.
Для изменения значения атомарного счетчика используются встроенные функции atomicCounterIncrement и atomicCounterDecrement, изменяющие значение счетчика на +1 и -1 соответственно.
uint atomicCounterIncrement ( atomic_uint counter ); uint atomicCounterDecrement ( atomic_uint counter );
Функция atomicCounterIncrement возвращает значение атомарноо счетчика до операции инкремента, а функция atomicCounterDecrement возврашает значение счетчика после декремента.
При помощи функции atomicCounter можнр получить значение атомарного счетчика не изменяя его.
uint atomicCounter ( atomic_uint counter );
Обратите внимание, что каждая операция изменения значения атомарного счетчика получает исключительное право на его изменение, изменяет его значения и после этого возвращает исключительное право. Поэтому много атомарных операций могут заметно снизить быстродействие программы.
Ниже мы рассмотрим пример, в котором атомарные счетчики используются для подсчета общего числа выводимых фрагментов, а также создания простейшей гистограммы по яркости фрагментов. Для этих целей мы будем использовать следующий фрагментный шейдер.
#version 420 core
uniform float scale;
in vec3 n;
in vec3 v;
in vec3 l;
in vec3 h;
out vec4 color;
layout(binding=0, offset=0) uniform atomic_uint c0;
layout(binding=0, offset=4) uniform atomic_uint c1;
layout(binding=0, offset=8) uniform atomic_uint c2;
layout(binding=0, offset=12) uniform atomic_uint total;
const vec3 lum = vec3 ( 0.27, 0.67, 0.06 );
const vec4 clr = vec4 ( 0.902, 0.694, 0.49, 1.0 );
void main (void)
{
const vec4 specColor = vec4 ( 0.7, 0.7, 0.0, 1.0 );
const float specPower = 70.0;
vec3 n2 = normalize ( n );
vec3 l2 = normalize ( l );
vec3 h2 = normalize ( h );
vec4 diff = vec4 ( max ( dot ( n2, l2 ), 0.0 ) + 0.3 );
vec4 spec = specColor * pow ( max ( dot ( n2, h2 ), 0.0 ), specPower );
atomicCounterIncrement ( total ); // total # of fragments
color = diff * clr + spec;
float l = dot ( lum, color.rgb );
if ( l < 0.5 )
atomicCounterIncrement ( c0 );
else
if ( l < 1.0 )
atomicCounterIncrement ( c1 );
else
atomicCounterIncrement ( c2 );
}
Здесь атомарные счетчики c0, c1, c2 и total испольуются для подсчета числа фрагментов. Ниже приодится соответствующий код на С++.
#include <GL/glew.h>
#ifdef _WIN32
#include <GL/wglew.h>
#else
#include <GL/glxew.h>
#endif
#include "GlutWindow.h"
#include "Program.h"
#include "mat4.h"
#include "vec2.h"
#include "Texture.h"
#include "BasicMesh.h"
#include "glUtilities.h"
#include "VertexBuffer.h"
#define NUM_VERTICES 3
#define VERTEX_SIZE (5*sizeof(float))
int mouseOldX = 0;
int mouseOldY = 0;
float angle = 0;
vec3 rot ( 0.0f );
vec3 eye ( 7, 7, 7 );
vec3 light ( 7, 7, 7 );
float scale = 9.0;
class MyWindow : public GlutWindow
{
Program program;
BasicMesh * mesh;
VertexBuffer counterBuf;
public:
MyWindow () : GlutWindow ( 200, 100, 400, 400, "ARB_shader_atomic_counters" )
{
glClearColor ( 0.5, 0.5, 0.5, 1.0 );
glEnable ( GL_DEPTH_TEST );
glDepthFunc ( GL_LEQUAL );
const char * err = getGlErrorString ();
if ( !program.loadProgram ( "atomic-counters.glsl" ) )
{
printf ( "Error building program: %s\n", program.getLog ().c_str () );
exit ( 2 );
}
else
printf ( "Shader loaded:\n%s\n", program.getLog ().c_str () );
mesh = createTorus ( 2, 4, 30, 30 );
counterBuf.create ();
counterBuf.bindBase ( GL_ATOMIC_COUNTER_BUFFER, 0 ); // unbind ???
}
virtual void redisplay ()
{
static GLuint buf [] = { 0, 0, 0, 0 };
static GLuint counters [4];
counterBuf.setData ( sizeof ( buf ), buf, GL_DYNAMIC_DRAW );
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
mat4 mv = mat4 :: rotateZ ( toRadians ( rot.z ) ) * mat4 :: rotateY ( toRadians ( rot.y ) ) * mat4 :: rotateX ( toRadians ( rot.x ) );
mat3 nm = normalMatrix ( mv );
program.bind ();
program.setUniformMatrix ( "mv", mv );
program.setUniformMatrix ( "nm", nm );
mesh -> render ();
program.unbind ();
glFinish ();
counterBuf.getSubData ( 0, sizeof ( buf ), counters );
printf ( "%4d %4d %d %d\n", counters [0], counters [1], counters [2], counters [3] );
}
virtual void reshape ( int w, int h )
{
GlutWindow::reshape ( w, h );
glViewport ( 0, 0, (GLsizei)w, (GLsizei)h );
mat4 proj = perspective ( 60.0f, (float)w / (float)h, 0.5f, 20.0f ) * lookAt ( eye, vec3 :: zero, vec3 ( 0, 0, 1 ) );
program.bind ();
program.setUniformMatrix ( "proj", proj );
program.setUniformVector ( "eye", eye );
program.setUniformVector ( "light", light );
program.unbind ();
}
virtual void mouseMotion ( int x, int y )
{
rot.y += ((mouseOldY - y) * 180.0f) / 200.0f;
rot.z += ((mouseOldX - x) * 180.0f) / 200.0f;
rot.x = 0;
if ( rot.z > 360 )
rot.z -= 360;
if ( rot.z < -360 )
rot.z += 360;
if ( rot.y > 360 )
rot.y -= 360;
if ( rot.y < -360 )
rot.y += 360;
mouseOldX = x;
mouseOldY = y;
}
virtual void mouseClick ( int button, int state, int modifiers, int x, int y )
{
if ( state == GLUT_DOWN )
{
mouseOldX = x;
mouseOldY = y;
}
}
virtual void keyTyped ( unsigned char key, int modifiers, int x, int y )
{
if ( key == 27 || key == 'q' || key == 'Q' ) // quit requested
exit ( 0 );
}
virtual void idle ()
{
angle = 4 * getTime ();
light.x = 8*cos ( angle );
light.y = 8*sin ( 1.4 * angle );
light.z = 8 + 0.5 * sin ( angle / 3 );
program.bind ();
program.setUniformVector ( "eye", eye );
program.setUniformVector ( "light", light );
program.unbind ();
GlutWindow::idle (); // for glutPostRedisplay ();
}
};
int main ( int argc, char * argv [] )
{
// initialize glut
GlutWindow::init( argc, argv, 4, 2 );
MyWindow win;
GlutWindow::run ();
return 0;
}
При помощи команды glGetActiveAtomicCounterBufferiv можно получить информацию об атомарных счетчиках используемых данной шейдерной программой.
void glGetActiveAtomicCounterBufferiv( GLuint program, GLuint bufferIndex, GLenum pname, GLint * params );
Параметры program и bufferIndex задают шейдерную программу и номер буфера. Максимальное значение для индекса буфера можно получить при помощи команды glGetIntegerv с параметром GL_ACTIVE_ATOMIC_COUNTER_BUFFERSю
Параметр pname задает возвращаемое свойство и может принимать одно из следующих значений:
Максимальное число атомарных счетчиок, допустимых для конкретного типа шейдера, можно узнать при помощи команды glGetIntegerv где параметр принимает одно из следующих значений - GL_MAX_VERTEX_ATOMIC_COUNTERS, GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS, GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS, GL_MAX_GEOMETRY_ATOMIC_COUNTERS и GL_MAX_FRAGMENT_ATOMIC_COUNTERS. Максимальное общее число атомарных счетчиков можно узнать через команду glGetIntegerv с параметром GL_MAX_COMBINED_ATOMIC_COUNTERS.
По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows и Linux.