NoesisGUI

Rendering Architecture

NoesisGUI rendering architecture is designed to have two threads working in parallel: the main thread and the render thread. Although, as explained later, this is not strictly mandatory. You can have more threads or even have no threads at all. Under no circumstances our core library will create threads under the hood. This responsibility is delegated to the client who is in charge of invoking Noesis from the appropriate thread.

RenderingTutorialImg1.jpg

Main Thread

This is the thread where all logical interactions happen. Things like event dispatching and layout processing are happening 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);

// 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);

NOTE

Any violation of this thread-safety would be reported as an error

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 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 that is needed, reusing them as necessary if possible.

Render Thread

This is the thread that directly interacts with the GPU through the RenderDevice abstract class. Being Noesis rendering agnostic, it is client code responsibility to provide one RenderDevice implementation. The application framework provides many reference implementations. The RenderDevice also exposes a few functions to control the internal Vector Graphics context created. The default values are normally fine though.

Ptr<GLRenderDevice> device = *new GLRenderDevice();
device->SetGlyphCacheWidth(1024);
device->SetGlyphCacheHeight(1024);
device->SetGlyphCacheMeshThreshold(48);

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

view->GetRenderer()->Init(device);

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.

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

// Generates offscreen textures
view->GetRenderer()->RenderOffscreen();

// ------->
// HERE: Insert code to render your 3D scene
// <-------

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

Note that several views can be managed in the same render thread just by initializing all of them with the same device. That way you can share internal textures like ramps and glyphs with all the views. The RenderDevice 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 sharing the same RenderDevice instance.

Table of Contents

© 2017 Noesis Technologies