Ferramentas Pessoais
Você está aqui: Página Inicial Cursos GPU Programming Soma de dois vetores
Acessar


Esqueceu sua senha?
 

Soma de dois vetores

Este exemplo ilustra como usar a placa gráfica para realizar cálculo de propósito geral

O objetivo deste exemplo é mostrar como somar dois vetores usando hardware gráfico. Serão definidos como entrada dois vetores A e B de tamanho n e o programa deverá retornar um vetor R de tal forma que R[i] = A[i] + B[i], para 0<i<n.

Os dois vetores serão enviados para a memoria da placa gráfica e para cada registro i será gerado um fragmento. Estes fragmentos serão processados pelo shader de soma e guardará o resultado correspondente num FrameBuffer.

Função main()

Antes de mais nada, a função principal irá definir o contexto do OpenGL. Após serão instanciados e inicializados os vetores A e B, os quais serão passados como parâmetros para a função sumVectors() a qual executará a operação de soma e guardará o resultado num vetor R. O seguinte código ilustra esta idéia:

int main (int argc, char** argv) {
   
   // OpenGl context creation
   glutInit(&argc, argv);
   GLuint winHandle = glutCreateWindow("GPGPU vectors sum");
   
   // Input data
   vector<Number> arrayA(n);
   vector<Number> arrayB(n);
   vector<Number> arrayR(n);
   for (int i=0; i<arrayA.size(); ++i) arrayA[i]=rand()/(Number)RAND_MAX;
   for (int i=0; i<arrayB.size(); ++i) arrayB[i]=rand()/(Number)RAND_MAX;
   cout<<"The input arrays are: "<<endl;
   copy(arrayA.begin(), arrayA.end(), ostream_iterator<Number>(cout, " ")); cout<<endl<<endl;
   copy(arrayB.begin(), arrayB.end(), ostream_iterator<Number>(cout, " ")); cout<<endl<<endl;
   
   // Sum the vectors
   sumVectors(arrayA, arrayB, arrayR);
   
   cout<<"The result is: "<<endl;
   copy(arrayR.begin(), arrayR.end(), ostream_iterator<Number>(cout, " ")); cout<<endl;
   
   glutDestroyWindow(winHandle);
   return 0;
}

A função sumVectors() recebe os dois vetores e os envia para a memoria da GPU usando a função uploadArray(). Apos os dados estarem na memoria da placa o shader de soma é chamado. Finalmente, o resultado da soma é levado da memoria da GPU para a memoria principal do sistema. Veja a seguir a seqüência destas chamadas:

/// Executes R = A + B using the graphics hardware
/// @param A an input vector
/// @param B an input vector
/// @param R the result
void sumVectors(const vector<Number> &A, const vector<Number> &B, vector<Number> &R) {

   GLuint arrayAid = uploadArray(A);
   GLuint arrayBid = uploadArray(B);
   runShader(arrayAid, arrayBid);
   downloadArray(R);
}

Enviando dados para a memoria da GPU

 A função uploadArray() envia para a memoria da GPU o array que recebe como parâmetro e retorna um identificador para ele:

/// Sends the data to the GPU memory
GLuint uploadArray(const vector<Number> &array) {

   /// Makes a texture
   GLuint texId; // texture identifier
   glGenTextures(1, &texId);
   
   /// Allocates texture memory for storing the data
   glBindTexture(GL_TEXTURE_RECTANGLE_NV, texId);
   glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_FLOAT_R32_NV, texSize, texSize, 
                0, GL_RED, GL_FLOAT, &array[0]);
   return texId;
} 

Executando o shader

Uma vez que os dados a serem processados já estão na memoria da GPU é iniciada a configuração do shader e do frameBuffer. Por último é criada a primitiva GL_QUADS que ira gerar os fragmentos.

void runShader(GLuint texAId, GLuint texBId) {

   fboSetup();
   fsKernelSetup();
   glUseProgram(programObject);
   
   // Set the data identifiers
   glActiveTexture(GL_TEXTURE0);
   glBindTexture(GL_TEXTURE_RECTANGLE_NV, texAId);
   GLint locationA = glGetUniformLocation(programObject, "arrayA");
   glUniform1i(locationA,0); // texunit 0
   glActiveTexture(GL_TEXTURE1);
   glBindTexture(GL_TEXTURE_RECTANGLE_NV, texBId);
   GLint locationB = glGetUniformLocation(programObject, "arrayB");
   glUniform1i(locationB,1); // texunit 1
   
   // Fragments generation
   glBegin(GL_QUADS);
     glVertex2f(0, 0);
     glVertex2f(texSize, 0);
     glVertex2f(texSize, texSize);
     glVertex2f(0, texSize);
   glEnd();
   glFinish();
   
   glUseProgram(0);
}

Definindo o shader

A configuração do programa GLSL consiste da definição, compilação e linkado do shader:

/// Performs the GLSL kernel setup
void fsKernelSetup() {
   
   // The source code of fragment shader 
   const char *FragSource = {
      "uniform sampler2DRect arrayA;"
      "uniform sampler2DRect arrayB;"
      "void main(void) {"
      "   vec2 pcoo = gl_FragCoord.xy;"
      "   float a = texture2DRect(arrayA, pcoo).r;"
      "   float b = texture2DRect(arrayB, pcoo).r;"
      "   gl_FragColor.r = a + b;"
      "}"
   };
   
   // Load, compile the source code
   GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
   glShaderSource(fragmentShader, 1, &FragSource, 0);
   glCompileShader(fragmentShader);
   
   // The GLSL program
   programObject = glCreateProgram();
   // Attach the fragment shader into the program object
   glAttachShader(programObject, fragmentShader);
   // Link the shader into a complete GLSL program
   glLinkProgram(programObject);
   GLint progLinkSuccess;
   glGetProgramiv(programObject, GL_LINK_STATUS, &progLinkSuccess);
   if (not progLinkSuccess){
      fprintf(stderr, "Fragment shader could not be linked \n");
      exit(1);
   }
}

Definindo o FBO

A definição do local onde serão guardados temporariamente os resultados da operação é feita através de um FBO (Frame Buffer Object). Consiste na definição de uma porção de memoria que será usada para este propósito:

/// Performs the FBO setup (key for off-screen rendering - gpgpu)
void fboSetup() {

   GLuint fboId; // FBO identifier
   glGenFramebuffersEXT(1, &fboId);
   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(0.0, texSize, 0.0, texSize);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glViewport(0, 0, texSize, texSize);
   
   GLuint texFBO; // FBO color texture
   glGenTextures(1, &texFBO);
   glBindTexture(GL_TEXTURE_RECTANGLE_NV, texFBO);
   glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_RECTANGLE_NV, GL_TEXTURE_WRAP_T, GL_CLAMP);
   
   glTexImage2D(GL_TEXTURE_RECTANGLE_NV, 0, GL_FLOAT_R32_NV, texSize, texSize, 
                   0, GL_RED, GL_FLOAT, 0);
   
   glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, 
                             GL_TEXTURE_RECTANGLE_NV, texFBO, 0);
   
   // clean the memory
   glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
   glClearColor(0.0,0.0,0.0,1.0);
   glClear(GL_COLOR_BUFFER_BIT);
   // turn on the fbo
   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
}

Código fonte

O código fonte deste exemplo esta disponível aqui. Em principio, bastaria dar um make e executar helloGLSL. O exemplo foi testado num sistema Linux (nas distribuições Ubuntu 7.10 e Fedora 7.0).
 

Ações do documento