Menu

Pipeline

Anveena

Pipeline

After creating an instance of AED3D11RenderingEngine, the first thing you need to do is to create a rendering window and to initialize Direct3D. When you call the initialization functions, default parameters are retrieved from the INI file 'AEngine.ini' with the functions (from the class AERenderingEngine) :

bool __stdcall GetFromINIFileAsBool(const std::string &section, const std::string &key, bool bDefaultValueIfInvalid);  
int __stdcall GetFromINIFileAsInt(const std::string &section, const std::string &key, int iDefaultValueIfInvalid);
std::string __stdcall GetFromINIFileA(const std::string &section, const std::string &key, const std::string &DefaultValueIfInvalid);
std::wstring __stdcall GetFromINIFileW(const std::wstring &section, const std::wstring &key, const std::wstring &DefaultValueIfInvalid);

Of course, if you choose other parameters after the initialization, these parameters will overwrite the ones read from the INI file. If the INI file was not found, it is created and filled in with the default values. Thus, deleting the INI file is a good way to restore it if it was corrupted.

InitializeWindow

HRESULT __stdcall AE::Graphics::AERenderingEngine::InitWindow( FrameListenerBase *pFrameListener = nullptr, const WCHAR *pwcWindowName = nullptr, unsigned int width = 0, unsigned int height = 0, int nCmdShow = SW_SHOW);

Creates a window and an input listener after initializing OIS (input system library). You can pass as argument your own input listener deriving AE::FrameListenerBase to handle the messages as you like (see CEGUIFrameListener, the default input listener, for an example). Note that we use OIS to capture the keyboard messages only. We could use it to capture the mouse inputs too but it seems that if we do so, after a mouse message is captured by OIS, they won't be passed to the window procedure. Therefore we handle the mouse inputs 'manually' in the window procedure, which is shown below (from the file AERenderingEngine.cpp) :

LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    ...

    AERenderingEngine *pRenderingEngine = AERenderingEngine::GetSingleton();

    // Freeze keyboard and mouse inputs if the windows is inactive
    if(pRenderingEngine)
    {
        if(CEGUI::System::getSingletonPtr())
        {
            POINT point;
            GetCursorPos(&point);
            // Scaling the coordinates
            ...
            if(point.x <= 0 || point.y <= 0 || point.x >= (rect.right-rect.left) || point.y >= (rect.bottom-rect.top))
            {
                sbCursorInMainWindow = false;
                CEGUI::System::getSingleton().getDefaultGUIContext().getMouseCursor().hide();
            }
            else
            {
                sbCursorInMainWindow = true;
                CEGUI::System::getSingleton().getDefaultGUIContext().getMouseCursor().show();
                CEGUI::System::getSingleton().getDefaultGUIContext().injectMousePosition(x, y);

                if(sbWindowActive && pRenderingEngine->IsRunning())
                {
                    if( message == WM_LBUTTONDOWN ||
                        message == WM_LBUTTONDBLCLK ||
                        message == WM_MBUTTONDOWN ||
                        message == WM_MBUTTONDBLCLK ||
                        message == WM_RBUTTONDOWN ||
                        message == WM_RBUTTONDBLCLK ||
                        message == WM_LBUTTONUP ||
                        message == WM_MBUTTONUP ||
                        message == WM_RBUTTONUP ||
                        message == WM_MOUSEWHEEL)
                    {
                        InjectWin32ButtonToCegui(message);
                    }

                    if(!pRenderingEngine->IsConsoleDisplayed())
                    {
                        AEBaseCamera *pCamera = pRenderingEngine->GetCamera();
                        if(pCamera)
                            pCamera->HandleMessages(hWnd, message, wParam, lParam);
                    }
                }
            }
        }

    }

    // Process other messages
    ...

    return 0;
}

In any case, do not forget to inject mouse and keyboard inputs into CEGUI so that the GUI is updated properly.

As for mouse inputs, we need to convert the screen coordinates into window coordinates.

InitDevice

HRESULT __stdcall AE::Graphics::AERenderingEngine::InitDevice();    // abstract
HRESULT __stdcall AE::Graphics::AED3D11RenderingEngine::InitDevice();

This function performs a typical initialization of Direct3D 11 :

  • Checks if the chosen display mode is available (only for full screen rendering)
  • Creates the D3D device and a swap chain.
  • Creates a device context.
  • Recreates the device and the swap chain if MSAA was demanded (after checking if the MSAA count is available)
  • Creates a render target resource, and a depth stencil buffer with a default depth stencil state.
  • Create a rasterizer state and a viewport

