OpenGL: рендеринг видео с использованием PBO, создающий рывок
У меня есть набор RGB-фреймов, которые я показываю, используя концепцию PBO openGL. Ниже мой код:
#include <stdio.h>
#include <stdlib.h>
#include "glew.h"
#include "glfw.h"
#include "glaux.h"
#include "logodata.h"
PFNGLGENBUFFERSARBPROC pglGenBuffersARB = 0; // VBO Name Generation Procedure
PFNGLBINDBUFFERARBPROC pglBindBufferARB = 0; // VBO Bind Procedure
PFNGLBUFFERDATAARBPROC pglBufferDataARB = 0; // VBO Data Loading Procedure
PFNGLBUFFERSUBDATAARBPROC pglBufferSubDataARB = 0; // VBO Sub Data Loading Procedure
PFNGLDELETEBUFFERSARBPROC pglDeleteBuffersARB = 0; // VBO Deletion Procedure
PFNGLGETBUFFERPARAMETERIVARBPROC pglGetBufferParameterivARB = 0; // return various parameters of VBO
PFNGLMAPBUFFERARBPROC pglMapBufferARB = 0; // map VBO procedure
PFNGLUNMAPBUFFERARBPROC pglUnmapBufferARB = 0; // unmap VBO procedure
#define glGenBuffersARB pglGenBuffersARB
#define glBindBufferARB pglBindBufferARB
#define glBufferDataARB pglBufferDataARB
#define glBufferSubDataARB pglBufferSubDataARB
#define glDeleteBuffersARB pglDeleteBuffersARB
#define glGetBufferParameterivARB pglGetBufferParameterivARB
#define glMapBufferARB pglMapBufferARB
#define glUnmapBufferARB pglUnmapBufferARB
int index;
int pboSupported;
int pboMode;
GLuint pixBuffObjs[2];
HDC hDC = NULL;
GLuint texture;
char *FileName;
unsigned char *guibuffer;
AUX_RGBImageRec texture1;
unsigned long long pos=0;
GLuint myPBO;
GLuint logoPBO;
unsigned char *logoBuff;
void initGL(void)
{
int maxSz;
int maxwidth = 416;
int maxheight = 240;
if( !glfwInit() )
{
exit( EXIT_FAILURE );
}
// if( !glfwOpenWindow(4096, 2118, 0,0,0,0,0,0, GLFW_WINDOW ) )
if( !glfwOpenWindow(maxwidth, maxheight, 0,0,0,0,0,0, GLFW_WINDOW ) ) //GLFW_FULLSCREEN
{
glfwTerminate();
exit( EXIT_FAILURE );
}
glfwSetWindowTitle("sample");
glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)wglGetProcAddress("glGenBuffersARB");
glBindBufferARB = (PFNGLBINDBUFFERARBPROC)wglGetProcAddress("glBindBufferARB");
glBufferDataARB = (PFNGLBUFFERDATAARBPROC)wglGetProcAddress("glBufferDataARB");
glBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC)wglGetProcAddress("glBufferSubDataARB");
glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)wglGetProcAddress("glDeleteBuffersARB");
glGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC)wglGetProcAddress("glGetBufferParameterivARB");
glMapBufferARB = (PFNGLMAPBUFFERARBPROC)wglGetProcAddress("glMapBufferARB");
glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)wglGetProcAddress("glUnmapBufferARB");
// check once again PBO extension
if(glGenBuffersARB && glBindBufferARB && glBufferDataARB && glBufferSubDataARB &&
glMapBufferARB && glUnmapBufferARB && glDeleteBuffersARB && glGetBufferParameterivARB)
{
pboSupported = 1;
pboMode = 1; // using 1 PBO
printf( "Video card supports GL_ARB_pixel_buffer_object.");
glGenBuffersARB(1, &pixBuffObjs[0]);
}
else
{
pboSupported = 0;
pboMode = 0; // without PBO
printf("Video card does NOT support GL_ARB_pixel_buffer_object.");
}
glGetIntegerv(GL_MAX_TEXTURE_SIZE,&maxSz);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // This Will Clear The Background Color To Black
glClearDepth(1.0); // Enables Clearing Of The Depth Buffer
glDepthFunc(GL_LESS); // The Type Of Depth Test To Do
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glShadeModel(GL_SMOOTH); // Enables Smooth Color Shading
glMatrixMode(GL_PROJECTION);
//glLoadIdentity();
hDC= wglGetCurrentDC();
#if 1
{ // TSS
HWND hCurrentWindow = GetActiveWindow();
char szTitle[256]="sample";
//SetWindowText(hCurrentWindow, );
// SetWindowLongA (hCurrentWindow , GWL_STYLE, (GetWindowLongA (hCurrentWindow , GWL_STYLE) & ~(WS_CAPTION)));
SetWindowLongA (hCurrentWindow, GWL_STYLE, (WS_VISIBLE));
}
#endif
glEnable(GL_TEXTURE_2D);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
}
int GL_Disply()
{
FILE *fptr=fopen("F:\\myRGBvideo.rgb","rb");
fseek(fptr,pos,SEEK_SET);
fread(guibuffer,sizeof(unsigned char),sizeof(unsigned char)*416*240*3,fptr);
pos+=416*240*3;
texture1.sizeX =416;
texture1.sizeY =240;
texture1.data = guibuffer;
glDepthFunc(GL_ALWAYS);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glDisable(GL_LIGHTING);
//glEnable(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
#if 0
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture1.sizeX, texture1.sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, guibuffer);
#else
glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, myPBO);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture1.sizeX, texture1.sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
#endif
glBegin(GL_QUADS);
//glNormal3f( 0.0f, 0.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, -1.0f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd();
//disp logo
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, LOGO_WIDTH, LOGO_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, logoBuff);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 1.0f); glVertex3f(0.8f, 0.8f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 0.97f, 0.8f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 0.97f, 0.97f, 0.0f);
glTexCoord2f(0.0f, 0.0f);glVertex3f(0.8f, 0.97f, 0.0f);
glEnd();
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
// Swap front and back rendering buffers
glfwSwapBuffers();
//glDeleteTextures(1, &texture);
fclose(fptr);
}
int main(int argc, char *argv[])
{
initGL(); // GL initialization
#if 0
/* CPU memory allocation using C - malloc */
guibuffer=(unsigned char*)malloc(sizeof(unsigned char)*416*240*3);
#else
/*GPU memory allocation using C*/
glGenBuffersARB(1, &myPBO);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, myPBO);
glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 416*240*3, NULL, GL_STREAM_DRAW_ARB);
#endif
//Allocating memory for RGBA logo data present in logodata.h
logoBuff=(unsigned char*)malloc(400*312*4*sizeof(unsigned char));
memcpy(logoBuff,TELLogo_RGB,400*312*4);
for(index=0;index<200;index++)
{
guibuffer=(unsigned char*)glMapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, GL_READ_WRITE_ARB);
glUnmapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB);
printf("frame %d displayed\r",index);
GL_Disply();
}
free(logoBuff);
return 0;
}
Когда приведенный выше код запускается в системе, в которой ниже указаны данные графической карты, дисплей работает нормально и работает нормально:
Первая система:
Graphics card: Nvidia GEForce 580
OpenGL Version: 4.3
Вторая система:
Graphics card: NVidia GEForce 310
OpenGL Version: 3.3
Третья система:
Graphics card: MSI
OpenGL Version: 4.2
Однако, когда тот же код выполняется на моем ноутбуке с такой же конфигурацией системы, но с деталями графических карт, как показано ниже, я получаю своего рода рывок (немного застрял) для некоторых потоков (некоторые потоки работают нормально). Я проверил одни и те же потоки в разных системах, перечисленных выше, и они, кажется, работают нормально.
Graphics card: Intel HD Graphics 4000
openGL Version: 4.0
В чем может быть проблема, которая вызывает эту проблему?
2 ответа
Ваш код серьезно сломан. Если это работает вообще, то только случайно:
for(index=0;index<200;index++)
{
guibuffer=(unsigned char*)glMapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, GL_READ_WRITE_ARB);
glUnmapBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB);
printf("frame %d displayed\r",index);
GL_Disply();
}
Ваш код затем пишет в guibuffer
в GL_Display
, а затем пытается получить данные в PBO для обновления текстуры.
Вы должны знать, что после glUnmapBufferARB()
указатель, полученный при сопоставлении, становится недействительным. Запись в это просто неопределенное поведение. Возможно, вам повезет, поскольку драйвер nvidia может использовать некоторую клиентскую память для буфера, а указатель случайно продолжает указывать на реальный буфер, но это будет более или менее неясным совпадением. Обратите внимание, что вы также не можете просто оставить отображенный буфер все время, так как пока он отображается, вы не можете использовать объект GL. По крайней мере, не без совершенно нового расширения GL_ARB_BUFFER_STORAGE (в ядре начиная с 4.4), и даже там, вам придется вручную синхронизировать доступ клиента и GL - и основная проблема останется той же, в любом случае.
Чтобы сделать это правильно, простейшим решением было бы сделать для каждого кадра следующее: отобразить буфер, прочитать файл в int, разархивировать его, обновить текстуру, нарисовать.
Тем не менее, это, вероятно, будет немного быстрее, чем вообще не использовать PBO, но производительность не будет оптимальной. Вы можете улучшить это, осиротев буфер, прежде чем отобразить его. Он сообщит GL о том, что вы больше не заботитесь о содержимом буфера (все ожидающие операции GL, работающие с этими данными, конечно же, будут завершены) и позволит GL немедленно предоставить новое хранилище, в то время как содержимое старого буфера могут быть использованы внутри, если они необходимы. Чтобы сделать сиротство, просто используйте glBufferData()
с правильным размером и NULL
как указатель данных снова. Сиротство также может быть сделано с использованием более современного (и на самом деле предпочтительного) glMapBufferRange()
функция с GL_MAP_INVALIDATE_BUFFER_BIT
бит доступа установлен. Посмотрите расширение GL_MAP_BUFFER_RANGE для деталей.
Другое решение для полностью асинхронных обновлений ресурсов - использование кольца PBO, чтобы вы могли загрузить следующий кадр в другое PBO, пока графический процессор все еще получает данные из текущего PBO. Это в основном будет работать как метод сиротства, но будет оказывать меньшее давление на GL и особенно на управление буферами, поэтому следует ожидать меньших накладных расходов (хотя это вряд ли будет актуально в вашем случае использования).
Помимо недостатков в вашей логике PBO, я хотел бы упомянуть еще несколько моментов:
- Ты используешь
GL_ELEMENT_ARRAY_BUFFER
, Вполне законно позже использовать этот буфер какGL_PIXEL_UNPACK_BUFFER
, но GL может использовать эту информацию для оптимизации для ожидаемого использования (особенно путем выбора расположения буфера в VRAM или в памяти клиента). - Вы сопоставляете свой буфер с
GL_READ_WRITE
гдеGL_WRITE_ONLY
достаточно и может быть более эффективным путем (и фактически потребовалось бы дляglMapBufferRange
) - Как уже указывал datenwolf в другом ответе, используйте
glTexSubImage2D()
вместоglTexImage2D()
для обновления текстур.
В вашей функции отображения вы звоните glTexImage2D
, Эта функция выполняет полное перераспределение / размещение объекта текстуры. В зависимости от деталей реализации драйвера это может вызвать внутреннюю сборку мусора, если со временем накопится слишком много объектов текстуры. Поскольку вы намереваетесь отобразить поток кадров одинакового формата и размера, вам просто нужно заменить данные текстуры, используя glTexSubImage2D
вместо.
Вызов glTexImage2D
только один раз, чтобы инициализировать объект текстуры, а затем использовать glTexSubImage2D
загрузить новые данные. Чтобы упростить кодирование, инициализируйте объект текстуры, вызвав glTexImage2D
с нулевым указателем для параметра данных, в то время как PBO не привязано.