NoesisGUI

Rendering Architecture

In v1.3 we changed the rendering architecture to allow easier integration for our clients. We no longer create threads under the hood. This responsibility is delegated to the client who is in charge of invoking Noesis from the appropriate thread.

Similar to v1.2, this new architecture is designed to have two threads working in parallel: the main thread and the render thread. Although as you will see later this is not strictly mandatory. You can have more threads or even have no threads at all.

RenderingTutorialImg1.jpg

Main Thread

This is the thread where all logical interactions happen. Things like event dispatching and layout processing are calculated in this thread.

The object in charge of providing this functionality implements the IView interface. It can be created by calling GUI::CreateView().

// Loads XAML and creates a view with it
Ptr<FrameworkElement> xaml = Noesis::GUI::LoadXaml<FrameworkElement>(Button.xaml);
Ptr<IView> view = Noesis::GUI::CreateView(xaml.GetPtr());

// Sets logical size
view->SetSize(width, height);

IView instances are not thread-safe. All interactions must happen in the same thread that created it.

// Send input events to view
view->MouseButtonDown(x, y, button);
view->MouseButtonUp(x, y, button);
view->MouseDoubleClick(x, y, button);
view->MouseMove(x, y);

Once per frame the view instance must be ticked to update its internal representation. At this step the current state is locked and stored to be consumed by the render thread described in the next section.

// Updates view passing global time
view->Update(time);

Note that although you can only interact with the view from the owner thread, you are allowed to create several views in separate threads. This way you can update each view in parallel. Although the overhead of each view in v1.3 is low, it is recommended to have only one view per surface. In a normal scenario, you create a view for the main camera and a separate view for each render texture needed.

Render Thread

This is the thread that directly interacts with the GPU through the RenderDevice. In v1.3, Noesis no longer provides a render device. It is client code responsibility to provide one implementation. A render device instance is needed to create the Vector Graphics context. You can customize the VG context behavior by passing a VGOptions structure at creation time. The default values are normally fine though.

Ptr<GLRenderDevice> device = *new GLRenderDevice();

VGOptions options;
options.glyphCacheTextureWidth = 1024;
options.glyphCacheTextureHeight = 1024;
options.glyphCacheMeshTreshold = 48;

Ptr<VGContext> context = Noesis::GUI::CreateVGContext(device.GetPtr(), options);

Once the VG context is created you can initialize the view renderer with it. All interactions with the view in the render thread are isolated with the IRenderer interface that you can get by calling GetRenderer().

view->GetRenderer()->Init(context.GetPtr());

Each time you need to render a new frame you must call UpdateRenderTree() to collect pending update commands from the main thread. Once done that you can proceed with both offscreen and onscreen render stages. Note that offscreen rendering must be done before binding the main surface, this is critical for tiled architectures.

IRenderer* renderer = view->GetRenderer();

// Applies last changes happened in view
renderer->UpdateRenderTree();

// Generates offscreen textures, if needed
If (renderer->NeedsOffscreen())
{
    renderer->RenderOffscreen();
}

//
// Insert here code to render your 3D scene
//

// Render UI in the active render target and viewport dimensions
renderer->Render();

Note that several views can be managed in the same render thread just by initializing all of them with the same VG context. That way you can share internal textures like ramps and glyphs with all the views. VGContext is a heavyweight object. Extra instances of this object should be created carefully.

Note also that depending on your device implementation you could parallelize the UI rendering with rendering the rest of the scene. For example, this can be achieved in D3D11 by using a deferred context.

Normally there will be one render thread. But in case you are interested in interacting with different render devices, several render threads can be created. Each one must create its own instance of RenderDevice. These objects are not thread-safe and must not be shared. Following this pattern, each render thread is in in charge of collecting updates from one or several views.

© 2017 Noesis Technologies