Главная Статьи Ссылки Скачать Скриншоты Юмор Почитать Tools Проекты Обо мне Гостевая Форум |
И OpenGL и CUDA работают с данными, находящимися в памяти GPU. Поэтому очень удобно иметь механизм, позволяющий им использовать эти данные совместно, без необходимости копирования в память CPU. CUDA предоставляет подобный механизм - CUDA OpenGL interoperability. Начиная с версии 3.0 в CUDA этот механизм взаимодействия с OpenGL был изменен и расширен.
Ключевым понятием в этом механизме является понятие графического ресурса, который может быть отображен в память CUDA, и обрабатываться средствами CUDA.
CUDA позволяет отображать в свое адресное пространство следующие объекты OpenGL - буфера, текстуры и рендербуферы. При этом буфера OpenGL отображаются в глобальную память, а текстуры и рендербуферы - в cudaArray.
Первым шагом взаимодействия CUDA и OpenGL является вызов функции cudaGLSetDevice (который заменяет вызов cudaSetDevice).
cudaError_t cudaGLSetGLDevice ( int device );
Прежде чем графический ресурс может быть использован в CUDA его необходимо зарегистрировать. Обратите внимание, что регистрация - это дорогостоящая операция, поэтому лучше всего для каждого ресурса выполнить ее всего один раз - в начале выполнения программы.
Для регистрации текстуры и рендербуфера используется функция cudaGLRegisterImage
cudaError_t cudaGraphicsGLRegisterImage ( struct cudaGraphicsResource ** resource, GLuint image, GLenum target, unsigned int flags );
Зарегистрированную текстуру можно отобразить в cudaArray cudaGraphicsSubResourceGetMappedArray:
cudaError_t cudaGraphicsSubResourceGetMappedArray ( cudaArray ** array, struct cudaGraphicsResource * resource, unsigned int arrayIndex, unsigned int mipLevel );
Обратите внимание, что пока CUDA поддерживает только текстуры с floating-point-компонентами (например, GL_RGBA_FLOAT32) и текстуры с ненормализованными целыми компонентами (например, GLRGBA8UI). Стандартные нормализованные целочисленные текстуры ( такие как GL_TGBA8) пока не поддерживаются.
Буфера OpenGL регистрируются при помощи функции cudaGraphicsGLRegisterBuffer:
cudaError_t cudaGraphicsGLRegisterBuffer ( struct cudaGraphicsResource ** resource, GLuint buffer, unsigned int flags );
Буфера отображаются в глобальную память CUDA.
При регистрации ресурса можно при помощи параметра flags сообщить CUDA о том, как именно будет использоваться данный ресурс, что может сделать работу с ним более эффективной. На данный момент поддерживаются следующие типы флагов:
После завершения всей работы с ресурсом, его следует "разрегистрировать" при помощи функции cudaGraphicsUnregisterResource:
cudaError_t cudaGraphicsUnregisterResource ( struct cudaGraphicsResource * resource );
Для непосредственной работы с уже зарегистрированным ресурсом необходимо сначала вызвать функцию cudaGraphicsMapResources, для отображения ресурса в память CUDA. После этого можно получить указатель для непосредственной работы с данными ресурса. Когда CUDA закончит работу с ресурсом, необходимо завершить отображение ресурса в память CUDA при помощи вызова функции cudaGraphicsUnmapResources.
Обратите внимание, что пока ресурс отображен в память CUDA доступ к нему из OpenGL невозможен (точнее дает непредсказуемый результат).
cudaError_t cudaGraphicsMapResources ( int count, struct cudaGraphicsResource ** resources, cudaStream_t stream ); cudaError_t cudaGraphicsUnmapResources ( int count, struct cudaGraphicsResource ** resources, cudaStream_t stream );
Отобразив ресурс в адресное пространство CUDA следует использовать функции cudaGraphicsResourceGetMappedPointer и cudaGraphicsSubResourceGetMappedArray для получения указателя, с которым может работать CUDA.
cudaError_t cudaGraphicsResourceGetMappedPointer ( void ** devPtr, size_t * size, struct cudaGraphicsResource * resource ); cudaError_t cudaGraphicsSubResourceGetMappedArray ( cudaArray ** array, struct cudaGraphicsResource * resource, unsigned int arrayIndex, unsigned int mipLevel );
Параметр arrayIndex служит для выбора грани для кубических текстур и текстурных массивов. Параметр mipLevel позволяет задать конкретный уровень в пирамиде mipmap-текстур.
Для упрощения дальнейшей работы с ресурсами OpenGL удобно сразу "завернуть" их в классы - CudaGlBuffer и CudaGlImage. Эти классы полностью инкапсулируют всю работу с ресурсами, включая регистрация/дерегистрацию (в конструкторе и деструкторе). Для непосредственного отображения ресурса в адресное пространство CUDA служат методы mapResource unmapResource.
После вызова mapResource для непосредственного доступа к данным ресурса их CUDA служат методы mappedPointer (для буферов OpenGL) и mappedArray (для текстур и рендербуферов OpenGL).
#ifndef __CUDA__GL_RESOURCE__ #define __CUDA__GL_RESOURCE__ #include <cuda.h> #include <cuda_runtime_api.h> #include <cuda_gl_interop.h> #include <GL/gl.h> #include "VertexBuffer.h" class CudaGlBuffer // VBO { cudaGraphicsResource * resource; VertexBuffer * buffer; GLenum target; public: CudaGlBuffer ( VertexBuffer * buf, GLenum theTarget, unsigned int flags = cudaGraphicsMapFlagsWriteDiscard ) // cudaGraphicsMapFlagsReadOnly { buffer = buf; target = theTarget; buffer -> bind ( target ); cudaGraphicsGLRegisterBuffer ( &resource, buffer -> getId (), flags ); buffer -> unbind (); } ~CudaGlBuffer () { cudaGraphicsUnregisterResource ( resource ); } bool mapResource ( cudaStream_t stream = 0 ) { return cudaGraphicsMapResources ( 1, &resource, stream ) == cudaSuccess; } bool unmapResource ( cudaStream_t stream = 0 ) { return cudaGraphicsUnmapResources ( 1, &resource, stream ) == cudaSuccess; } void * mappedPointer ( size_t& numBytes ) const { void * ptr; if ( cudaGraphicsResourceGetMappedPointer ( &ptr, &numBytes, resource ) != cudaSuccess ) return NULL; return ptr; } GLuint getId () const { return buffer -> getId (); } GLenum getTarget () const { return target; } cudaGraphicsResource * getResource () const { return resource; } }; class CudaGlImage // texture or renderbuffer { GLuint image; GLenum target; cudaGraphicsResource * resource; public: CudaGlImage ( GLuint theImage, GLenum theTarget, unsigned int flags = cudaGraphicsMapFlagsWriteDiscard ) // cudaGraphicsMapFlagsReadOnly, cudaGraphicsMapFlagsNone { image = theImage; target = theTarget; cudaGraphicsGLRegisterImage ( &resource, image, target, flags ); } ~CudaGlImage () { cudaGraphicsUnregisterResource ( resource ); } bool mapResource ( cudaStream_t stream = 0 ) { return cudaGraphicsMapResources ( 1, &resource, stream ) == cudaSuccess; } bool unmapResource ( cudaStream_t stream = 0 ) { return cudaGraphicsUnmapResources ( 1, &resource, stream ) == cudaSuccess; } cudaArray * mappedArray ( unsigned int index = 0, unsigned int mipLevel = 0 ) const { cudaArray * array; if ( cudaGraphicsSubResourceGetMappedArray ( &array, resource, index, mipLevel ) != cudaSuccess ) return NULL; return array; } GLuint getImage () const { return image; } GLenum getTarget () const { return target; } }; #endif
Далее мы рассмотрим несколько примеров взаимодействия CUDA и OpenGL. Простейшим из этих примеров будет создание и рендеринг анимированной водной поверхности. Координаты вершин и значения нормали в них для каждого кадра будем рассчитывать при помощи ядра CUDA, а непосредственно рендеринг осуществлять средствами OpenGL.
Нам понадобятся два вершинных буфера (VBO) - один для хранения вершин, второй - для хранения нормалей. На следующем листинге приводится само ядро и вызывающая его функция:
#define TEX_SIZE 512 #define k1 2.345431f #define k2 1.12312312f #define k3 3.784728394f #define scale 0.03f __global__ void animateKernel ( float4 * pos, float4 * norm, int w, int wh, int hh, float time ) { int ix = blockIdx.x * blockDim.x + threadIdx.x; int iy = blockIdx.y * blockDim.y + threadIdx.y; int i = iy * w + ix; float x = (ix - wh) * 0.03f; float y = (iy - hh) * 0.03f; float v1 = x * 0.47f + y * 1.3f + k1 * time; float v2 = x * 2.9f + y * 3.1f + k2 * time; float v3 = x * 5.577f - y * 7.57f + k3 * time; float nx = scale * ( 2.77f * 0.47f * __cosf ( v1 ) + 2.9f * __cosf ( v2 ) + 0.357f * 5.577f * __cosf ( v3 ) ); float ny = scale * ( 2.77f * 1.3f * __cosf ( v1 ) + 3.1f * __cosf ( v2 ) - 0.357f * 7.57f * __cosf ( v3 ) ); float nz = 1.0f / sqrtf ( nx * nx + ny * ny ); pos [i] = make_float4 ( x, y, scale * ( 2.77f * __sinf ( v1 ) + __sinf ( v2 ) + 0.357f * __sinf ( v3 ) ), 1.0f ); norm [i] = make_float4 ( nx, ny, nz, 0.0f ); } extern "C" void buildVertices ( float4 * v, float4 * n, int w, int h, float time ) { dim3 threads ( 16, 16 ); dim3 blocks ( w / 16, h / 16 ); animateKernel <<<blocks, threads>>> ( v, n, w, w / 2, h / 2, time ); cudaThreadSynchronize (); }
Ядро использует аналитическое задание поверхности воды при помощи суммы нескольких гармоник. Вектор нормали также вычисляется аналитически.
Ниже приводится основной код на С++, осуществляющий рендеринг анимированной поверхности воды. Обратите внимание, что ряд постоянно используемых во многих примерах функций удалены из листинга (полный текст всех программ доступен по ссылке в конце статьи).
#include "libExt.h" #ifdef MACOSX #include <GLUT/glut.h> #else #include <glut.h> #endif #include <stdio.h> #include <stdlib.h> #include "libTexture.h" #include "TypeDefs.h" #include "Vector3D.h" #include "Vector2D.h" #include "boxes.h" #include "GlslProgram.h" #include "CudaGlResource.h" #define TEX_SIZE 512 // width & height of texture Vector3D eye ( -10, -10, 5 ); // camera position Vector3D light ( 5, 0, 4 ); // light position float angle = 0; Vector3D rot ( 0, 0, 0 ); int mouseOldX = 0; int mouseOldY = 0; float waveScale = 0.2; float time = 0.0f; unsigned reflectionMap; // cubic map with reflection CudaGlBuffer * pos = NULL; CudaGlBuffer * norm = NULL; VertexBuffer * vertexBuffer; // vertex coordinates VertexBuffer * normalBuffer; // normal buffer VertexBuffer * indexBuffer; // vertex index buffer GlslProgram program; extern "C" void buildVertices ( float4 * v, float4 * n, int w, int h, float time ); void createIndexBuffer () { int * data = (int *) malloc ( (TEX_SIZE-1)*(TEX_SIZE-1)*6*sizeof (int) ); int k = 0; for ( int i = 0; i < TEX_SIZE - 1; i++ ) for ( int j = 0; j < TEX_SIZE - 1; j++ ) { data [k] = i + TEX_SIZE*j; // first triangle (i,j)-(i+1,j) - (i,j+1) data [k+1] = i + TEX_SIZE*j + 1; data [k+2] = i + TEX_SIZE*j + TEX_SIZE; data [k+3] = i + TEX_SIZE*j + 1; // second triangle (i+1,j)-(i+1,j+1) - (i,j+1) data [k+4] = i + TEX_SIZE*j + TEX_SIZE + 1; data [k+5] = i + TEX_SIZE*j + TEX_SIZE; k += 6; } indexBuffer = new VertexBuffer (); indexBuffer -> bind ( GL_ELEMENT_ARRAY_BUFFER_ARB ); indexBuffer -> setData ( (TEX_SIZE - 1) * (TEX_SIZE - 1) * 6 * sizeof ( int ), data, GL_STATIC_DRAW ); indexBuffer -> unbind (); free ( data ); } void createVertexBuffer () { vertexBuffer = new VertexBuffer (); vertexBuffer -> bind ( GL_PIXEL_PACK_BUFFER_ARB ); vertexBuffer -> setData ( TEX_SIZE * TEX_SIZE * 4 * sizeof ( float ), NULL, GL_DYNAMIC_DRAW ); vertexBuffer -> unbind (); } void createNormalBuffer () { normalBuffer = new VertexBuffer (); normalBuffer -> bind ( GL_PIXEL_PACK_BUFFER_ARB ); normalBuffer -> setData ( TEX_SIZE * TEX_SIZE * 4 * sizeof ( float ), NULL, GL_DYNAMIC_DRAW ); normalBuffer -> unbind (); } void display () { float scale = 1.0f; size_t vSize, nSize; pos -> mapResource (); norm -> mapResource (); buildVertices ( (float4 *) pos -> mappedPointer ( vSize ), (float4 *) norm -> mappedPointer ( nSize ), TEX_SIZE, TEX_SIZE, time ); pos -> unmapResource (); norm -> unmapResource (); glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glMatrixMode ( GL_MODELVIEW ); glPushMatrix (); glRotatef ( rot.x, 1, 0, 0 ); glRotatef ( rot.y, 0, 1, 0 ); glRotatef ( rot.z, 0, 0, 1 ); glScalef ( scale, scale, 2 ); glActiveTextureARB ( GL_TEXTURE0_ARB ); glBindTexture ( GL_TEXTURE_CUBE_MAP, reflectionMap ); glPushClientAttrib ( GL_CLIENT_VERTEX_ARRAY_BIT ); glEnableClientState ( GL_VERTEX_ARRAY ); vertexBuffer -> bind ( GL_ARRAY_BUFFER_ARB ); glVertexPointer ( 4, GL_FLOAT, 0, NULL ); glEnableClientState ( GL_TEXTURE_COORD_ARRAY ); normalBuffer -> bind ( GL_ARRAY_BUFFER_ARB ); glTexCoordPointer ( 4, GL_FLOAT, 0, NULL ); indexBuffer -> bind ( GL_ELEMENT_ARRAY_BUFFER_ARB ); glIndexPointer ( GL_UNSIGNED_INT, 0, 0 ); program.bind (); glDrawElements ( GL_TRIANGLES, (TEX_SIZE-1)*(TEX_SIZE-1)*6, GL_UNSIGNED_INT, 0 ); program.unbind (); glPopClientAttrib (); glPopMatrix (); glutSwapBuffers (); } int main ( int argc, char * argv [] ) { // initialize glut glutInit ( &argc, argv ); glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH ); glutInitWindowSize ( 640, 480 ); // create window glutCreateWindow ( "OpenGL/CUDA interoperability: vertex buffers" ); // register handlers glutDisplayFunc ( display ); glutReshapeFunc ( reshape ); glutKeyboardFunc ( key ); glutMouseFunc ( mouse ); glutMotionFunc ( motion ); glutIdleFunc ( animate ); init (); initExtensions (); if ( !GlslProgram :: isSupported () ) { printf ( "GLSL not supported\n" ); return 1; } const char * faces [6] = { "../../Textures/Cubemaps/skyrt.bmp", "../../Textures/Cubemaps/skylf.bmp", "../../Textures/Cubemaps/skydn.bmp", "../../Textures/Cubemaps/skyup.bmp", "../../Textures/Cubemaps/skybk.bmp", "../../Textures/Cubemaps/skyfr.bmp", }; if ( !program.loadShaders ( "water-draw.vsh", "water-draw.fsh" ) ) { printf ( "Error loading water-draw shaders:\n%s\n", program.getLog ().c_str () ); return 3; } program.bind (); program.setTexture ( "reflectionMap", 0 ); program.setUniformVector ( "eye", eye ); program.unbind (); reflectionMap = createCubeMap ( true, faces ); // initialize CUDA device for OpenGL cudaGLSetGLDevice ( 0 ); createVertexBuffer (); createNormalBuffer (); createIndexBuffer (); pos = new CudaGlBuffer ( vertexBuffer, GL_ARRAY_BUFFER ); norm = new CudaGlBuffer ( normalBuffer, GL_ARRAY_BUFFER ); glutMainLoop (); return 0; }
Далее мы рассмотрим image processing средствами CUDA. При этом для записи результатов в текстуру возможны несколько путей. Можно осуществить запись результатов ядром в обычный массив в глобальной памяти, а затем уже скопировать данные в cudaArray, соответствующий выходной текстуре.
Второй вариант заключается в использовании расширения ARB_pixel_buffer_object, позволяющего копировать данные из вершинного буфера прямо в текстуру. В этом случае создается специальный вершинный буфер, который отображается в глобальную память CUDA. Ядро записывает в него результат работы. Далее через расширение ARB_pixel_buffer_object производится запись данных из этого вершинного буфера в текстуру.
Ниже приводится соответствующий код на CUDA для первого случая - вся работа идет через cudaArray. Поскольку ядро не может напрямую писать в cudaArray, полученный отображением текстуры, то ядро выводит результат в заранее выделенный блок глобальной памяти, а затем при помощи функции cudaMemcpyToArray, копирует результат в cudaArray, соответствующий выходной текстуре.
// // // CUDA kernal to convert image to grey-scale , keep intact one specific color hue // #define EPS 0.006f typedef unsigned char byte; texture<uchar4, 2, cudaReadModeElementType> inTex; __device__ float3 rgbToHsv ( const float3 c ) { float mn = min ( min ( c.x, c.y ), c.z ); float mx = max ( max ( c.x, c.y ), c.z ); float delta = mx - mn; float h, s; if ( mx > 0.001f ) { s = delta / mx; if ( c.x == mx ) h = ( c.y - c.z ) / delta; else if ( c.y == mx ) h = 2.0f + ( c.z - c.x ) / delta; else h = 4.0f + ( c.x - c.y ) / delta; } else { s = 0.0f; h = 0.0f; } return make_float3 ( h / 6.0f, s, mx ); } __global__ void ppKernel ( uchar4 * out, int w, int h ) { int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; int i = y * w + x; uchar4 res = tex2D ( inTex, x, y ); float scale = 1.0f / 255.0f; float3 clr = make_float3 ( res.x * scale, res.y * scale, res.z * scale ); float3 hsv = rgbToHsv ( clr ); // convert to HSV float l = 0.3f * clr.x + 0.59f * clr.y + 0.11f * clr.z; // intensity of a color if ( abs ( hsv.x - 0.077f ) < EPS ) // hit hue with enough precision => keep color out [i] = res; else // use greyscale image { byte r = (byte)( 255.0f * l); out [i] = make_uchar4 ( r, r, r, 255 ); } } extern "C" void doPostprocess ( cudaArray * inMap, cudaArray * outMap, uchar4 * buf, int pitch, int w, int h ) { cudaBindTextureToArray ( inTex, inMap ); dim3 threads ( 16, 16 ); dim3 blocks ( w / threads.x, h / threads.y); ppKernel <<<blocks, threads>>> ( buf, w, h ); cudaMemcpyToArray ( outMap, 0, 0, buf, w * h * sizeof(uchar4), cudaMemcpyDeviceToDevice ); cudaThreadSynchronize (); cudaUnbindTexture ( inTex ); }
На следующем листинге приводится соответствующий код на С++. Обратите внимание, что поскольку мы использование специальных шейдеров, служащий для записи и чтения в текстуры с ненормализованным целочисленным форматом компонент.
#include "libExt.h" #ifdef MACOSX #include <GLUT/glut.h> #else #include <glut.h> #endif #include <stdio.h> #include <stdlib.h> #include "libTexture.h" #include "TypeDefs.h" #include "Vector3D.h" #include "Vector2D.h" #include "boxes.h" #include "FrameBuffer.h" #include "GlslProgram.h" #include "CudaGlResource.h" Vector3D eye ( -0.5, -0.5, 1.5 ); // camera position unsigned decalMap; // decal (diffuse) texture unsigned stoneMap; unsigned teapotMap; unsigned screenMap; unsigned outMap; float angle = 0; float rot = 0; bool useFilter = true; CudaGlImage * src = NULL; CudaGlImage * dst = NULL; uchar4 * buf = NULL; FrameBuffer buffer ( 640, 480, FrameBuffer :: depth32 ); GlslProgram program1; // render to uchar4 texture GlslProgram program2; // render to normalized color buffer void renderToBuffer (); void postProcess (); extern "C" void doPostprocess ( cudaArray * inArray, cudaArray * outArray, uchar4 * buf, int pitch, int w, int h ); void displayBoxes () { glMatrixMode ( GL_MODELVIEW ); glPushMatrix (); glRotatef ( rot, 0, 0, 1 ); drawBox ( Vector3D ( -5, -5, 0 ), Vector3D ( 10, 10, 3 ), stoneMap, false ); drawBox ( Vector3D ( 3, 2, 0.5 ), Vector3D ( 1, 2, 2 ), decalMap ); glBindTexture ( GL_TEXTURE_2D, teapotMap ); glTranslatef ( 0.2, 1, 1.5 ); glRotatef ( angle * 45.3, 1, 0, 0 ); glRotatef ( angle * 57.2, 0, 1, 0 ); glutSolidTeapot ( 0.3 ); glPopMatrix (); } void display () { renderToBuffer (); postProcess (); glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); startOrtho (); program2.bind (); glEnable ( GL_TEXTURE_2D ); glBindTexture ( GL_TEXTURE_2D, outMap ); glBegin ( GL_QUADS ); glTexCoord2f ( 0, 0 ); glVertex2f ( 0, 0 ); glTexCoord2f ( 1, 0 ); glVertex2f ( buffer.getWidth (), 0 ); glTexCoord2f ( 1, 1 ); glVertex2f ( buffer.getWidth (), buffer.getHeight () ); glTexCoord2f ( 0, 1 ); glVertex2f ( 0, buffer.getHeight () ); glEnd (); glDisable ( GL_TEXTURE_2D ); glBindTexture ( GL_TEXTURE_2D, 0 ); program2.unbind (); endOrtho (); glutSwapBuffers (); } void renderToBuffer () { glBindTexture ( GL_TEXTURE_2D, 0 ); program1.bind (); buffer.bind (); glClearColor ( 0, 0, 0, 1 ); glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); reshape ( buffer.getWidth (), buffer.getHeight () ); displayBoxes (); buffer.unbind (); program1.unbind (); } void postProcess () { src -> mapResource (); dst -> mapResource (); cudaArray * inArray = src -> mappedArray (); cudaArray * outArray = dst -> mappedArray (); doPostprocess ( inArray, outArray, buf, 0, buffer.getWidth (), buffer.getHeight () ); src -> unmapResource (); dst -> unmapResource (); } GLuint createTextureDst ( int w, int h ) { GLuint tex; glGenTextures ( 1, &tex ); glBindTexture ( GL_TEXTURE_2D, tex ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); glTexImage2D ( GL_TEXTURE_2D, 0, GL_RGBA8UI_EXT, w, h, 0, GL_RGBA_INTEGER_EXT, GL_UNSIGNED_BYTE, NULL ); return tex; } int main ( int argc, char * argv [] ) { // initialize glut glutInit ( &argc, argv ); glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH ); glutInitWindowSize ( buffer.getWidth (), buffer.getHeight () ); // create window glutCreateWindow ( "OpenGL/CUDA interoperability: postprocessing" ); // register handlers glutDisplayFunc ( display ); glutReshapeFunc ( reshape ); glutKeyboardFunc ( key ); glutSpecialFunc ( specialKey ); glutIdleFunc ( animate ); init (); initExtensions (); assertExtensionsSupported ( "GL_ARB_shading_language_100 GL_ARB_shader_objects EXT_framebuffer_object ARB_texture_rectangle" ); outMap = createTextureDst ( buffer.getWidth (), buffer.getHeight () ); decalMap = createTexture2D ( true, "../../Textures/oak.bmp" ); stoneMap = createTexture2D ( true, "../../Textures/block.bmp" ); teapotMap = createTexture2D ( true, "../../Textures/Oxidated.jpg" ); screenMap = buffer.createColorTexture ( GL_RGBA_INTEGER_EXT, GL_RGBA8UI_EXT ); buffer.create (); buffer.bind (); buffer.attachColorTexture ( GL_TEXTURE_2D, screenMap ); if ( !buffer.isOk () ) printf ( "Error with framebuffer\n" ); buffer.unbind (); if ( !program1.loadShaders ( "draw.vsh", "draw-uint.fsh" ) ) { printf ( "Error loading draw-uint shaders:\n%s\n", program1.getLog ().c_str () ); return 3; } program1.bind (); program1.setTexture ( "texImage", 0 ); program1.unbind (); if ( !program2.loadShaders ( "draw.vsh", "draw-color.fsh" ) ) { printf ( "Error loading draw-uint shaders:\n%s\n", program2.getLog ().c_str () ); return 3; } program2.bind (); program2.setTexture ( "texImage", 0 ); program2.unbind (); // initialize CUDA device for OpenGL cudaGLSetGLDevice ( 0 ); cudaMalloc ( (void **) &buf, buffer.getWidth () * buffer.getHeight () * 4 ); src = new CudaGlImage ( buffer.getColorBuffer (), GL_TEXTURE_2D, cudaGraphicsMapFlagsNone ); dst = new CudaGlImage ( outMap, GL_TEXTURE_2D, cudaGraphicsMapFlagsNone ); glutMainLoop (); return 0; }
В случае использования расширения ARB_pixel_buffer_object код на CUDA получается даже проще - ядро сразу записывает результат в глобальную память, только это уже не заранее выделенный блок глобальной памяти, а отображенный вершинный буфер.
// // CUDA kernal to convert image to grey-scale , keep intact one specific color hue // #define EPS 0.006f typedef unsigned char byte; texture<uchar4, 2, cudaReadModeElementType> inTex; __device__ float3 rgbToHsv ( const float3 c ) { float mn = min ( min ( c.x, c.y ), c.z ); float mx = max ( max ( c.x, c.y ), c.z ); float delta = mx - mn; float h, s; if ( mx > 0.001f ) { s = delta / mx; if ( c.x == mx ) h = ( c.y - c.z ) / delta; else if ( c.y == mx ) h = 2.0f + ( c.z - c.x ) / delta; else h = 4.0f + ( c.x - c.y ) / delta; } else { s = 0.0f; h = 0.0f; } return make_float3 ( h / 6.0f, s, mx ); } __global__ void ppKernel ( int w, int h, uchar4 * outPtr ) { int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; int i = y * w + x; uchar4 res = tex2D ( inTex, x, y ); float scale = 1.0f / 255.0f; float3 clr = make_float3 ( res.x * scale, res.y * scale, res.z * scale ); float3 hsv = rgbToHsv ( clr ); // convert to HSV float l = 0.3f * clr.x + 0.59f * clr.y + 0.11f * clr.z; // intensity of a color if ( abs ( hsv.x - 0.077f ) < EPS ) // hit hue with enough precision => keep color outPtr [i] = res; else // use greyscale image { byte r = (byte)(255.0f * l); outPtr [i] = make_uchar4 ( r, r, r, 255 ); //make_uchar4 ( x, y, 100, 100 ); } } extern "C" void doPostprocess ( cudaArray * inMap, uchar4 * outPtr, int w, int h ) { cudaBindTextureToArray ( inTex, inMap ); dim3 threads ( 16, 16 ); dim3 blocks ( w / threads.x, h / threads.y); ppKernel <<<blocks, threads>>> ( w, h, outPtr ); cudaThreadSynchronize (); cudaUnbindTexture ( inTex ); }
Ниже приводится соответствующий кода на С++.
#include "libExt.h" #ifdef MACOSX #include <GLUT/glut.h> #else #include <glut.h> #endif #include <stdio.h> #include <stdlib.h> #include "libTexture.h" #include "TypeDefs.h" #include "Vector3D.h" #include "Vector2D.h" #include "boxes.h" #include "FrameBuffer.h" #include "GlslProgram.h" #include "CudaGlResource.h" Vector3D eye ( -0.5, -0.5, 1.5 ); // camera position unsigned decalMap; // decal (diffuse) texture unsigned stoneMap; unsigned teapotMap; unsigned screenMap; unsigned outMap; float angle = 0; float rot = 0; bool useFilter = true; CudaGlImage * src = NULL; CudaGlBuffer * out = NULL; VertexBuffer * imageBuffer; // vertex coordinates FrameBuffer buffer ( 640, 480, FrameBuffer :: depth32 ); GlslProgram program1; void renderToBuffer (); void postProcess (); extern "C" void doPostprocess ( cudaArray * inArray, uchar4 * outPtr, int w, int h ); void createVertexBuffer () { imageBuffer = new VertexBuffer (); imageBuffer -> bind ( GL_PIXEL_PACK_BUFFER_ARB ); imageBuffer -> setData ( buffer.getWidth () * buffer.getHeight () * 4, NULL, GL_DYNAMIC_DRAW ); imageBuffer -> unbind (); } void displayBoxes () { glMatrixMode ( GL_MODELVIEW ); glPushMatrix (); glRotatef ( rot, 0, 0, 1 ); drawBox ( Vector3D ( -5, -5, 0 ), Vector3D ( 10, 10, 3 ), stoneMap, false ); drawBox ( Vector3D ( 3, 2, 0.5 ), Vector3D ( 1, 2, 2 ), decalMap ); glBindTexture ( GL_TEXTURE_2D, teapotMap ); glTranslatef ( 0.2, 1, 1.5 ); glRotatef ( angle * 45.3, 1, 0, 0 ); glRotatef ( angle * 57.2, 0, 1, 0 ); glutSolidTeapot ( 0.3 ); glPopMatrix (); } void display () { renderToBuffer (); postProcess (); glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); startOrtho (); glEnable ( GL_TEXTURE_RECTANGLE_ARB ); glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, outMap ); glBegin ( GL_QUADS ); glTexCoord2f ( 0, 0 ); glVertex2f ( 0, 0 ); glTexCoord2f ( buffer.getWidth (), 0 ); glVertex2f ( buffer.getWidth (), 0 ); glTexCoord2f ( buffer.getWidth (), buffer.getHeight () ); glVertex2f ( buffer.getWidth (), buffer.getHeight () ); glTexCoord2f ( 0, buffer.getHeight () ); glVertex2f ( 0, buffer.getHeight () ); glEnd (); glDisable ( GL_TEXTURE_RECTANGLE_ARB ); glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, 0 ); endOrtho (); glutSwapBuffers (); } void renderToBuffer () { glBindTexture ( GL_TEXTURE_2D, 0 ); program1.bind (); buffer.bind (); glClearColor ( 0, 0, 0, 1 ); glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); reshape ( buffer.getWidth (), buffer.getHeight () ); displayBoxes (); buffer.unbind (); program1.unbind (); } void postProcess () { src -> mapResource (); out -> mapResource (); size_t size; cudaArray * inArray = src -> mappedArray (); uchar4 * res = (uchar4 *)out -> mappedPointer ( size ); doPostprocess ( inArray, res, buffer.getWidth (), buffer.getHeight () ); src -> unmapResource (); out -> unmapResource (); imageBuffer -> bind ( GL_PIXEL_UNPACK_BUFFER_ARB ); glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, outMap ); glTexSubImage2D ( GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, buffer.getWidth (), buffer.getHeight (), GL_RGBA, GL_UNSIGNED_BYTE, NULL ); imageBuffer -> unbind (); glBindBufferARB ( GL_PIXEL_UNPACK_BUFFER_ARB, 0 ); glBindBufferARB ( GL_PIXEL_PACK_BUFFER_ARB, 0 ); } GLuint createTextureDst ( int w, int h ) { GLuint tex; glGenTextures ( 1, &tex ); glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, tex ); glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); glTexImage2D ( GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, 0 ); return tex; } int main ( int argc, char * argv [] ) { // initialize glut glutInit ( &argc, argv ); glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH ); glutInitWindowSize ( buffer.getWidth (), buffer.getHeight () ); // create window glutCreateWindow ( "OpenGL/CUDA interoperability: postprocessing" ); // register handlers glutDisplayFunc ( display ); glutReshapeFunc ( reshape ); glutKeyboardFunc ( key ); glutSpecialFunc ( specialKey ); glutIdleFunc ( animate ); init (); initExtensions (); decalMap = createTexture2D ( true, "../../Textures/oak.bmp" ); stoneMap = createTexture2D ( true, "../../Textures/block.bmp" ); teapotMap = createTexture2D ( true, "../../Textures/Oxidated.jpg" ); outMap = createTextureDst ( buffer.getWidth (), buffer.getHeight () ); screenMap = buffer.createColorTexture ( GL_RGBA ); buffer.create (); buffer.bind (); buffer.attachColorTexture ( GL_TEXTURE_2D, screenMap ); if ( !buffer.isOk () ) printf ( "Error with framebuffer\n" ); buffer.unbind (); if ( !program1.loadShaders ( "draw.vsh", "draw.fsh" ) ) { printf ( "Error loading draw shaders:\n%s\n", program1.getLog ().c_str () ); return 3; } program1.bind (); program1.setTexture ( "texImage", 0 ); program1.unbind (); // initialize CUDA device for OpenGL cudaGLSetGLDevice ( 0 ); src = new CudaGlImage ( buffer.getColorBuffer (), GL_TEXTURE_2D, cudaGraphicsMapFlagsNone ); createVertexBuffer (); out = new CudaGlBuffer ( imageBuffer, GL_ARRAY_BUFFER ); glutMainLoop (); return 0; }
По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows.