Multithreaded game program suddenly locks up on glXSwapBuffers

686 views Asked by At

I'm polishing up a Linux game program I wrote, and after about 10 minutes of playing, it suddenly slows down to 1 frame per thirty seconds or so, slowing the entire system down as well. Even after interrupting the process, the system continues to be slow for about a minute.

In multiple tests I've interrupted the process in GDB when the slowdown occurs, and it is always in the middle of a call to glXSwapBuffers.

It happens regardless of game state or input. The only thing that prevents it is not beginning playback of a repeating music track in a separate thread: the thread still runs, but it doesn't constantly write to the sound card buffer. I've ensured that two shared lists are properly locked.

Has anybody run into a problem with glXSwapBuffers and other, seemingly unrelated threads?

The OS is Ubuntu 9, using the Mesa 7.6.0 implementation of OpenGL and ALSA libasound2 1.0.20-3. I updated my NVIDIA drivers for my GeForce 6800 graphics card this morning, but to no avail.

(Relevant?) code follows.

Display functions:

int DisplayInterface::init()
{   
    xDisplay = XOpenDisplay(NULL);
    if (xDisplay == NULL)
    {
        printf("Error: cannot connect to the X server\n");
        return -1;
    }

    rootWindow = DefaultRootWindow(xDisplay);

    fbConfigs = glXChooseFBConfig(xDisplay, DefaultScreen(xDisplay), fbAttributes, &numConfigs);
    if (fbConfigs == NULL)
    {
        printf("Error: no X framebuffer configuration available as specified\n");
        return -1;
    }

    visualInfo = glXGetVisualFromFBConfig(xDisplay, fbConfigs[0]);
    if (visualInfo == NULL)
    {
        printf("Error: no appropriate X visual found\n");
        return -1;
    }

    colorMap = XCreateColormap(xDisplay, rootWindow, visualInfo->visual, AllocNone);
    xAttributes.colormap = colorMap;
    xAttributes.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask; // need KeyPress and KeyRelease for InputInterface

    gameWindow = XCreateWindow(xDisplay, rootWindow, 0, 0, displayWidth, displayHeight, 0, visualInfo->depth, InputOutput, visualInfo->visual, CWColormap | CWEventMask, &xAttributes);
    XMapWindow(xDisplay, gameWindow);
    XStoreName(xDisplay, gameWindow, "Vuess Vow Vong Vo Vold Vown Vhe Vey");

    glxWindow = glXCreateWindow(xDisplay, fbConfigs[0], gameWindow, NULL);

    renderContext = glXCreateNewContext(xDisplay, fbConfigs[0], GLX_RGBA_TYPE, NULL, GL_TRUE);
    glXMakeContextCurrent(xDisplay, glxWindow, glxWindow, renderContext);

    //glViewport(0, 0, displayWidth, displayHeight);
    glViewport(-2.0 * displayWidth, -2.0 * displayHeight, 5.0 * displayWidth, 5.0 * displayHeight);

    //glMatrixMode(GL_PROJECTION);
    //glLoadIdentity();
    //gluOrtho2D(0.0, (GLfloat)displayWidth, 0.0, (GLfloat)displayHeight);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glPixelZoom((GLfloat)((float)displayWidth / (float) pictureWidth), (GLfloat)((float)displayHeight / (float) pictureHeight));

    glClearColor((float)clearColor[0] / 255.0, (float)clearColor[1] / 255.0, (float)clearColor[2] / 255.0, (float)clearColor[3] / 255.0);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);

    glClear(GL_COLOR_BUFFER_BIT);

    return 0;
}

// draw a Sprite from left to right and from top to bottom, starting at the given pixel
void DisplayInterface::draw(Sprite *sprite, Pixel& pixel)
{
    if (sprite == NULL)
    {
        return;
    }

    pixelstorage_t *spritePixels = sprite->getPixelData();
    const unsigned int format = sprite->getPixelFormat();

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(-2.0 * (GLfloat)displayWidth, 3.0 * (GLfloat)displayWidth, -2.0 * (GLfloat)displayHeight, 3.0 * (GLfloat)displayHeight);
    glRasterPos2i(pixel.x * (int)displayWidth / (int)pictureWidth, (int)displayHeight - (pixel.y + (int)sprite->getHeight()) * (int)displayHeight / (int)pictureHeight);
    switch (format)
    {
        case SPRITE_RGBA:
            glDrawPixels(sprite->getWidth(), sprite->getHeight(), GL_RGBA, PIXEL_TYPE, spritePixels);
    }
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
}

void DisplayInterface::finalizeFrame()
{   
    glFinish();
    glXSwapBuffers(xDisplay, glxWindow);
}

Playback thread functions:

int writeFramesToHwBuffer(pcmsamplestorage_t *frames, snd_pcm_sframes_t numframes)
{
    int pcmreturn;

    while ((pcmreturn = snd_pcm_writei(pcm_handle, frames, numframes)) < 0)
    {
        snd_pcm_prepare(pcm_handle);
        fprintf(stderr, "Speaker Interface error: hardware buffer underrun.\n");
    }
    return pcmreturn;
}

