![]() |
Главная
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
Данное расширение (вошедшее в 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.