Concurrent Renderer

Using functional decomposition, rendering in my engine is performed in a dedicated rendering thread. Starting with a robust Renderer class my goal was to offload rendering to a separate thread with minimum modification to the engine. I came up with the idea of creating a simple abstract IRenderable interface. The interface provides three abstract functions to create, render and destroy rendering data that any pre-existing class would simply need to implement.

The Renderer thread, which owns the OpenGL context, then simply processes all opaque IRenderable objects eliminating the complexity of OpenGL context switches with minimal modifications to existing classes.

The provided source code below has been simplified and stripped down to better illustrate the aforementioned.

Source Code

/*
file    IRendererable.h
date    05/01/2013
author  Warsam Osman
brief   Renderable Interface 
*/
#pragma once

#ifndef _IRENDERABLE_
#define _IRENDERABLE_

#include "Mutex.h"

namespace core
{
    class IRenderable : public Mutex
    {
    public:
        IRenderable() {}
        virtual ~IRenderable() {}

        virtual void IRenderCreate() = 0;
        virtual void Render() = 0;
        virtual void IRenderDestroy() = 0;
    };
}

#endif  //_IRENDERABLE_

/*
file    Renderer.h
date    01/18/2013
author  Warsam Osman
brief   Renderer class
*/
#pragma  once

#ifndef _RENDERER_
#define _RENDERER_

#include "../Core/Thread.h"

namespace renderer
{
    class IRenderable;
    
    //Stripped down Renderer for clarity.
    
    class Renderer : public Thread
    {
    public:
        Renderer();
        virtual ~Renderer();
        
        void Init( HWND hwnd );
        void ShutDown();
        void RegisterRenderable( IRenderable* renderableObject );
        void CreateRenderables();
        void DestroyRenderables();
        void RequestDraw();
              
    protected:
        HGLRC       RenderingContext;
        HDC         HDCWindow;
        HWND        HWnd;
        LastError   err;
                
    private:
        void InitializeOpenGL();
        void ShutdownOpenGL();
        
        //Virtual Rendering Thread Function
        void VThreadProc();
        
        CONDITION_VARIABLE              RenderingThreadDone;        
        std::vector< IRenderable* >     Renderables;
        Mutex                           RendererLock;       
        bool                            bWindowResizing;
        bool                            bRendering;
        bool                            bShutDownRendererThread;
        
        PREVENT_COPYING( Renderer )
    };
    
    extern Renderer* g_pRenderer;
}
#endif //_RENDERER_

#include "stdafx.h"
#include "Renderer.h"

namespace renderer
{
    Renderer* g_pRenderer = nullptr;

    Renderer::Renderer()
        :m_hWnd( nullptr )
        ,bRendering( false )
        ,bShutDownRendererThread( false )
        ,bWindowResizing( false )
    {
        assert( !g_pRenderer );
        g_pRenderer = this;
    }
        
    Renderer::~Renderer()       
    {
        Thread::join(nullptr);
    }
    
    void Renderer::Init( HWND hwnd )
    {
        HWnd = hwnd;
        assert( HWnd );

        InitializeConditionVariable(&RenderingThreadDone);

        //Start thread
        Thread::VInit( Thread::normal );
        Thread::start();
        
        //Wait for Thread to finish init OpenGL
        if( SleepConditionVariableCS (&RenderingThreadDone, &RendererLock, INFINITE) == 0 )
            err.ShowLastError( LPTSTR(L"Renderer::Init SleepConditionVar error!") );

    }

    void Renderer::ShutDown()
    {
        RendererLock.Lock();
        if( bShutDownRendererThread ) return;
        while( bRendering || bWindowResizing )
        {
            MutexUnlock unlock(RendererLock);
            if( SleepConditionVariableCS (&RenderingThreadDone, &RendererLock, INFINITE) == 0 )
                err.ShowLastError( LPTSTR(L"Renderer::OnWindowResized SleepConditionVar error!") );
        }
        std::cout <<  "Requesting thread shut down \n";
        bShutDownRendererThread = true;
        RendererLock.Unlock();

        //Wait for Thread to finish shutting down OpenGL
        if( SleepConditionVariableCS (&RenderingThreadDone, &RendererLock, INFINITE) == 0 )
            err.ShowLastError( LPTSTR(L"Renderer::ShutDown SleepConditionVar error!") );
    }
    
