Synchronization mechanism in DirectX/XAML UWP application

Probably it would be better if I learned Java 15 years ago because Java technologies do not change so rapidly, but I love C++ very much and I spent some time investigating how the application based on DirectX 11 and XAML Visual Studio 2015 template works on relatively new UWP platform.

image

In opposite to Win32, there are no main thread in UWP applications, because in UWP application each top-level window runs on its own thread. Our application has only one top-level window so it has one UI thread and also it has event handling thread and renderer thread (creating new top-level window will require some adaptive changes in our application).

Both event handling thread and render thread are created in FramePanel (originally DirectXPanel) constructor. Event handling thread is created with the following code:

// Register our SwapChainPanel to get independent input pointer events
auto workItemHandler = ref new WorkItemHandler([this](IAsyncAction ^)
{
    // The CoreIndependentInputSource will raise pointer events for the specified device types on whichever thread it's created on.
    m_coreInput = swapChainPanel->CreateCoreIndependentInputSource(
        Windows::UI::Core::CoreInputDeviceTypes::Mouse |
        Windows::UI::Core::CoreInputDeviceTypes::Touch |
        Windows::UI::Core::CoreInputDeviceTypes::Pen
        );

    // Register for pointer events, which will be raised on the background thread.
    m_coreInput->PointerPressed += ref new TypedEventHandler<Object^, PointerEventArgs^>(this, &FramePanel::OnPointerPressed);
    m_coreInput->PointerMoved += ref new TypedEventHandler<Object^, PointerEventArgs^>(this, &FramePanel::OnPointerMoved);
    m_coreInput->PointerReleased += ref new TypedEventHandler<Object^, PointerEventArgs^>(this, &FramePanel::OnPointerReleased);

    // Begin processing input messages as they're delivered.
    m_coreInput->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
});

it is not clear why not to subscribe directly to FramePanel’s events and what for do we really need the events coming from a separate thread? But from other side, what is the difference between those two ways? In both the cases we need to care about synchronization.

Creation of renderer thread:

// Create a task that will be run on a background thread.
auto workItemHandler = ref new WorkItemHandler([this](IAsyncAction ^ action)
{
    // Calculate the updated frame and render once per vertical blanking interval.
    while (action->Status == AsyncStatus::Started)
    {
        critical_section::scoped_lock lock(m_criticalSection);
        Update();
        if (Render())
        {
            m_deviceResources->Present();
        }
    }
});

// Run task on a dedicated high priority background thread.
m_renderLoopWorker = ThreadPool::RunAsync(workItemHandler, WorkItemPriority::High, WorkItemOptions::TimeSliced);

The critical section is used to make rendering an atomic operation, if we need to start/stop rendering loop, change DPI or window size, we lock this critical section. This kind of synchronization is enough for a sample application, but what synchronization mechanism should be used in a real-world application that has nontrivial data structures in UI thread and renderer thread? For example, if in UI thread I have a collection of an items having X and Y properties bound to some ListView allowing the user to add/remove/edit its items, how can I draw some small green ellipses at X,Y coordinates in render thread? The critical section is the obvious solution in this case, but it will result in the fatal performance bottleneck, because I need to add the critical section to X and Y property setters and to IObservableVector methods (create my own thread-safe IObservableVector implementation). For example, X setter will wait until rendering operation is completed so I will not be able to set X or Y more than 60 times per second. So my idea is that we need to keep a copy of the items collection in render thread and synchronize this copy with some mechanism like SynchronizationContext or Dispatcher that will allow UI thread to run arbitrary code on render thread. I called my experimental analog LambdaUpdateQueue:

#include "Awl/QuickList.h" 

template <class TUpdate>
class UpdateQueue
{
    struct Message : public awl::quick_link<Message>
    {
        TUpdate ^ Update;
    };

    class MessageQueue : public awl::quick_list<Message>
    {
    public:

        ~MessageQueue()
        {
            iterator i = begin();

            while (i != end())
            {
                delete *(i++);
            }
        }
    };

public:

    //called by UI thread to propagate changes to render thread
    void Push(TUpdate ^ update)
    {
        std::lock_guard<std::mutex> lock(queueMutex);

        Message * p_message = nullptr;

        if (FreeBlocks.empty())
        {
            p_message = new Message();
        }
        else
        {
            p_message = FreeBlocks.pop_front();
        }

        p_message->Update = update;

        PendingMessages.push_back(p_message);
    }

    //called by render thread to apply changes queued by UI thread
    void ApplyUpdates()
    {
        PrepareData();

        //we do not lock anything while applying the changes
        //so Push can be called while ApplyUpdates is still executed
        for (MessageQueue::const_iterator i = RenderingMessages.begin(); i != RenderingMessages.end(); ++i)
        {
            i->Update();
        }

        FreeData();
    }

private:

    void PrepareData()
    {
        std::lock_guard<std::mutex> lock(queueMutex);

        //move all the messages from PendingMessages to RenderingMessages
        //very short operation that actually performs a few assignments
        RenderingMessages.attach(PendingMessages);
    }

    void FreeData()
    {
        std::lock_guard<std::mutex> lock(queueMutex);

        for (MessageQueue::iterator i = RenderingMessages.begin(); i != RenderingMessages.end(); ++i)
        {
            //release lambda expression
            i->Update = nullptr;
        }

        //recycle RenderingMessages (also very short operation)
        FreeBlocks.push_back(RenderingMessages);
    }

    std::mutex queueMutex;

    //messages (changes) queued by UI thread while rendering operation is in progress
    MessageQueue PendingMessages;
    
    //we recycle freed queue elements to avoid superfluous dynamic memory allocation
    MessageQueue FreeBlocks;

    //messages (changes) that render thread is applying at the moment
    MessageQueue RenderingMessages;
};

public delegate void UpdateItemHandler();

typedef UpdateQueue<UpdateItemHandler> LambdaUpdateQueue;

Awl/QuickList.h is not included into C++ standard library yet, you can visit GitHub to learn more about it. So what for I spent so much time writing this specific list and what we achieved with this LambdaUpdateQueue? The answers are coming, Neo…but the matrix has you…

With LambdaUpdateQueue we can do actually whatever we want with render thread data. Let assume render thread has some object of type SceneRenderer that contains all the rendering data. To allow UI thread update its data, SceneRenderer contains LambdaUpdateQueue instance and Post(…) method:

public ref class SceneRenderer sealed
{
public:

    //dispatches an asynchronous message to the renderer
    void Post(UpdateItemHandler ^ item)
    {
        updateQueue.Push(item);
    }

    property SomeDataType Latitude
    {
        SomeDataType get()
        {
            return myData;
        }

        void set(SomeDataType val)
        {
            myData = val;
        }
    }

internal:

    //called from rendering loop (see above)
    void Update()
    {
        updateQueue.ApplyUpdates();
    }

private:

    LambdaUpdateQueue updateQueue;

    SomeDataType myData
};

UI thread can do the following in C++:

SomeDataType data = GetFromUI();

sceneRenderer->Post(ref new UpdateItemHandler([sceneRenderer, data]()
{
    sceneRenderer.Data = data;
}));

and the following in C#:

SomeDataType data = GetFromUI();

sceneRenderer.Post(new UpdateItemHandler(() =>
{
    sceneRenderer.Data = data;
}));

Yes, it was real, but how do you define real?

Leave a Reply

Your email address will not be published. Required fields are marked *