void *playback(void *arg)
{
    int i;

    unsigned int availableframes;
    unsigned int framesFromThisBuffer;
    unsigned int framesThisTime;

    pcmsamplestorage_t *frames_mix;
    pcmsamplestorage_t *frames_track;
    unsigned int framesOffset;

    std::list<struct playbackState *>::iterator stateIter;

    while (1)
    {
        if (snd_pcm_wait(pcm_handle, 1000) < 0)
        {
            fprintf(stderr, "Speaker Interface error: poll failed.\n");
            break;
        }

        if ((availableframes = snd_pcm_avail_update(pcm_handle)) < 0)
        {
            if (availableframes == -EPIPE)
            {
                fprintf(stderr, "Speaker Interface error: an xrun occured.\n");
                break;
            }
            else
            {
                fprintf(stderr, "Speaker Interface error: unknown ALSA avail update return value (%d).\n", availableframes);
                break;
            }
        }

        // mix and write more frequently than necessary
        while (availableframes > 0)
        {
            framesThisTime = std::min(availableframes, 1024u);
            availableframes -= framesThisTime;
            //printf("Frames this time: %d / frames left to go: %d\n", framesThisTime, availableframes);

            frames_mix = new pcmsamplestorage_t[framesThisTime * 2];
            for (i = 0; i < framesThisTime * 2; i++)
            {
                frames_mix[i] = 0;
            }

            // BEGIN CRITICAL SECTION
            if (pthread_mutex_lock(&soundslists_lock) != 0)
            {
                fprintf(stderr, "Speaker Interface error: couldn't lock sounds lists from playback thread.\n");
            }

            printf("soundsPlaying has %d elements.\n", (int)soundsPlaying.size());
            printf("soundsToStop has %d elements.\n", (int)soundsToStop.size());

            for (stateIter = soundsPlaying.begin(); stateIter != soundsPlaying.end(); stateIter++)
            {
                frames_track = (*stateIter)->sound->getSamples();

                if ((*stateIter)->deliveredframes < (*stateIter)->totalframes)
                {
                    if ((*stateIter)->repeating)
                    {
                        framesFromThisBuffer = framesThisTime;
                    }
                    else
                    {
                        // mix in silence if we reach the end of this sound's buffer
                        framesFromThisBuffer = std::min(framesThisTime, (*stateIter)->totalframes - (*stateIter)->deliveredframes);                 
                    }

                    for (i = 0; i < framesFromThisBuffer * 2; i++)
                    {
                        // add samples to the mix, potentially running off the end of this buffer and wrapping around
                        if (SHRT_MAX - frames_mix[i] < frames_track[((*stateIter)->deliveredframes * 2 + i) % ((*stateIter)->totalframes * 2)])
                        {
                            // prevent overflow
                            frames_mix[i] = SHRT_MAX;
                        }
                        else if (SHRT_MIN - frames_mix[i] > frames_track[((*stateIter)->deliveredframes * 2 + i) % ((*stateIter)->totalframes * 2)])
                        {
                            // prevent underflow
                            frames_mix[i] = SHRT_MIN;
                        }
                        else
                        {
                            frames_mix[i] += frames_track[((*stateIter)->deliveredframes * 2 + i) % ((*stateIter)->totalframes * 2)];
                        }
                    }

                    (*stateIter)->deliveredframes = ((*stateIter)->deliveredframes + framesFromThisBuffer);
                    if ((*stateIter)->repeating)
                    {
                        (*stateIter)->deliveredframes = (*stateIter)->deliveredframes % (*stateIter)->totalframes;
                    }
                }
                else
                {

                    soundsToStop.push_back(stateIter);
                }
            }

            writeFramesToHwBuffer(frames_mix, framesThisTime);

            delete frames_mix;

            for (std::list<std::list<struct playbackState *>::iterator>::iterator stateiterIter = soundsToStop.begin(); stateiterIter != soundsToStop.end(); stateiterIter++)
            {
                soundsPlaying.erase(*stateiterIter);
                free(**stateiterIter);
                stateiterIter = soundsToStop.erase(stateiterIter);
            }

            if (pthread_mutex_unlock(&soundslists_lock) != 0)
            {
                fprintf(stderr, "Speaker Interface error: couldn't unlock sounds lists from playback thread.\n");
            }
            // END CRITICAL SECTION
        }
    }
}
1

There are 1 answers

0
Nathan Kidd On

The OS is Ubuntu 9, using the Mesa 7.6.0 implementation of OpenGL and ALSA libasound2 1.0.20-3. I updated my NVIDIA drivers for my GeForce 6800 graphics card this morning, but to no avail.

You can be using a Mesa libGL.so, or NVIDIA libGL.so, but not both at the same time. I suggest you try a different OpenGL driver (e.g. really use Mesa. Check glxinfo | grep OpenGL.vendor)

As a wild guess: glXSwapBuffers often will interface with the vertical sync on your screen, you might try playing with options for that (see Google).