    void Renderer::OnWindowResized()
    {       
        RendererLock.Lock();

        while( bRendering || bWindowResizing )
        {
            //MutexUnlock unlock(RendererLock);
            RendererLock.Unlock();
            if( SleepConditionVariableCS (&RenderingThreadDone, &RendererLock, INFINITE) == 0 )
                err.ShowLastError( LPTSTR(L"Renderer::OnWindowResized SleepConditionVar error!") );
        }
        
        std::cout <<  "Requesting window resize \n";
        bWindowResizing = true;     
        RendererLock.Unlock();
        
        //Wait for Thread to finish resize window
        if( SleepConditionVariableCS (&RenderingThreadDone, &RendererLock, INFINITE) == 0 )
            err.ShowLastError( LPTSTR(L"Renderer::OnWindowResized SleepConditionVar error!") );
        
    }
}

#include "stdafx.h"
#include "Renderer.h"
#include "..\Core\IRenderable.h"    

namespace renderer
{
    void Renderer::VThreadProc( void )
    {
        std::cout <<  "Initializing OpenGL... \n";
        InitializeOpenGL();

        //Set up initial render state
        ResizeWindow();

        //Initialize RenderableObjects
        CreateRenderables();

        //Finished init - wake main thread
        WakeConditionVariable (&RenderingThreadDone);

        while( true )
        {
            RendererLock.Lock();

            //Shut down?
            if( bShutDownRendererThread ) 
            {
                std::cout <<  "Shutting down rendering thread... \n";
                DestroyRenderables();

                ShutdownOpenGL();
                
                RendererLock.Unlock();

                WakeConditionVariable (&RenderingThreadDone);   
    
                break;
            }

            //Resize window?
            else if( bWindowResizing )
            {
                std::cout <<  "Resizing window in thread... \n";
                bWindowResizing = false;
                RendererLock.Unlock();

                ResizeWindow();
            }

            //Render?
            else if( bRendering )
            {
                bRendering = false;
                RendererLock.Unlock();

                //Render
                PreRender();
                Render(); 
                PostRender();
            }   
            else
                RendererLock.Unlock();
            
            WakeConditionVariable (&RenderingThreadDone);
        }
        return;
    }

    void Renderer::PreRender()
    {
        ClearBufferToColor( RENDERER_CLEAR_COLOR );
        PushMatrix( GetModelView3() );
    }

    void Renderer::Render() const
    {
        for( auto iter = m_renderableObjectList.cbegin(); iter != m_renderableObjectList.cend(); ++iter )
            (*iter)->Render();
    }

    void Renderer::PostRender()
    {
        DrawFrame();
        PopMatrix();
    }

    void Renderer::RegisterRenderable( IRenderable* renderableObject )
    {
        MutexLock scopedLock( RendererLock );
        m_renderableObjectList.push_back( renderableObject );
    }

    void Renderer::CreateRenderables()
    {
        for( auto iter = m_renderableObjectList.begin(); iter != m_renderableObjectList.end(); ++iter )
            (*iter)->IRenderCreate();
    }


    void Renderer::DestroyRenderables()
    {
        for( auto iter = m_renderableObjectList.begin(); iter != m_renderableObjectList.end(); ++iter )
            (*iter)->IRenderDestroy();
    }

    void Renderer::RequestDraw()
    {
        RendererLock.Lock();

        //Sync threads
        // |Simulate... wait |Simulate... wait |
        // |Render...........|Render...........|
        while( bRendering || bWindowResizing )
        {
            RendererLock.Unlock();
            if( SleepConditionVariableCS (&RenderingThreadDone, &RendererLock, INFINITE) == 0 )
                err.ShowLastError( LPTSTR("Renderer::RequestDraw SleepConditionVar error!") );
        }

        bRendering = true;

        RendererLock.Unlock();
    }
}