NoesisGUI Integration Tutorial

This tutorial focuses on the steps you must follow to integrate NoesisGUI in your own application and render interfaces with it. Although we provide an open source framework for using NoesisGUI in all our supported platforms, the Application Framework, this guide will show you the minimal steps to achieve it.

Note

Integration and IntegrationGLUT are fully annotated samples included inside the SDK. It is highly recommended following those samples when reading this guide.

Prerequisites

SDK Directories

The SDK is structured using the following root folders:

/Bin: this is the directory where Noesis dynamic library can be found. Your executable must be able to reach this path. The easiest way is by copying it to the executable location in a post-build step.

/Include: directory for public headers. You must add this path to the Additional Include Directories of your project

/Lib: object library to link against with is stored in this directory. Like the include directory, you must add this path to you Additional Libraries Directory. Apart from adding this directory to the project you must also link with the corresponding Noesis library.

/Build: project for building all samples is included in this directory.

/Data: location of many small samples for testing your application and XamlPlayer.

Initialization

Before being able to render any XAML, Noesis must be initialized by invoking Noesis::GUI::Init() and optionally passing an error handler, log handler and memory allocator.

The default error handler just redirects to the log handler. So, just setting only the log handler is probably enough. If you want to have control about Noesis allocations you must pass a memory allocator though. For simplicity's sake we are not doing it here.

Resource Providers

After initialization, a resource provider must be installed for each kind of asset your application is going to load. For example, loading resources from the current directory is achieved this way:

Each time a resource (xaml, texture, font) is needed, the corresponding provider is invoked to get a stream to the content. You must install a provider for each needed resource. There are a few implementations available in the app framework (like LocalXamlProvider to load from disk)

View creation

A view is needed to render the user interface and interact with it. A view holds a tree of elements. The easiest way to build interface trees is by loading them from XAML files. This can be done using the helper function LoadXaml. Once the XAML is loaded you must create a view with it and specify its dimensions. Each time your window or surface dimensions change you must indicate it to the view.

Once the view is created, its renderer must be initialized with a render device. You should provide your own implementation although we provide several reference implementations within the Application Framework.

Attaching to events

To have interaction with the user interface you need hooking to events. As described in the Events tutorial, there are many ways to achieve this. An easy way is connecting control events with local delegates. Just find each desired control by name and connect to a delegate.

Input Management

Once per frame you must gather input events from keyboard, mouse, touch and gamepad and send them to each view. For specific details about how to translate events from each window subsystem to Noesis we provide implementations for each platform in the Application Framework: Win32Display, AppKitDisplay, UIKitDisplay, XDisplay, etc.

Mouse

The following functions are available to indicate when the mouse moves, when is clicked and when the horizontal and vertical wheel are rotated.

Gamepad

In the Key enumeration used to send keyboard events, there are a few virtual codes specifically added to support hardware gamepad buttons:

Noesis Key

Xbox mapping

Key_GamepadLeft

D-pad left

Key_GamepadUp

D-pad up

Key_GamepadRight

D-pad right

Key_GamepadDown

D-pad down

Key_GamepadAccept

A button

Key_GamepadCancel

B button

Key_GamepadMenu

Menu button

Key_GamepadView

View button

Key_GamepadPageUp

Left trigger

Key_GamepadPageDown

Right trigger

Key_GamepadPageLeft

Left bumper

Key_GamepadPageRight

Right bumper

Key_GamepadContext1

X button

Key_GamepadContext2

Y button

Key_GamepadContext3

Left stick

Key_GamepadContext4

Right stick

Besides that, there are also two functions to send scrolling feedback to the view. You normally map this to the right analog stick.

voidScroll(floatvalue);voidHScroll(floatvalue);

The following image is an example about how to map the Xbox controller to Noesis events.

Update

Once per frame the view needs to be updated with the global time. At this point things like layout and animation are internally calculated and the current state is prepared to be displayed.

doubletime=GetGlobalTime();view->Update(time);

Note

A common error here is passing a delta time instead of a global one

Render

After updating, the view is ready to be rendered. Before sending commands to the GPU, first thing you must do is updating the renderer to collect commands from the last update performed.

view->GetRenderer()->UpdateRenderTree();

Note

UpdateRenderTree returns whether there are changes from the last render. This boolean can be used to skip rendering in case you have a valid copy of the last frame.

After updating the renderer, offscreen textures must be generated. This step populates all the internal textures that are needed for the current frame. From a performance point of view, it is critical to apply this step before binding the main render target.

view->GetRenderer()->RenderOffscreen();

After that, the main render target and viewport must be bound. Note that the above step for rendering offscreen textures modifies the GPU state, so you need to restore it. This is also the right moment to render you 3D scene in case you using a HUD (head-up display) interface.

Note

Because the Render() function modifies the GPU state, you must restore it properly to a sane state for your application. For performance reasons it is not done automatically. The most straightforward solution is to save device state before the call to RenderOffscreen() and restore it afterwards. Greater performance can be achieved if your application implements a way to invalidate its required states. This is faster because you avoid getting the current state from the driver.

And finally, the interface is rendered into the current render target.

view->GetRenderer()->Render();

Note

Masking, used to hide part of UI elements, is implemented in NoesisGUI using the StencilBuffer. Make sure that you are binding a a stencil buffer with at least 8 bits to properly visualize masks. And also make sure to clear it to zero before doing the on-screen UI rendering.

Finalization

Before exiting your application each view renderer must be shutdown. This must be done from the render thread if you are using any. Besides, each Ptr you own must be Reset(). After cleaning all objects, Noesis must be properly closed by invoking the Shutdown() function. This releases all internal allocated resources.