Тема: OpenGL PBO
Показать сообщение отдельно
Старый 16.01.2013, 16:35   #1
unself
AnyKey`щик
 
Регистрация: 30.12.2011
Сообщений: 12
Написано одно полезное сообщение
OpenGL PBO

Потребовалось написать приложение, которое максимально быстро сможет вывести массив пикселей на экран. Недолгие поиски привели меня к PBO. Примеров в интернете много, поэтому с этим трудностей не возникло. Приведу лишь некоторые основные методы:

Создание OpenGL контекста
void CreateGLContext(Window *window)
{
  //---------------------------------------------------------------------
  // Дескриптор контекста окна
  window->DC = GetDC(window->hWnd);
  if (  !window->DC)
  {
    exit(-1);
  }
  //---------------------------------------------------------------------

  //---------------------------------------------------------------------
  PIXELFORMATDESCRIPTOR pfd =
  {
    sizeof(PIXELFORMATDESCRIPTOR),
    1,
    PFD_DRAW_TO_WINDOW |
    PFD_SUPPORT_OPENGL |
    PFD_DOUBLEBUFFER,         //Flags
    PFD_TYPE_RGBA,            //The kind of framebuffer. RGBA or palette.
    32,                       //Colordepth of the framebuffer.
    0, 0, 0, 0, 0, 0,
    0,
    0,
    0,
    0, 0, 0, 0,
    24,                       //Number of bits for the depthbuffer
    8,                        //Number of bits for the stencilbuffer
    0,                        //Number of Aux buffers in the framebuffer.
    0,
    0,
    0, 0, 0
  };

  // Если ни один из системных форматов не подходит или его невозможно применить к окн
  int pixelFormat = ChoosePixelFormat(window->DC, &pfd);
  if (  !pixelFormat ||
        !SetPixelFormat(window->DC, pixelFormat, &pfd))
  {
    exit(-1);
  }
  //---------------------------------------------------------------------

  //---------------------------------------------------------------------
  // Создаем контекст воспроизведения OpenGL
  window->hGLRC = wglCreateContext(window->DC);
  // Устанавливаем его текущим для окна
  if (  !window->hGLRC ||
        !wglMakeCurrent(window->DC, window->hGLRC))
  {
    exit(-1);
  }
  //---------------------------------------------------------------------

  UpdateWindow(window->hWnd);
  ShowWindow(window->hWnd, SW_SHOWDEFAULT);
}


Установка ViewPort'a
void SetViewPort(int &w, int &h)
{
  glClearColor    ( 0.0, 0.0, 0.0, 1.0 );
  glViewport      (0, 0, w, h);
  glMatrixMode    (GL_PROJECTION);
  glLoadIdentity  ();
  glOrtho         (0, w, 0, h, -1.0f, 1.0f);
  //glEnable(GL_DEPTH_TEST);
}


После загрузки массива пикселей (32-битное изображение), я генерирую индексы, выделяю память в видеопамяти и устанавливаю параметры фильтрации / наложения
  // Генерируем индекс текстуры
  //---------------------------------------------------------------------
  glGenTextures(1, &surface->glTextureID);
  if (  !surface->glTextureID ) {
    FreeSurface(surface);
    return (nullptr);
  }
  //---------------------------------------------------------------------


  // PBOs is simply an array of bytes in memory
  //---------------------------------------------------------------------
  glGenBuffers(1, &surface->pboID);
  if (  !surface->pboID) {
    FreeSurface(surface);
    return (nullptr);
  }
  //---------------------------------------------------------------------

  // GL_PIXEL_PACK_BUFFER_ is for transferring pixel data from OpenGL
  // to your application, and GL_PIXEL_UNPACK_BUFFER_ means transferring
  // pixel data from an application to OpenGL
  //---------------------------------------------------------------------
  glBindBuffer(GL_PIXEL_UNPACK_BUFFER, surface->pboID);  // GL_PIXEL_UNPACK_BUFFER

  // Allocate a memory space
  // glBufferData creates a new data store and copy pixel data for the buffer object currently bound to target
  glBufferData(GL_PIXEL_UNPACK_BUFFER, surface->byteCount, (const void*)surface->pixels, GL_STREAM_DRAW);
  if (  glGetError() == GL_OUT_OF_MEMORY) {
    // GL is unable to create a data store with the specified size
    FreeSurface(surface);
    return (nullptr);
  }
  glReleaseBuffer();
  //---------------------------------------------------------------------
  
  //---------------------------------------------------------------------
  // Выбираем предварительно загруженное изображение
  glBindTexture     (GL_TEXTURE_2D, surface->glTextureID);

  // Установим параметры фильтрации
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  // Параметры наложения
  glTexParameterf   (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); // GL_CLAMP
  glTexParameterf   (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); // GL_REPEAT
  
  //Constant GL_BGRA available if the GL version is 1.2 or greater
  glTexImage2D      (GL_TEXTURE_2D, 0, GL_RGBA, surface->width, surface->height, 0, surface->pixelFormat, surface->internalPixelFormat, nullptr);
  
  glReleaseTexture  ();


И сам метод отрисовки изображения
void DrawSurface(Surface *surface, iRectangle *target)
{
  //---------------------------------------------------------------------
  glEnable          (GL_TEXTURE_2D);
  glBindTexture     (GL_TEXTURE_2D, surface->glTextureID);
  // GL_PIXEL_PACK_BUFFER_ is for transferring pixel data from OpenGL
  // to your application, and GL_PIXEL_UNPACK_BUFFER_ means transferring
  // pixel data from an application to OpenGL
  glBindBuffer      (GL_PIXEL_UNPACK_BUFFER, surface->pboID); // GL_PIXEL_UNPACK_BUFFER_
                                                          // glBindBuffer()
  //---------------------------------------------------------------------

  // slowly then glMapBuffer
  //glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, surface->byteCount, surface->pixels);

  //glBufferData(GL_PIXEL_PACK_BUFFER, surface->byteCount, nullptr, GL_STREAM_DRAW);
  GLubyte* ptr =    (GLubyte*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); // GL_WRITE_ONLY_
  if(ptr)
  {
    // update data directly on the mapped buffer
    //---------------------------------------------------------------------
    memcpy(ptr, surface->pixels, surface->byteCount);
    //memset((unsigned char*)ptr, (unsigned char)0, surface->byteCount);
    glUnmapBuffer   (GL_PIXEL_UNPACK_BUFFER); // glUnmapBuffer
    //---------------------------------------------------------------------
  }

  //---------------------------------------------------------------------
  // Копируем данные с PBO
  glTexSubImage2D   (GL_TEXTURE_2D, 0, 0, 0, surface->width, surface->height, surface->pixelFormat, surface->internalPixelFormat, nullptr);
  //---------------------------------------------------------------------

  //---------------------------------------------------------------------
  glBegin           (GL_QUADS);
  //---------------------------------------------------------------------
  glTexCoord2i      (0, 0); glVertex2i(target->x, target->y);
  glTexCoord2i      (1, 0); glVertex2i(target->x + target->w, target->y);
  glTexCoord2i      (1, 1); glVertex2i(target->x + target->w, target->y + target->h);
  glTexCoord2i      (0, 1); glVertex2i(target->x, target->y + target->h);
  //---------------------------------------------------------------------
  glEnd             ();
  //---------------------------------------------------------------------

  //---------------------------------------------------------------------
  glReleaseBuffer   ();
  glReleaseTexture  ();
  glDisable         (GL_TEXTURE_2D);
  //---------------------------------------------------------------------
}

Для удобно доступа к отдельным пикселям изображения я определил для себя два макроса
#define GET_PIXEL(y, x)         *(src  + (y << img1->widthShift) + x)
#define SET_PIXEL(y, x, color)  *(dest + (y * img2->width) + x) = color
Вот тут я столкнулся с проблемой. Есть метод, который рендерит изображение и заносит пиксели в массив
void RenderImage3(Surface *img1, Surface *img2, Camera &cam)
{
  //---------------------------------------------------------------------
  unsigned int *src   = (unsigned int*)img1->pixels;
  unsigned int *dest  = (unsigned int*)img2->pixels;
  //---------------------------------------------------------------------

  /* Rendering ...*/

  unsigned int get = GET_PIXEL(((int)y0 & SRC_HEIGHT_MASK), ((int)x0 & SRC_WIDTH_MASK));
  SET_PIXEL((y + HALF_SCR_HEIGHT), (x + HALF_SCR_WIDTH), get);
}
При попытке измерить производительность, я получил FPS в районе 200 кадров



Поначалу меня это не смущало (грешил на неоптимизированный алгоритм), пока не подставил случайный цвет
void RenderImage3(Surface *img1, Surface *img2, Camera &cam)
{
  //---------------------------------------------------------------------
  unsigned int *src   = (unsigned int*)img1->pixels;
  unsigned int *dest  = (unsigned int*)img2->pixels;
  //---------------------------------------------------------------------

  /* Rendering ...*/

  unsigned int get = GET_PIXEL(((int)y0 & SRC_HEIGHT_MASK), ((int)x0 & SRC_WIDTH_MASK));
  SET_PIXEL((y + HALF_SCR_HEIGHT), (x + HALF_SCR_WIDTH), (unsigned int)0xFF00FF00);
}
И получил производительность более 1000 FPS



То есть, разница в скорости работы оказалось более чем в 5 раз, при том, что в первом случае, цвет пикселя берется из текстуры, а во втором, это случайно заданный цвет. В остальном это абсолютно одинаковые функции. Логически это объяснить сложно, складывается впечатление, что OpenGL придирчив к выбору цвета. Есть подозрения, что я не правильно установил параметры OpenGL. Куда дальше двигаться мне не ясно. Пожалуйста, помогите разобраться.

Последний раз редактировалось unself, 16.01.2013 в 18:47.
(Offline)
 
Ответить с цитированием