OpenGL: Rendering a video using PBO's creating a jerk

1.4k views Asked by At

I have a set of RGB Frames which i'm displaying using openGL's PBO concept. Below is my code:

#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;
}

When the above code is run on a system having graphics card details listed below the display is normal and works fine:

First System:

Graphics card: Nvidia GEForce 580
OpenGL Version: 4.3

Second system:

Graphics card: NVidia GEForce 310
OpenGL Version: 3.3

Third system:

Graphics card: MSI
OpenGL Version: 4.2

However when the same code is run on my laptop with same system configuration but with the graphic cards details as shown below, i get a kind of Jerk (slight stuck) for some streams (some streams are working fine). I have checked same streams on different systems listed above and they seem to work fine.

Graphics card: Intel HD Graphics 4000
openGL Version: 4.0

What might be the problem that is causing this issue?

2

There are 2 answers

0
derhass On

Your code is seriously broken. If it works at all, than only by accident:

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();

}

Your code then writes to guibuffer in GL_Display, and then tries to source the data in the PBO to do the texture update.

You must be aware that, after the glUnmapBufferARB(), the pointer you got by the mapping becomes invalid. Writing to it is just undefined behavior. You might be lucky here as the nvidia driver might use some client-memory for the buffer and the pointer by accident continues to point to the real buffer - but that would be more or less an obscure coincidence. Note that you also can not just leave the buffer mapped all the time, since while it is mapped, you cannot use the GL object. At least not without the quite new GL_ARB_BUFFER_STORAGE extension (in core since 4.4), and even there, you would have to manually synchronize acces of the client and the GL - and the underlying problem would remain the same, anyways.

To do this right, the simplest solution would be to per frame do the following: map the buffer, read the file into int, unmap it, update the texture, draw.

However, that would probably be a bit faster than not using PBOs at all, but peformance will not be optimal. You could improve this by orphaning the buffer before mapping it. It will tell the GL that you do not care about the contents of the buffer anymore (all pending GL operations which work with this data will be completed, of course) and allows the GL to supply a new storage immediately, while the old buffer contents can be internally used as long they are needed. To do the orphaning, just use glBufferData() with the correct size and NULL as the data pointer again. The orphaning could also be done using the more modern (and actually preferred) glMapBufferRange() function with the GL_MAP_INVALIDATE_BUFFER_BIT access bit set. See the GL_MAP_BUFFER_RANGE extension for details.

Another solution for fully asynchronous resource updates is using a ring of PBOs, so that you can upload the next frame to another PBO while the GPU is still sourcing data from the current PBO. This will basically work out like the orphaning method, but push less pressure on the GL and especially it's buffer management, so less overhead is to be expected (although it is unlikely to be relevant in your use case).

Besides the flaws in your PBO logic, there are several other points I'd like to mention:

  1. You are using GL_ELEMENT_ARRAY_BUFFER. It is perfectly legit to later use this buffer as GL_PIXEL_UNPACK_BUFFER, but the GL might use this information to optimize for the expected usage (especially by chosing the location of the buffer in VRAM or in client memory).
  2. You map your buffer with GL_READ_WRITE where GL_WRITE_ONLY is sufficient and could be a more efficient path (and would actually be required for the suggested orphaning via glMapBufferRange)
  3. As datenwolf already pointed out in another answer, use glTexSubImage2D() instead of glTexImage2D() for the texture updates.
5
datenwolf On

In your display function you're calling glTexImage2D. This function performs a full texture object re-/allocation. Depending on the implementation details of the driver this might trigger internal garbage collection, if too many texture objects accumulated over time. Since your intention is display a stream of frames, of identical format and size, you should just replace the texture data using glTexSubImage2D instead.

Call glTexImage2D only once to initialize the texture object and then use glTexSubImage2D to upload new data. To make coding easier initialize the texture object by calling glTexImage2D with a null pointer for the data parameter while no PBO is bound.