Please refer to the tutorials from the DirectX SDK or to How to Use Direct3D 11 to for more technical details.

CreateDeviceDependentResources

This function must be called after InitDevice().

  • Creates the factories which manage the resources : Texture, Effect, Mesh, Font factories.
  • Creates a scene.
  • Initializes CEGUI

    • Creation of a D3D11 CEGUI renderer and of a CEGUI system
    • Creation of resource groups
    • Creation of default resources (the skin 'Vanilla' is used as default).
    • Creation of a root window, a CEGUI console used to parse user messages and a FPS label.

      Refer to CEGUI beginner guides for more information.

  • Creates worker threads to enable multi-threaded rendering (the engine will use them only if driver command lists are supported) and deferred contexts. NumberOfPhysicalCores-1 threads will be created.

A worker thread =

  • 1 deferred context per rendering group (all the entities belonging to a rendering group are rendered in the immediate context altogether, then we can render the other rendering groups)
  • 1 event to signal the thread to start the deferred rendering
  • 1 event per rendering group to signal that the thread finished the deferred rendering.

(See Introduction to Multithreaded rendering and the usage of Deferred contexts for an explanation of the concept and Immediate and Deferred Rendering for code samples)

Scene rendering

The principle is to determine in advance which object to render with frustrum culling and then to render them concurrently in deferred contexts running in worker threads.

Main loop

AEngine will loop indefinitely after you call AERenderingEngine::Run until the message WM_QUIT is received or until you press Alt-F4 (customizable in the input listener). At each frame we update the inputs and notify them to the input listener, update the elapsed time (which is used for some effects), the camera position and we traverse the whole scene.

// Main message loop
MSG msg = {0};
tbb::tick_count now = tbb::tick_count::now();
while( WM_QUIT != msg.message )
{
    if( m_pKeyboard )
    {
        m_pKeyboard->capture();
        if( !m_pKeyboard->buffered() )
            m_pEventHandler->HandleNonBufferedKeys(m_pKeyboard);
    }

    if( m_pMouse )
    {
        m_pMouse->capture();
        if( !m_pMouse->buffered() )
            m_pEventHandler->HandleNonBufferedMouse(m_pMouse);
    }

    if( PeekMessage( &msg, nullptr, 0, 0, PM_REMOVE ) )
    {
        TranslateMessage( &msg );
        DispatchMessage( &msg );
    }
    else
    {
        m_dElapsedTime = (tbb::tick_count::now()-now).seconds();
        now = tbb::tick_count::now();
        m_FPSMeter.Update(m_dElapsedTime);

        if(m_pScene)
            m_pScene->Update(m_dElapsedTime);
        if(m_pCamera && sbWindowActive &&!m_bDisplayConsole)
            m_pCamera->FrameMove(m_dElapsedTime);

        Render();
    }
}

You can use the function pointer AERenderingEngine::m_fnUpdateScene to pass you own function to update the scene right before the rendering.

Frustrum culling

The 3D viewing area on the screen where everything is drawn to is called the viewing frustum (whose parameters are defined by the camera). Everything that is inside the frustum will be rendered to the screen by the video card but the everything which is outside the frustrum will also be examined and then discarded during the rendering process. Thus, relying exclusively on the video card to cull for us can be expensive if we have large scenes.

Instead, we determine before rendering if a model is in our frustum or not (see struct AEFrustrum). For each entity, we retrieve its bounding box to determine whether it is in the frustrum or not (for faster calculations). If no bounding box exists, the entity will be rendered regardless of its position.

void __stdcall AERenderingEngine::PrepareSceneForRendering(SceneGlobalParameters *params, __out RenderingGroupsMap *pRenderingGroupsMap)

The function above deals with this task and process the objects of the scene concurrently, while filling a rendering order list (pRenderingGroupsMap), which comprises a priority list of concurrent queues.

If multithreading is enabled and supported, after the culling is done, worker threads pop items from this rendering order list and render them in deferred contexts which perform the rendering. For each rendering group, the main thread waits for the worker threads to finish the deferred rendering (via a handle on an event) and then executes the command lists generated by these deferred contexts. This way we can render the scene concurrently while guaranteeing the rendering order.

If no multithreading is available, the immediate context does all the rendering.

Rendering groups

