Animated load screens with OpenGL, a simple solution

I came up with a simple solution for animated loading screens since I really don’t like the few options that are available for trying to multi-thread parts of OpenGL. My simple solution allows you to load everything from the main thread and run your animation from newly spawned thread. This only works if your using SDL.

The simple solution is…

Use SDL’s bitmap software rendering tools to load and render your “loading” animation from a thread. Since this is a completely separate rendering system, there’s no OpenGL conflects. Also, your engine probably has some dependencies that need to be loaded just to get part of your system up and running before even a simple image can be displayed as well as start up the render loop. That’s no longer an issue.

I just start the thread that plays the “loading” animation and then load everything. This is happening outside of the game loop, independent of the engine, from within one function.

Here’s my code to animate from a thread to show how easy it is to do. I’m just fading the logo in and out while loading sound, textures, etc with my engine.

I thought this was such a cool solution so I thought I’d share. Works great for me. :slight_smile:

int CStartUpState::Animate()
{
    // Get window surface
    SDL_Surface * pScreenSurface = SDL_GetWindowSurface( CDevice::Instance().GetWindow() );
    if( pScreenSurface == NULL )
        NGenFunc::PostDebugMsg( "Surface Creation error! - " + std::string(SDL_GetError()) );

    // Create a bitmap surface
    SDL_Surface * pLogoSurface = SDL_LoadBMP( "data/textures/startup/logo.bmp" );
    if( pLogoSurface == NULL )
        NGenFunc::PostDebugMsg( "Logo Surface Creation error! - " + std::string(SDL_GetError()) );

    // Calculate the rect position and scaled size
    SDL_Rect rect;
    rect.w = pLogoSurface->w * CSettings::Instance().GetScreenRatio().GetH();
    rect.h = pLogoSurface->h * CSettings::Instance().GetScreenRatio().GetH();
    rect.x = (pScreenSurface->w - rect.w) / 2;
    rect.y = (pScreenSurface->h - rect.h) / 2;

    SDL_Delay( 200 );

    FadeTo( 500, 0, 255, pLogoSurface, pScreenSurface, rect );

    SDL_Delay( 2000 );

    FadeTo( 500, 255, 0, pLogoSurface, pScreenSurface, rect );

    SDL_Delay( 200 );

    SDL_FreeSurface( pLogoSurface );
    SDL_FreeSurface( pScreenSurface );

    return thread::THREAD_EXIT_CODE;

}  // Animate


void CStartUpState::FadeTo(
    float time, float current, float final, SDL_Surface * pSource, SDL_Surface * pTarget, SDL_Rect & rect )
{
    float inc = (final - current) / time;

    while( time > 0 )
    {
        // First thing we need to do is get our elapsed time
        CHighResTimer::Instance().CalcElapsedTime();

        time -= CHighResTimer::Instance().GetElapsedTime();

        current += inc * CHighResTimer::Instance().GetElapsedTime();

        SDL_SetSurfaceColorMod( pSource, current, current, current );

        if( time < 0 )
        {
            SDL_SetSurfaceColorMod( pSource, final, final, final );
            break;
        }

        SDL_BlitScaled( pSource, NULL, pTarget, &rect );
        SDL_UpdateWindowSurface( CDevice::Instance().GetWindow() );

        SDL_Delay( 5 );
    }

}   // FadeTo

This is where it all happens. The first line of code starts the thread for animating loading screen. The following lines of code is loading everything I need for the engine at this time. The last line waits for the thread to finish. All within one function, completely independent of the OpenGL/SDL game engine.

void CStartUpState::Load()
{
    // Start the animated loading thread
    loadThread.Start( this, &CStartUpState::Animate, "startupStateThread" );

    // Load in any fonts
    CFontMgr::Instance().LoadFromXML( "data/textures/fonts/font.lst" );

    // Shaders must always be loaded first because they are accessed from object data
    CShaderMgr::Instance().LoadFromXML( "data/shaders/shader.cfg" );

    // Load all of the meshes and materials in these groups
    CObjectDataMgr2D::Instance().LoadListTable( "data/objects/2d/objectDataList/dataListTable.lst" );
    CObjectDataMgr2D::Instance().LoadGroup("(menu)");
    CObjectDataMgr2D::Instance().LoadGroup( "(title_screen)" );

    // Load sound resources for the menu
    CSoundMgr::Instance().LoadListTable( "data/sound/soundListTable.lst" );
    CSoundMgr::Instance().LoadGroup("(menu)");

    // Creates the script manager and loads the list table
    CScriptManager::Instance().LoadListTable( "data/objects/2d/scripts/scriptListTable.lst" );

    // Register the script items
    RegisterStdString( CScriptManager::Instance().GetEnginePtr() );
    NScriptGlobals::Register( CScriptManager::Instance().GetEnginePtr() );
    NScriptColor::Register( CScriptManager::Instance().GetEnginePtr() );
    CSpriteScriptComponent2d::Register( CScriptManager::Instance().GetEnginePtr() );

    // Load group specific script items
    CScriptManager::Instance().LoadGroup("(menu)");

    // Create the menu system
    CMenuManager::Instance().LoadFromXML( "data/objects/2d/menu/tree.list" );
    CMenuManager::Instance().ActivateTree("main");

    // Init the currently plugged in game controllers
    CDevice::Instance().InitStartupGamepads();

    // Load the action manager
    CActionManager::Instance().LoadActionFromXML( "data/settings/controllerMapping.cfg" );

    // Wait for the thread to finish
    loadThread.WaitForThread();

}   // Load

really nice ;).

Thanks! I soon realized I can’t load everything from a separate thread because I use SDL2 mixer and loading ogg sound files don’t seem to work from another thread. Further research showed that OpenGL is not designed for threading except for a few work arounds. This is a kick in the teeth if what you’re after is a simple elegant system for loading screen animations. I was able to do this with my DirectX engine but I still had to get parts of my engine up and running and enter the game loop to load and animate at the same time. What’s amazing is that you can use OpenGL and SDL2 interchangeable. Never thought I’d find a use for the SDL2 software rendering feature but I’m extremely glad it’s there.