|
Главная
Статьи
Ссылки
Скачать
Скриншоты
Юмор
Почитать
Tools
Обо мне
Гостевая
Форум
|
Ряд операций по обработке данных можно разделить как операции типа gather и операции типа scatter.
Типичным примером операции типа gather является применение матричного фильтра к изображению - каждый результирующий пиксел (для сглаживания с ядром kij) будет задаваться следующей формулой:
Из приведенной формулы легко видно, что для получение одного тексела результирующего изображения необходимо прочесть несколько текселов исходного и объединить их при помощи некоторой операции (в данном случае свертки).
Типичным примером операции scatter является построение гистограммы заданного изображения - весь диапазон возможных значений некоторого параметра (например, яркости) разбивается на ряд отрезков ("корзин", bins) и для каждого такого отрезка считается сколько исходных текселов попали в него.
Классическая реализация построения гистограмм на CPU инициализирует массив "корзин" и для каждого пиксела определяет номер соответствующей ему корзины и увеличивает счетчик по этому номеру (см. псевдокод ниже).
for pix in image:
index = colorToBinIndex ( pix ) # get bin index for a pixel
bin [index] = bin [index] + 1 # increment bin's counter
Однако если операции первого типа (gather) очень легко и эффективно реализуются на GPU, то операции второго типа (scatter) обычно крайне плохо ложатся на архитектуру современных GPU, так как требуют доступа к памяти типа random write.
Однако сравнительно недавно было замечено, что ряд операций данного типа (включая и построение гистограмм) можно довольно легко и эффективно реализовать на GPU.
Для этого сопоставим каждому текселу исходного изображения одну точку. Массиву "корзин" (bins) сопоставим текстуру в которую мы будем осуществлять рендеринг.
После этого выведем каждую точку исходного изображения именно как точку. Воспользовавшись операций чтения из текстуры в вершинном шейдере (поддерживаемой начиная с GPU GeForce 6xxx) можно легко прочесть цвет соответствующей точки, определить индекс корзины и изменить координаты данной точки таким образом, чтобы при рендеринге она попадала точно в нужную корзину.
Теперь если задать в качестве режима вывода alpha-blinding с параметрами (GL_ONE, GL_ONE), то вывод каждой точки будет увеличивать значение в соответствующей корзине и в результате вывода всех точек мы получим в массиве корзин интересующие нас количества точек (может быть только отмасштабированные некоторым множителем).
Ниже приводится вершинный шейдер, как раз осуществляющий перенаправление точек по корзинам в зависимости от яркости соответствующего тексела текстуры.
//
// Fast GPU scatter via vertex texture fetch - vertex shader
//
uniform sampler2D srcMap; // map to compute histogram
uniform float numBins; // number of bins (in [0,1] segment]
void main(void)
{
vec4 color = texture2D ( srcMap, gl_MultiTexCoord0.xy ); // get color
float lum = dot ( color.rgb, vec3 ( 0.3, 0.59, 0.11 ) );
float bin = floor ( lum * numBins );
gl_Position = gl_ModelViewProjectionMatrix * vec4 ( bin, 0.0, 0.0, 1.0 );
}
Соответствующий код на С++ приводится на листинге ниже.
#include "libExt.h"
#ifdef MACOSX
#include <GLUT/glut.h>
#else
#include <glut.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include "libTexture.h"
#include "Vector3D.h"
#include "Vector2D.h"
#include "Vector4D.h"
#include "GlslProgram.h"
#include "FrameBuffer.h"
#include "utils.h"
#include "fpTexture.h"
#define TEX_SIZE 256 // width & height of texture
#define NUM_BINS 128
GLenum mapFormat; // texture format used for vertex map
unsigned srcMap; // map with source image
FrameBuffer buffer ( NUM_BINS, 1 );
GlslProgram program;
float res [NUM_BINS*4];
void reshape ( int w, int h );
void display ()
{
float h = 1.0 / (float) TEX_SIZE;
float h2 = h * 0.5;
startOrtho ( buffer.getWidth (), buffer.getHeight () );
glDisable ( GL_DEPTH_TEST );
glEnable ( GL_BLEND );
glBlendFunc ( GL_ONE, GL_ONE );
glBindTexture ( GL_TEXTURE_2D, srcMap );
glPointSize ( 1.0 );
glDisable ( GL_POINT_SMOOTH );
buffer.bind ();
program.bind ();
glClear ( GL_COLOR_BUFFER_BIT );
glBegin ( GL_POINTS );
for ( int i = 0; i < TEX_SIZE; i++ )
for ( int j = 0; j < TEX_SIZE; j++ )
{
glTexCoord2f ( i*h + h2, j*h + h2 );
glVertex2f ( 0, 0 );
}
glEnd ();
program.unbind ();
glBindTexture ( GL_TEXTURE_2D, 0 );
glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, buffer.getColorBuffer () );
glReadPixels ( 0, 0, NUM_BINS, 1, GL_RGBA, GL_FLOAT, res );
glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, 0 );
buffer.unbind ();
endOrtho ();
glutSwapBuffers ();
float sum = 0;
for ( int i = 0; i < NUM_BINS; i++ )
{
sum += res [4*i];
printf ( "%4d ", (int)res [4*i] );
}
printf ( "\nTotal: %d pixels.\n", (int) sum );
exit ( 0 );
}
void reshape ( int w, int h )
{
glViewport ( 0, 0, (GLsizei)w, (GLsizei)h );
glMatrixMode ( GL_PROJECTION );
glLoadIdentity ();
glMatrixMode ( GL_MODELVIEW );
glLoadIdentity ();
}
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_RGB | GLUT_DEPTH );
glutInitWindowSize ( 512, 512 );
// create window
glutCreateWindow ( "Fast GPU scatter via vertex texture fetch" );
// register handlers
glutDisplayFunc ( display );
glutReshapeFunc ( reshape );
glutKeyboardFunc ( key );
init ();
initExtensions ();
assertExtensionsSupported ( "ARB_pixel_buffer_object" );
if ( (mapFormat = fpFormatWithPrecision ( 32 ) ) == 0 )
{
printf ( "Floating-point textures not supported !\n" );
return 1;
}
if ( !FrameBuffer :: isSupported () )
{
printf ( "Fbo not supported\n" );
return 1;
}
if ( !GlslProgram :: isSupported () )
{
printf ( "GLSL not supported\n" );
return 1;
}
srcMap = createTexture2D ( false, "../../Textures/floor.bmp" ); //256*256 pixels
if ( srcMap == 0 )
{
printf ( "Texture floor.bmp not found.\n" );
return 1;
}
unsigned tempMap = buffer.createColorRectTexture ( GL_RGBA, mapFormat );
buffer.create ();
buffer.bind ();
if ( !buffer.attachColorTexture ( GL_TEXTURE_RECTANGLE_ARB, tempMap ) )
printf ( "buffer error with color attachment\n");
if ( !buffer.isOk () )
printf ( "Error with framebuffer\n" );
buffer.unbind ();
if ( !program.loadShaders ( "scatter-1.vsh", "scatter-1.fsh" ) )
{
printf ( "Error loading shaders:\n%s\n", program.getLog ().c_str () );
return 3;
}
program.bind ();
program.setTexture ( "srcMap", 0 );
program.setUniformFloat ( "numBins", NUM_BINS );
program.unbind ();
glutMainLoop ();
return 0;
}
Существует и еще один способ, позволяющий эффективно реализовать построение гистограмм на GPU, к тому же он часто оказывается быстрее. Этот способ заключается в использовании рендеринга в вершинный буфер для создания массива вершин, при этом каждой создаваемой вершине сразу же назначается правильные координаты.
После этого остается только вывести данный вершинный массив как набор точек, используя alpha-blinding.
Ниже приводятся соответствующие вершинный и фрагментный шейдеры, осуществляющие построение такого массива вершин.
//
// Fast GPU scatter via render-to-vertex-buffer - vertex shader
//
void main(void)
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_TexCoord [0] = gl_MultiTexCoord0;
}
//
// Fragment shader GPU scatter via vertex texture fetch - fragment shader
//
uniform sampler2D srcMap; // map to compute histogram
uniform float numBins; // number of bins (in [0,1] segment]
void main ()
{
vec3 clr = texture2D ( srcMap, gl_TexCoord [0].xy ).xyz;
float lum = dot ( clr, vec3 ( 0.3, 0.59, 0.11 ) );
float bin = floor ( lum * numBins );
gl_FragColor = vec4 ( bin, 0.0, 0.0, 1.0 );
}
Ниже приводится фрагмент кода на С++, реализующий данный подход.
#include "libExt.h"
#ifdef MACOSX
#include <GLUT/glut.h>
#else
#include <glut.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include "libTexture.h"
#include "Vector3D.h"
#include "Vector2D.h"
#include "Vector4D.h"
#include "GlslProgram.h"
#include "FrameBuffer.h"
#include "utils.h"
#include "fpTexture.h"
#include "VertexBuffer.h"
#define TEX_SIZE 256 // width & height of texture
#define NUM_BINS 128
GLenum mapFormat; // texture format used for vertex map
unsigned srcMap; // map with source image
FrameBuffer buffer ( NUM_BINS, 1 );
FrameBuffer buffer2 ( TEX_SIZE, TEX_SIZE );
GlslProgram program;
GlslProgram program2;
VertexBuffer * vertexBuffer = NULL; // vertex coordinates
float res [NUM_BINS*4];
float res2[TEX_SIZE*TEX_SIZE*4];
void reshape ( int w, int h );
void createVertexBuffer ()
{
if ( vertexBuffer != NULL )
return;
vertexBuffer = new VertexBuffer ();
vertexBuffer -> bind ( GL_PIXEL_PACK_BUFFER_ARB );
vertexBuffer -> setData ( buffer2.getWidth () * buffer2.getHeight () * 4 * sizeof ( float ), NULL, GL_DYNAMIC_DRAW );
vertexBuffer -> unbind ();
}
//
// create vertex coordinates using r2vb
//
void buildVertices ()
{
createVertexBuffer ();
// load source image map
glActiveTextureARB ( GL_TEXTURE0_ARB );
glBindTexture ( GL_TEXTURE_2D, srcMap );
program.bind (); // perform rendering to FBO texture
buffer2.bind (); // render to vertex buffer
startOrtho ( TEX_SIZE, TEX_SIZE );
drawQuad ( TEX_SIZE, TEX_SIZE );
endOrtho ();
program.unbind ();
// copy vertex data from texture to vertex buffer
vertexBuffer -> bind ( GL_PIXEL_PACK_BUFFER_ARB );
glReadPixels ( 0, 0, TEX_SIZE, TEX_SIZE, GL_RGBA, GL_FLOAT, NULL );
buffer2.unbind ();
vertexBuffer -> getSubData ( 0, TEX_SIZE*TEX_SIZE*4*sizeof(float), res2 );
vertexBuffer -> unbind ();
}
void display ()
{
float h = 1.0 / (float) TEX_SIZE;
float h2 = h * 0.5;
buildVertices ();
startOrtho ( buffer.getWidth (), buffer.getHeight () );
glDisable ( GL_DEPTH_TEST );
glEnable ( GL_BLEND );
glBlendFunc ( GL_ONE, GL_ONE );
glPointSize ( 1.0 );
glDisable ( GL_POINT_SMOOTH );
glEnableClientState ( GL_VERTEX_ARRAY );
vertexBuffer -> bind ( GL_ARRAY_BUFFER );
glVertexPointer ( 4, GL_FLOAT, 0, NULL );
program2.bind ();
buffer.bind ();
glClear ( GL_COLOR_BUFFER_BIT );
glDrawArrays ( GL_POINTS, 0, TEX_SIZE*TEX_SIZE );
glBindTexture ( GL_TEXTURE_2D, 0 );
glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, buffer.getColorBuffer () );
glReadPixels ( 0, 0, NUM_BINS, 1, GL_RGBA, GL_FLOAT, res );
glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, 0 );
buffer.unbind ();
program2.unbind ();
endOrtho ();
glutSwapBuffers ();
float sum = 0;
for ( int i = 0; i < NUM_BINS; i++ )
{
sum += res [4*i];
printf ( "%4d ", (int)res [4*i] );
}
printf ( "\nTotal: %d pixels.\n", (int) sum );
exit ( 0 );
}
void reshape ( int w, int h )
{
glViewport ( 0, 0, (GLsizei)w, (GLsizei)h );
glMatrixMode ( GL_PROJECTION );
glLoadIdentity ();
glMatrixMode ( GL_MODELVIEW );
glLoadIdentity ();
}
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_RGB | GLUT_DEPTH );
glutInitWindowSize ( 512, 512 );
// create window
glutCreateWindow ( "Fast GPU scatter via r2vb" );
// register handlers
glutDisplayFunc ( display );
glutReshapeFunc ( reshape );
glutKeyboardFunc ( key );
init ();
initExtensions ();
assertExtensionsSupported ( "ARB_pixel_buffer_object" );
if ( (mapFormat = fpFormatWithPrecision ( 32 ) ) == 0 )
{
printf ( "Floating-point textures not supported !\n" );
return 1;
}
if ( !FrameBuffer :: isSupported () )
{
printf ( "Fbo not supported\n" );
return 1;
}
if ( !GlslProgram :: isSupported () )
{
printf ( "GLSL not supported\n" );
return 1;
}
srcMap = createTexture2D ( false, "../../Textures/floor.bmp" ); //256*256 pixels
if ( srcMap == 0 )
{
printf ( "Texture floor.bmp not found.\n" );
return 1;
}
unsigned tempMap = buffer.createColorRectTexture ( GL_RGBA, mapFormat );
buffer.create ();
buffer.bind ();
if ( !buffer.attachColorTexture ( GL_TEXTURE_RECTANGLE_ARB, tempMap ) )
printf ( "buffer error with color attachment\n");
if ( !buffer.isOk () )
printf ( "Error with framebuffer\n" );
buffer.unbind ();
tempMap = buffer2.createColorRectTexture ( GL_RGBA, mapFormat );
buffer2.create ();
buffer2.bind ();
if ( !buffer2.attachColorTexture ( GL_TEXTURE_RECTANGLE_ARB, tempMap ) )
printf ( "buffer error with color attachment\n");
if ( !buffer2.isOk () )
printf ( "Error with framebuffer\n" );
buffer2.unbind ();
if ( !program.loadShaders ( "r2vb.vsh", "r2vb.fsh" ) )
{
printf ( "Error loading shaders:\n%s\n", program.getLog ().c_str () );
return 3;
}
if ( !program2.loadShaders ( "scatter2.vsh", "scatter-2.fsh" ) )
{
printf ( "Error loading shaders:\n%s\n", program.getLog ().c_str () );
return 3;
}
program.bind ();
program.setTexture ( "srcMap", 0 );
program.setUniformFloat ( "numBins", NUM_BINS );
program.unbind ();
glutMainLoop ();
return 0;
}
При использовании данных подходов следует обратить внимание на точность представление числе в текстуре, в которой происходит накопление результатов - если использовать обычную текстуру типа GL_RBA8, то 8 бит дадут нам только 256 различных значений счетчика, после чего происходит переполнение.
Существует два варианта борьбы с этим два подхода. Первый вариант заключается в разбиении изображения на ряд блоков и одновременно строим гистограммы для каждого такого блока и в конце объединяем счетчики по блокам.
Второй подход заключается в использовании форматов текстур с большей разрешающей способностью - так использование 16-битовых floating point чисел позволяет получать до 2048 различных значения счетчика, а если использовать GPU серии GeForce 8xxx, поддерживающие alpha blending в 32-битовые целочисленные и floating point-форматы, то проблема фактически полностью решается (правда ценой более высоких требований к используемому GPU).
Данная статья написана по материалам статьи Efficient Histogram Generation Using Scattering on GPUs.
По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows, Linux и Mac OS X.