The reason for the rendering order list is that to be drawn properly, some objects such as transparent ones or the GUI need to be drawn after all the other items are rendered. You can add rendering groups with AERenderingEngine::AddRenderingGroup and choose a group for each entity with AEEntity::SetRenderingGroup. The default group is Default (opaque objects).

The groups used are :

enum RenderingGroup
        {
            RenderOnImmediateContext = 0,
            Default = 10,   //! Opaque objects, default
            TransparentRenderOnImmediateContext = 15,
            Transparent = 20,   //! To be rendered correctly (semi-)transparent objects must be rendered after the other objects
            PostRendering = 30, // Post-processing (FXAA for instance)
        };

Messages from the rendering order queues : functors

The rendering order queues use functor containers using the class AE::Function::AEFunctor. We can pass class member functions taking one or two arguments, regardless of the class, and also functions. Example :

    Function::AEFunctor functor1(&AEEntity::Render, pointerOnTheEntityToRender, arg1, arg2);
    Function::AEFunctor functor2(&AED3D11WiredSphere::Render, pointerOnTheSphereToRender, arg1);
    Function::AEFunctor functor3(Render, pointerOnTheSphereToRender);
    m_MapRenderingGroup[Default]->push(functor1);
    m_MapRenderingGroup[Default]->push(functor2);
    m_MapRenderingGroup[Default]->push(functor3);
    // functor1() will call  pointerOnTheEntityToRender->Render(arg1, arg2)
    // If you want to pass different arguments regardless of the ones you used to initialize functor1, you can do it too :
    // functor1(otherArg1, otherArg2)

In AERenderingEngine, for the sake of consistency, all the functions passed to the rendering group queues in m_MapRenderingGroup follow this model : T::*function(const SceneGlobalParameters*, ID3D11DeviceContext*)

The second parameter ID3D11DeviceContext* will be ignored if we do not use multithreading and the immediate context only will be used. In previous Direct3D version, there is no device context. In this case we just pass const SceneGlobalParameters*

Remarks

When using deferred contexts, it is important not to forget to update the state of the device context after each call to ExecuteCommandList, for it is reset each time.

We use some particular rendering groups to render objects which can't be rendered (yet) properly using multithreading. These groups are RenderOnImmediateContext and TransparentRenderOnImmediateContext. When the worker threads process rendering messages from these groups, they skip it instead, letting the immediate context (and the main thread) perform the actions.

// Code from Render_MT which runs on worker threads
for(;;)
{
    WaitForSingleObject(pRenderingEngine->m_vBeginDeferredRenderingEvent[iThreadIndex], INFINITE);

    for(auto it = pRenderingEngine->m_vRenderingGroups.begin(); it != pRenderingEngine->m_vRenderingGroups.end(); it++)
    {
        if((*it) == RenderOnImmediateContext || (*it) == TransparentRenderOnImmediateContext)
        {
            SetEvent( pRenderingEngine->m_MapEndDeferredRenderingEvents[(*it)]->at(iThreadIndex) );
            continue;
        }
    ...

See AED3D11RenderingEngine::Render(), Render_MT (in AED3D11RenderingEngine.cpp) and AED3D11RenderingEngine::PrepareSceneRendering(const SceneGlobalParameters &params) to see how the frustrum culling is implemented, how we populate m_MapRenderingGroup and how we pop up messages from the queues, while synchronizing the work between the threads.

Sample code for the multithreaded rendering in the main thread (that is to say the thread on which the immediate context is running) :

for(auto it = m_vRenderingGroups.begin(); it != m_vRenderingGroups.end(); it++)
{
    if(*it == RenderOnImmediateContext || *it == Default || (*it) == Transparent || (*it) == TransparentRenderOnImmediateContext)
    {
        ...
        // Updates the state of the context
    }

    Function::AEFunctor FunctorRenderingCommand;
    while(m_RenderingGroupsMap[*it]->try_pop(FunctorRenderingCommand))
    {
        FunctorRenderingCommand(&params, m_pImmediateContext);
    }

    // Synchronization
    for(auto itEvent = m_MapEndDeferredRenderingEvents[*it]->begin(); itEvent != m_MapEndDeferredRenderingEvents[*it]->end(); itEvent++)
        WaitForSingleObject(*itEvent, INFINITE);
    for(auto itCommandList = m_MapCommandLists[*it]->begin(); itCommandList != m_MapCommandLists[*it]->end(); itCommandList++)
    {
        if(*itCommandList)
        {
            m_pImmediateContext->ExecuteCommandList(*itCommandList, false);
                SAFE_RELEASE( (*itCommandList) );
        }
    }

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.