NoesisGUI

NoesisGUI Integration Tutorial

github Tutorial Data

This tutorial concentrates on the steps you must follow to render XAML interfaces with NoesisGUI inside your own application.

Integration samples for OpenGL, DirectX v9.0, DirectX v11, Windows Store, Android, iOS and Windows Phone are provided. The code mentioned in this document is taken from that samples.

Prerequisites

This tutorial assumes that you are familiar about the following:

  • You know how to build .xaml files. If not, read the BuildTool document.
  • You understand how to extend NoesisGUI with your own classes as described in the Extending NoesisGUI tutorial.

SDK Directories

Apart from the SDK, that contains documentation and tools, you need a runtime for the platform you are working on. Runtimes for the supported platforms are provided independently. Inside each runtime you will find the following directories:

  • Bin/: This is the directory where dynamic libraries (.dll .so) can be found. Your executable must be able to reach this path. The easiest way is by copying these files to where your executable is located.
  • Include/: Directory for public headers. You must add this path to your additional include directories in your project
  • Lib/: Object libraries to link against are stored in this directory. Like the include directory, you must add this path to you additional libraries directory. Apart from adding this directory you must link with the proper library files.

Using NoesisGUI

An overview is given in this section to understand the steps that are needed to use NoesisGUI. This section applies to all the platforms. For specific details about each platform and fully working samples please read the next sections.

API

NoesisGUI API is provided in a single file, NoesisGUI.h and under the C++ namespace Noesis.

#include <NoesisGUI.h>

using namespace Noesis;

Note

Being a big file, it is recommended adding NoesisGUI.h to a precompiled header. We are doing that in our samples.

Initialization

Before being able to render any XAML the following steps must be performed once:

  1. Initialization by invoking a init function passing the rendering device and error handler.
void ErrorHandler(const NsChar* filename, NsInt line, const NsChar* desc)
{
    MessageBoxA(0, desc, "NoesisGUI Fatal Error", MB_ICONERROR);
    exit(1);
}

void main()
{
    Noesis::GUI::InitDirectX9(DXUTGetD3D9Device(), ErrorHandler);

    // ...
}
  1. Register own classes (optional) as described here.
// Register own classes
NsRegisterReflection(NsGetKernel()->GetComponentFactory(), true);

Note

The provided error handler must never return. Errors notified through this function must be always considered fatal.

Note

Optionally a Memory Allocator can be passed to the init function if you want to track all memory allocations done by NoesisGUI.

Resource Loading

XAMLs are loaded using the helper function LoadXaml. Once loaded, you need a renderer associated to the XAML. This renderer needs valid dimensions for the layout. Each time your window or surface dimensions change you must indicate it to the renderer.

// Create the UI renderer
Ptr<FrameworkElement> xaml = Noesis::GUI::LoadXaml<FrameworkElement>("UI.xaml");
xamlRenderer = Noesis::GUI::CreateRenderer(xaml.GetPtr());
xamlRenderer->SetSize(1024, 768);
xamlRenderer->SetAntialiasingMode(Noesis::Gui::AntialiasingMode_PPAA);

Attaching to events

You must connect UI events to local functions to allow UI interaction. An easy way is connecting control events with local delegates. You only have to find the desired controls by name and connect them to the proper delegates.

// Attach to events
Slider* slider = xaml->FindName<Slider>("Luminance");
if (slider != 0) slider->ValueChanged() += MakeDelegate(&LuminanceChanged);

An alternative is using a Code-Behind class.

Input Management

There are functions in the renderer interface to pass input events from your application to the UI. Your application must collect the events from the operating system and translate them to IRenderer calls.

renderer->MouseMove(x, y);
renderer->MouseButtonDown(x, y, MouseButton_Left);
renderer->MouseButtonUp(x, y, MouseButton_Left);
renderer->MouseWheel(x, y, delta);
renderer->KeyDown(key);
renderer->KeyUp(key);
renderer->Char(char);

Update and Render

In the loop of your application your must perform the following steps:

  1. Tick the kernel to update all the systems.
// Tick kernel
Noesis::GUI::Tick();
  1. Update each renderer with the current time and collect its render commands.
// Update UI
renderer->Update(timeInSeconds);
RenderCommands commands = gXamlRenderer->WaitForUpdate();

Note

Update() is asynchronous. To improve CPU usage you could perform additional work between Update() and WaitForUpdate(). Also if there is a render thread in your architecture you should invoke WaitForUpdate and Render in that thread.

  1. Render: the returned render commands from WaitForUpdate() are executed by calling the Render() function that send the data to the GPU. This is the unique function that invoke GPU commands. There are two kind of blocks inside RenderCommands:
  • Offscreen Commands: these commands update textures that are needed by the main render. For optimal performance these commands must be executed before setting and clearing the final surface. This is critical for tiled architectures, present in most mobile GPUs.
  • Direct Commands: these are the commands that directly render in the active surface. These commands must be executed with the desired color and stencil surface properly bound. This step is normally done after rendering the 3D scene, that way the UI is rendered on top of the scene.

Note

Masking, used to hide part of a UI element, is implemented in NoesisGUI using the StencilBuffer. Make sure that you are binding a color buffer and a stencil buffer with at least 8 bits to properly visualize masks.

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 Render() 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.

// Render offscreen
if (commands.offscreenCommands != 0)
{
    xamlRenderer->Render(commands.offscreenCommands.GetPtr());
}

// Start frame
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// Render Scene
// ...

// Render HUD
xamlRenderer->Render(commands.commands.GetPtr());

// SwapBuffers
// ...

Finalization

Before exiting from your application the kernel must be properly closed invoking the Shutdown() function. This will close all systems and free all resources. Remember to free all resources before that invocation (for example, if you have global objects).

// Free global resources and shutdown kernel
xamlRenderer.Reset();
Noesis::GUI::Shutdown();

DirectX v9.0

Visual Studio 2005, 2008, 2010 and 2012 compiler versions are supported. We are going to describe how to configure a Visual Studio 2008 project. Similar steps must be performed on the rest.

  1. Add path to Additional Include Directories
  2. Add path to Additional Library Directories
  3. Add the following libraries to additional Dependencies in Linker Input: Noesis.lib

The sample for DirectX v9.0 is a modified version of the ShadowVolume scene include in the DirectX SDK. For brevity only relevant parts are included here. Please download the full sample for more information.

Initialization

Noesis::GUI::InitDirectX9(DXUTGetD3D9Device(), ErrorHandler);
Noesis::GUI::AddResourceProvider(".");

// Create the UI renderer
Ptr<FrameworkElement> xaml = Noesis::GUI::LoadXaml<FrameworkElement>("UI.xaml");
gXamlRenderer = Noesis::GUI::CreateRenderer(xaml.GetPtr());
gXamlRenderer->SetSize(640, 480);
gXamlRenderer->SetAntialiasingMode(Noesis::Gui::AntialiasingMode_PPAA);

// Attach to events
Slider* slider = xaml->FindName<Slider>("Luminance");
if (slider != 0) slider->ValueChanged() += MakeDelegate(&LuminanceChanged);

ComboBox* combo0 = xaml->FindName<ComboBox>("Background");
if (combo0 != 0) combo0->SelectionChanged() += MakeDelegate(&BackgroundChanged);

ComboBox* combo1 = xaml->FindName<ComboBox>("Lights");
if (combo1 != 0) combo1->SelectionChanged() += MakeDelegate(&LightsChanged);

ComboBox* combo2 = xaml->FindName<ComboBox>("Visualization");
if (combo2 != 0) combo2->SelectionChanged() += MakeDelegate(&VisualizationChanged);

Message Handling

Mouse Capture can be easily implemented using VisualTreeHelper::HitTest as shown in the example code.

static bool captured = false;

if (gXamlRenderer != 0)
{
    switch (uMsg)
    {
        case WM_KEYDOWN:
        {
            gXamlRenderer->KeyDown(wParam < 256 ? gKeyMap[wParam] : Noesis::Key_None);
            break;
        }
        case WM_KEYUP:
        {
            gXamlRenderer->KeyUp(wParam < 256 ? gKeyMap[wParam] : Noesis::Key_None);
            break;
        }
        case WM_CHAR:
        {
            gXamlRenderer->Char(NsUInt32(wParam));
            break;
        }
        case WM_MOUSEMOVE:
        {
            gXamlRenderer->MouseMove(LOWORD(lParam), HIWORD(lParam));
            break;
        }
        case WM_LBUTTONDOWN:
        {
            gXamlRenderer->MouseButtonDown(LOWORD(lParam), HIWORD(lParam), MouseButton_Left);

            Point p(LOWORD(lParam), HIWORD(lParam));
            if (VisualTreeHelper::HitTest(gXamlRenderer->GetContent(), p).visualHit != 0)
            {
                captured = true;
            }
            break;
        }
        case WM_LBUTTONUP:
        {
            gXamlRenderer->MouseButtonUp(LOWORD(lParam), HIWORD(lParam), MouseButton_Left);
            captured = false;
            break;
        }
        case WM_MOUSEWHEEL:
        {
            POINT point;
            point.x = LOWORD(lParam);
            point.y = HIWORD(lParam);
            ::ScreenToClient(hWnd, &point);

            gXamlRenderer->MouseWheel(point.x, point.y, GET_WHEEL_DELTA_WPARAM(wParam));
            break;
        }
    }
}

if (!captured)
{
    g_Camera.HandleMessages( hWnd, uMsg, wParam, lParam );
    g_MCamera.HandleMessages( hWnd, uMsg, wParam, lParam );
    g_LCamera.HandleMessages( hWnd, uMsg, wParam, lParam );
}

Lost-Device

if (gXamlRenderer != 0)
{
    Noesis::GUI::OnLostDevice();
}
SAFE_RELEASE(gStateBlock);

Reset-Device

pd3dDevice->CreateStateBlock(D3DSBT_ALL, &gStateBlock);

if (gXamlRenderer != 0)
{
    Noesis::GUI::OnResetDevice();
    gXamlRenderer->SetSize(pBackBufferSurfaceDesc->Width, pBackBufferSurfaceDesc->Height);
}

Frame

Note that in DirectX 9 we are using a StateBlock object to save and restore the GPU state.

// Render the scene
if( SUCCEEDED( pd3dDevice->BeginScene() ) )
{
    // Tick kernel
    Noesis::GUI::Tick();

    // Update UI
    gXamlRenderer->Update(fTime);
    RenderCommands commands = gXamlRenderer->WaitForUpdate();

    // Render offscreen textures
    if (commands.offscreenCommands != 0)
    {
        IDirect3DSurface9* color;
        DXUTGetD3D9Device()->GetRenderTarget(0, &color);
        color->Release();

        IDirect3DSurface9* depth;
        DXUTGetD3D9Device()->GetDepthStencilSurface(&depth);
        depth->Release();

        V(gStateBlock->Capture());
        gXamlRenderer->Render(commands.offscreenCommands.GetPtr());
        V(gStateBlock->Apply());

        DXUTGetD3D9Device()->SetRenderTarget(0, color);
        DXUTGetD3D9Device()->SetDepthStencilSurface(depth);
    }

    // Clear the render target and the zbuffer
    V( pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB( 0, 66, 75, 121 ), 1.0f, 0 ) );

    ...
    ///////////////////////
    // RENDER SCENE
    ///////////////////////
    ...

    // Draw GUI
    V(gStateBlock->Capture());
    gXamlRenderer->Render(commands.commands.GetPtr());
    V(gStateBlock->Apply());


    V( pd3dDevice->EndScene() );

}

Shutdown

// Free global resources and shutdown kernel
gXamlRenderer.Reset();
Noesis::GUI::Shutdown();
SDKGuideImg1.jpg

DirectX v11.0

The sample provided for DirectX 11 is very similar to the DirectX 9 one. We took a tutorial from MSDN and adapted it. Similar steps must be performed. For initialization the function InitDirectX11 must be used instead.

Noesis::GUI::InitDirectX11(g_pd3dDevice, ErrorHandler);
Noesis::GUI::AddResourceProvider(".");

Ptr<FrameworkElement> xaml = Noesis::GUI::LoadXaml<FrameworkElement>("ComboBox.xaml");
xamlRenderer = Noesis::GUI::CreateRenderer(xaml.GetPtr());
xamlRenderer->SetSize(800, 600);
xamlRenderer->SetAntialiasingMode(Noesis::Gui::AntialiasingMode_PPAA);

Windows Store

Follow the DirectX v11.0 guide for Windows Store.

OpenGL

The desktop OpenGL sample includes projects for both Visual Studio and Xcode. The code, that uses GLUT, is quite straightforward and easy to understand.

#include "pch.h"

using namespace Noesis;

Ptr<IRenderer> xamlRenderer;
int width, height;

#ifdef _MSC_VER
    #define GL_FRAMEBUFFER 0x8D40

    typedef void (WINAPI *PFNGLBINDFRAMEBUFFERPROC)(GLenum target, GLuint framebuffer);
    PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer;

    typedef void (WINAPI * PFNGLUSEPROGRAMPROC)(GLuint program);
    PFNGLUSEPROGRAMPROC glUseProgram;
#endif

/*
** Function called to update rendering
*/
void DisplayFunc(void)
{
    // Tick kernel
    Noesis::GUI::Tick();

    // Update UI
    xamlRenderer->Update(glutGet(GLUT_ELAPSED_TIME) / 1000.0f);
    // ...Do something useful here because Update() is concurrent...
    RenderCommands commands = xamlRenderer->WaitForUpdate();

    // Render offscreen textures
    if (commands.offscreenCommands != 0)
    {
        xamlRenderer->Render(commands.offscreenCommands.GetPtr());
    }

    static float alpha = 0;

    glClearColor(0, 0, 0, 0);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glClearDepth(1.0f);
    glDepthMask(GL_TRUE);

    glDisable(GL_CULL_FACE);
    glDisable(GL_ALPHA_TEST);
    glDisable(GL_STENCIL_TEST);
    glDisable(GL_BLEND);
    glDisable(GL_SCISSOR_TEST);

    glUseProgram(0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glViewport(0, 0, width, height);
    glColorMask(true, true, true, true);

    /* Clear the buffer, clear the matrix */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    /* A step backward, then spin the cube */
    glTranslatef(0, 0, -10);
    glRotatef(30, 1, 0, 0);
    glRotatef(alpha, 0, 1, 0);

    /* We tell we want to draw quads */
    glBegin(GL_QUADS);

    /* Every four calls to glVertex, a quad is drawn */
    glColor3f(0, 0, 0); glVertex3f(-1, -1, -1);
    glColor3f(0, 0, 1); glVertex3f(-1, -1,  1);
    glColor3f(0, 1, 1); glVertex3f(-1,  1,  1);
    glColor3f(0, 1, 0); glVertex3f(-1,  1, -1);

    glColor3f(1, 0, 0); glVertex3f( 1, -1, -1);
    glColor3f(1, 0, 1); glVertex3f( 1, -1,  1);
    glColor3f(1, 1, 1); glVertex3f( 1,  1,  1);
    glColor3f(1, 1, 0); glVertex3f( 1,  1, -1);

    glColor3f(0, 0, 0); glVertex3f(-1, -1, -1);
    glColor3f(0, 0, 1); glVertex3f(-1, -1,  1);
    glColor3f(1, 0, 1); glVertex3f( 1, -1,  1);
    glColor3f(1, 0, 0); glVertex3f( 1, -1, -1);

    glColor3f(0, 1, 0); glVertex3f(-1,  1, -1);
    glColor3f(0, 1, 1); glVertex3f(-1,  1,  1);
    glColor3f(1, 1, 1); glVertex3f( 1,  1,  1);
    glColor3f(1, 1, 0); glVertex3f( 1,  1, -1);

    glColor3f(0, 0, 0); glVertex3f(-1, -1, -1);
    glColor3f(0, 1, 0); glVertex3f(-1,  1, -1);
    glColor3f(1, 1, 0); glVertex3f( 1,  1, -1);
    glColor3f(1, 0, 0); glVertex3f( 1, -1, -1);

    glColor3f(0, 0, 1); glVertex3f(-1, -1,  1);
    glColor3f(0, 1, 1); glVertex3f(-1,  1,  1);
    glColor3f(1, 1, 1); glVertex3f( 1,  1,  1);
    glColor3f(1, 0, 1); glVertex3f( 1, -1,  1);

    /* No more quads */
    glEnd();

    /* Rotate a bit more */
    alpha = alpha + 0.1;

    // Draw GUI
    xamlRenderer->Render(commands.commands.GetPtr());

    /* End */
    glFlush();
    glutSwapBuffers();

    /* Update again and again */
    glutPostRedisplay();
}

/*
** Function called when the window is created or resized
*/
void ReshapeFunc(int width_, int height_)
{
    width = width_;
    height = height_;

    glMatrixMode(GL_PROJECTION);

    glLoadIdentity();
    gluPerspective(20, width / (float) height, 5, 15);

    glMatrixMode(GL_MODELVIEW);
    glutPostRedisplay();

    xamlRenderer->SetSize(width, height);
}

void KeyboardFunc(unsigned char key, int x, int y)
{
    if (key == 9)
    {
        xamlRenderer->KeyDown(Noesis::Key_Tab);
    }
    else if (key == 8)
    {
        xamlRenderer->KeyDown(Noesis::Key_Back);
    }
    else
    {
        xamlRenderer->Char(key);
    }
}

void KeyboardUpFunc(unsigned char key, int x, int y)
{
    if (key == 9)
    {
        xamlRenderer->KeyUp(Noesis::Key_Tab);
    }
    else if (key == 8)
    {
        xamlRenderer->KeyUp(Noesis::Key_Back);
    }
}

void MouseFunc(int button, int state, int x, int y)
{
    if (button == GLUT_LEFT_BUTTON)
    {
        if (state == GLUT_UP)
        {
            xamlRenderer->MouseButtonUp(x, y, MouseButton_Left);
        }
        else
        {
            xamlRenderer->MouseButtonDown(x, y, MouseButton_Left);
        }
    }
}

void MouseMove(int x, int y)
{
    xamlRenderer->MouseMove(x, y);
}

void ErrorHandler(const NsChar* filename, NsInt line, const NsChar* desc)
{
    printf("\nERROR: %s\n\n", desc);
    exit(1);
}

void Shutdown(void)
{
    // Free global resources and shutdown kernel
    xamlRenderer.Reset();
    Noesis::GUI::Shutdown();
}

int main(int argc, char **argv)
{
    /* Creation of the window */
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(500, 500);
    glutCreateWindow("Spinning cube");

#ifdef _MSC_VER
    glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)wglGetProcAddress("glBindFramebuffer");
    glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram");
#endif

    // NoesisGUI setup
    Noesis::GUI::InitOpenGL(ErrorHandler);
    Noesis::GUI::AddResourceProvider("..");

    // Create the UI renderer
    Ptr<FrameworkElement> xaml = Noesis::GUI::LoadXaml<FrameworkElement>("UI.xaml");
    xamlRenderer = Noesis::GUI::CreateRenderer(xaml.GetPtr());
    xamlRenderer->SetAntialiasingMode(Noesis::Gui::AntialiasingMode_PPAA);

    atexit(Shutdown);

    /* Declaration of the callbacks */
    glutDisplayFunc(&DisplayFunc);
    glutReshapeFunc(&ReshapeFunc);
    glutKeyboardFunc(&KeyboardFunc);
    glutKeyboardUpFunc(&KeyboardUpFunc);
    glutMouseFunc(&MouseFunc);
    glutMotionFunc(&MouseMove);
    glutPassiveMotionFunc(&MouseMove);

    /* Loop */
    glutMainLoop();

    /* Never reached */
    return 0;
}

/* ========================================================================= */
SDKGuideImg2.jpg

iOS

The provided sample for iOS is configured using XCode 4.6. Include and Library directories and configured in the Search Paths section of the project settings. As dynamic libraries are not allowed in iOS we need to statically link against the library found in the /Lib folder. We used the following extra settings:

ARCHS = armv7;
CLANG_CXX_LIBRARY = "libstdc++";
GCC_SYMBOLS_PRIVATE_EXTERN = YES;
HEADER_SEARCH_PATHS = "../../../Runtimes/NoesisGUI-ios/Include";
LIBRARY_SEARCH_PATHS = "../../../Runtimes/NoesisGUI-ios/Lib";
OTHER_LDFLAGS = "-lnoesis";

Apart from that, the following Frameworks are needed:

  • UIKit.framework
  • Foundation.framework
  • CoreGraphics.framework
  • GLKit.framework
  • OpenGLES.framework

Initialization

Noesis::GUI::InitOpenGL(ErrorHandler);

static NsChar rootPath[PATH_MAX];
CFBundleRef mainBundle = CFBundleGetMainBundle();
CFURLRef url = CFBundleCopyBundleURL(mainBundle);
CFStringRef str = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
CFStringGetCString(str, rootPath, sizeof(rootPath), kCFStringEncodingUTF8);
CFRelease(url);
CFRelease(str);

Noesis::GUI::AddResourceProvider(rootPath);

// Create the UI renderer
Ptr<FrameworkElement> xaml = Noesis::GUI::LoadXaml<FrameworkElement>("Data/Tux.xaml");
_xamlRenderer = Noesis::GUI::CreateRenderer(xaml.GetPtr());

Render

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    // Tick kernel
    Noesis::GUI::Tick();

    static NsFloat64 t;
    t += self.timeSinceLastUpdate;

    // Update renderer
    _xamlRenderer->SetSize(view.drawableWidth, view.drawableHeight);
    _xamlRenderer->Update(t);
    RenderCommands commands = _xamlRenderer->WaitForUpdate();

    // Render offscreen surfaces
    _xamlRenderer->Render(commands.offscreenCommands.GetPtr());

    // Restore the state
    [view bindDrawable];
    glDisable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glDepthMask(GL_TRUE);
    glClearDepthf(1.0f);

    glClearColor(0.40f, 0.40f, 0.40f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glBindVertexArrayOES(_vertexArray);

    // Render the object with GLKit
    [self.effect prepareToDraw];

    glDrawArrays(GL_TRIANGLES, 0, 36);

    // Render the object again with ES2
    glUseProgram(_program);

    glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX], 1, 0, _modelViewProjectionMatrix.m);
    glUniformMatrix3fv(uniforms[UNIFORM_NORMAL_MATRIX], 1, 0, _normalMatrix.m);

    glDrawArrays(GL_TRIANGLES, 0, 36);

    // Render HUD
    _xamlRenderer->Render(commands.commands.GetPtr());
}
SDKGuideImg3.jpg

Android

The integration sample for Android uses the Native Android API. A Java Activity is needed to preload NoesisGUI library.

package com.example.hellotriangle;

import android.app.NativeActivity;
import android.os.Bundle;

public class HelloTriangle extends NativeActivity {

    static {
        System.loadLibrary("Noesis");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(android.R.layout.activity_list_item);
    }
}

Built resources must be copied inside the /assets folder. They are accesed by the AndroidResourceProvider that is created indicating where the data root is located.

Ptr<AndroidResourceProvider> provider = *new AndroidResourceProvider(
    userData->app->activity->assetManager, "NoesisGUI/", "");

Apart from those details the sample is a very basic OpenGL ES 2.0 example.

////////////////////////////////////////////////////////////////////////////////////////////////////
/// Hello Triangle: An OpenGL ES 2.0 Example
////////////////////////////////////////////////////////////////////////////////////////////////////


#include <stdlib.h>

#include <android_native_app_glue.h>
#include <android/native_window.h>
#include <android/log.h>

#include <EGL/egl.h>
#include <GLES2/gl2.h>


#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "HelloTriangle", __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "HelloTriangle", __VA_ARGS__))


#define NOESIS_GUI

#ifdef NOESIS_GUI
    #include <NoesisGUI.h>
    using namespace Noesis;
#endif


struct UserData
{
    android_app* app;

    bool animating;

    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    EGLConfig pixelFormat;

    GLuint fbo;

    GLuint vShader;
    GLuint fShader;
    GLuint programObject;

    GLint width;
    GLint height;

#ifdef NOESIS_GUI
    Ptr<IRenderer> xamlRenderer;
    RenderCommands xamlCommands;
#endif
};

#ifdef NOESIS_GUI
////////////////////////////////////////////////////////////////////////////////////////////////
void NoesisErrorHandler(const NsChar* filename, NsInt line, const NsChar* desc)
{
    LOGE("Error: %s [%d] : %s", filename, line, desc);
    exit(-1);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void NoesisInit(UserData* userData)
{
    Noesis::GUI::InitOpenGL(NoesisErrorHandler);

    Ptr<AndroidResourceProvider> provider = *new AndroidResourceProvider(
        userData->app->activity->assetManager, "NoesisGUI/", "");
    Noesis::GUI::AddResourceProvider(provider.GetPtr());

    Ptr<FrameworkElement> xaml = Noesis::GUI::LoadXaml<FrameworkElement>("UI.xaml");

    userData->xamlRenderer = Noesis::GUI::CreateRenderer(xaml.GetPtr());
    userData->xamlRenderer->SetSize(userData->width, userData->height);
    userData->xamlRenderer->SetAntialiasingMode(Noesis::Gui::AntialiasingMode_PPAA);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void NoesisResize(UserData* userData)
{
    userData->xamlRenderer->SetSize(userData->width, userData->height);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void NoesisTick()
{
    // Tick kernel
    Noesis::GUI::Tick();
}

////////////////////////////////////////////////////////////////////////////////////////////////
void NoesisPreRender(UserData* userData)
{
    // Update renderer
    userData->xamlRenderer->Update(HighResTimer::Seconds() - HighResTimer::StartTime());
        // ...Do something useful here because Update() is concurrent...
    userData->xamlCommands = userData->xamlRenderer->WaitForUpdate();

    // Render offscreen textures
    if (userData->xamlCommands.offscreenCommands != 0)
    {
        userData->xamlRenderer->Render(userData->xamlCommands.offscreenCommands.GetPtr());
        userData->xamlCommands.offscreenCommands.Reset();
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////
void NoesisPostRender(UserData* userData)
{
    // Render GUI to the active surface
    userData->xamlRenderer->Render(userData->xamlCommands.commands.GetPtr());
    userData->xamlCommands.commands.Reset();
}

////////////////////////////////////////////////////////////////////////////////////////////////
void NoesisShutdown(UserData* userData)
{
    // free renderer resources
    userData->xamlCommands.offscreenCommands.Reset();
    userData->xamlCommands.commands.Reset();
    userData->xamlRenderer.Reset();

    // shut down NoesisGUI
    Noesis::GUI::Shutdown();
}
#endif

////////////////////////////////////////////////////////////////////////////////////////////////
GLuint LoadShader(const char *shaderSrc, GLenum type)
{
    // Create the shader object
    GLuint shader = glCreateShader(type);
    if (shader == 0)
    {
        LOGE("Unable to create shader [type=%X]", type);
        return 0;
    }

    // Load the shader source
    glShaderSource(shader, 1, &shaderSrc, NULL);

    // Compile the shader
    glCompileShader(shader);

    // Check the compile status
    GLint compiled;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    if (!compiled)
    {
        GLint infoLen = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
        if (infoLen > 1)
        {
            char msg[512];
            glGetShaderInfoLog(shader, 512, NULL, msg);
            LOGE("Error compiling shader: %s", msg);
        }
        glDeleteShader(shader);

        return 0;
    }

    return shader;
}

////////////////////////////////////////////////////////////////////////////////////////////////
bool UpdateDisplaySize(UserData* userData)
{
    GLint w, h;
    w = userData->app->contentRect.left - userData->app->contentRect.right;
    h = userData->app->contentRect.top - userData->app->contentRect.bottom;

    if (w == 0 || h == 0)
    {
        w = ANativeWindow_getWidth(userData->app->window);
        h = ANativeWindow_getHeight(userData->app->window);

        if (w == 0 || h == 0)
        {
            eglQuerySurface(userData->display, userData->surface, EGL_WIDTH, &w);
            eglQuerySurface(userData->display, userData->surface, EGL_HEIGHT, &h);
        }
    }

    if (userData->width != w || userData->height != h)
    {
        userData->width = w;
        userData->height = h;
        LOGI("Surface size: %dx%d", w, h);
        return true;
    }

    return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////
bool InitDisplay(UserData* userData)
{
    // initialize OpenGL ES and EGL
    EGLSurface surface;
    EGLContext context;

    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

    if (eglInitialize(display, 0, 0) == EGL_FALSE)
    {
        LOGE("Can't initialize display");
        return false;
    }

    if (!eglBindAPI(EGL_OPENGL_ES_API))
    {
        LOGE("Can't bind OpenGL ES API");
        return false;
    }

    // Here specify the attributes of the desired configuration.
    // Below, we select an EGLConfig with at least 8 bits per color
    // component compatible with on-screen windows
    const EGLint attribs[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_BLUE_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_RED_SIZE, 8,
        EGL_ALPHA_SIZE, 8,
        EGL_DEPTH_SIZE, 24,
        EGL_STENCIL_SIZE, 8,
        EGL_NONE
    };

    // Here, the application chooses the configuration it desires. In this
    // sample, we have a very simplified selection process, where we pick
    // the first EGLConfig that matches our criteria
    EGLint numConfigs;
    EGLConfig config;
    eglChooseConfig(display, attribs, &config, 1, &numConfigs);
    if (numConfigs < 1)
    {
        LOGE("Unable to eglChooseConfig");
        return false;
    }

    // EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
    // guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
    // As soon as we picked a EGLConfig, we can safely reconfigure the
    // ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID.
    EGLint format;
    eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);

    ANativeWindow_setBuffersGeometry(userData->app->window, 0, 0, format);

    surface = eglCreateWindowSurface(display, config, userData->app->window, NULL);

    // Specify an OpenGL ES 2.0 context
    const EGLint contextAttribs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE
    };
    context = eglCreateContext(display, config, NULL, contextAttribs);

    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
    {
        LOGE("Unable to eglMakeCurrent");
        return false;
    }

    GLint fbo;
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo);

    // Create a shader for rendering the triangle
    const char vShaderStr[] =
        "attribute vec4 vPosition; \n"
        "void main() \n"
        "{ \n"
        "  gl_Position = vPosition; \n"
        "} \n";
    const char fShaderStr[] =
        "precision mediump float; \n"
        "void main() \n"
        "{ \n"
        "  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n"
        "} \n";

    // Load the vertex/fragment shaders
    GLuint vertexShader = LoadShader(vShaderStr, GL_VERTEX_SHADER);
    if (vertexShader == 0)
    {
        return false;
    }
    GLuint fragmentShader = LoadShader(fShaderStr, GL_FRAGMENT_SHADER);
    if (fragmentShader == 0)
    {
        return false;
    }

    // Create the program object
    GLuint programObject = glCreateProgram();
    if (programObject == 0)
    {
        LOGE("Unable to create shader program object");
        return false;
    }

    glAttachShader(programObject, vertexShader);
    glAttachShader(programObject, fragmentShader);

    // Bind vPosition to attribute 0
    glBindAttribLocation(programObject, 0, "vPosition");

    // Link the program
    glLinkProgram(programObject);

    // Check the link status
    GLint linked;
    glGetProgramiv(programObject, GL_LINK_STATUS, &linked);
    if (!linked)
    {
        GLint infoLen = 0;
        glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &infoLen);
        if (infoLen > 1)
        {
            char msg[512];
            glGetProgramInfoLog(programObject, 512, NULL, msg);
            LOGE("Error linking program: %s", msg);
        }
        glDeleteProgram(programObject);

        return false;
    }

    // Store all created objects
    userData->display = display;
    userData->context = context;
    userData->surface = surface;
    userData->pixelFormat = config;
    userData->fbo = fbo;
    userData->vShader = vertexShader;
    userData->fShader = fragmentShader;
    userData->programObject = programObject;

    UpdateDisplaySize(userData);

    // Initialize GL state
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

#ifdef NOESIS_GUI
    NoesisInit(userData);
#endif

    return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////
void ResizeDisplay(UserData* userData)
{
    if (userData->display == EGL_NO_DISPLAY)
    {
        return;
    }

    if (UpdateDisplaySize(userData))
    {
        // display size changed
#ifdef NOESIS_GUI
        NoesisResize(userData);
#endif
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////
void DrawFrame(UserData* userData)
{
    if (userData->display == EGL_NO_DISPLAY)
    {
        return;
    }

#ifdef NOESIS_GUI
    NoesisTick();

    NoesisPreRender(userData);
#endif

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // Set display dimensions
    glViewport(0, 0, userData->width, userData->height);

    // Clear the color buffer
    glClear(GL_COLOR_BUFFER_BIT);

    // Use the program object
    glUseProgram(userData->programObject);

    // Load the vertex data
    GLfloat vertices[] = { 0.0f,0.5f,0.0f, -0.5f,-0.5f,0.0f, 0.5f,-0.5f,0.0f };
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(0);
    glDrawArrays(GL_TRIANGLES, 0, 3);

#ifdef NOESIS_GUI
    NoesisPostRender(userData);
#endif

    // End
    glFlush();
    eglSwapBuffers(userData->display, userData->surface);
}

////////////////////////////////////////////////////////////////////////////////////////////////
void ShutdownDisplay(UserData* userData)
{
    if (userData->display == EGL_NO_DISPLAY)
    {
        return;
    }

#ifdef NOESIS_GUI
    NoesisShutdown(userData);
#endif

    glDeleteProgram(userData->vShader);
    glDeleteProgram(userData->fShader);
    glDeleteProgram(userData->programObject);

    eglMakeCurrent(userData->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    if (userData->context != EGL_NO_CONTEXT)
    {
        eglDestroyContext(userData->display, userData->context);
    }
    if (userData->surface != EGL_NO_SURFACE)
    {
        eglDestroySurface(userData->display, userData->surface);
    }

    eglTerminate(userData->display);

    userData->animating = false;
    userData->display = EGL_NO_DISPLAY;
    userData->context = EGL_NO_CONTEXT;
    userData->surface = EGL_NO_SURFACE;
}

////////////////////////////////////////////////////////////////////////////////////////////////
void KeyDown(UserData* userData, int keyCode)
{
#ifdef NOESIS_GUI
    // TODO: translate key code to NoesisGUI key enum
    Key key = Key_None;
    userData->xamlRenderer->KeyDown(key);
#endif
}

////////////////////////////////////////////////////////////////////////////////////////////////
void KeyUp(UserData* userData, int keyCode)
{
#ifdef NOESIS_GUI
    // TODO: translate key code to NoesisGUI key enum
    Key key = Key_None;
    userData->xamlRenderer->KeyUp(key);
#endif
}

////////////////////////////////////////////////////////////////////////////////////////////////
void MouseDown(UserData* userData, float x, float y)
{
#ifdef NOESIS_GUI
    userData->xamlRenderer->MouseButtonDown((int)x, (int)y, MouseButton_Left);
#endif
}

////////////////////////////////////////////////////////////////////////////////////////////////
void MouseUp(UserData* userData, float x, float y)
{
#ifdef NOESIS_GUI
    userData->xamlRenderer->MouseButtonUp((int)x, (int)y, MouseButton_Left);
#endif
}

////////////////////////////////////////////////////////////////////////////////////////////////
void MouseMove(UserData* userData, float x, float y)
{
#ifdef NOESIS_GUI
    userData->xamlRenderer->MouseMove((int)x, (int)y);
#endif
}

////////////////////////////////////////////////////////////////////////////////////////////////
int32_t HandleInput(android_app* app, AInputEvent* event)
{
    UserData* userData = (UserData*)app->userData;

    switch (AInputEvent_getType(event))
    {
        case AINPUT_EVENT_TYPE_KEY:
        {
            switch (AKeyEvent_getAction(event))
            {
                case AKEY_EVENT_ACTION_DOWN:
                {
                    KeyDown(userData, AKeyEvent_getKeyCode(event));
                    break;
                }
                case AKEY_EVENT_ACTION_UP:
                {
                    KeyUp(userData, AKeyEvent_getKeyCode(event));
                    break;
                }
            }
            break;
        }
        case AINPUT_EVENT_TYPE_MOTION:
        {
            size_t numPointers = AMotionEvent_getPointerCount(event);
            if (numPointers > 0)
            {
                int32_t action = AMotionEvent_getAction(event);

                switch (action & AMOTION_EVENT_ACTION_MASK)
                {
                    case AMOTION_EVENT_ACTION_DOWN:
                    case AMOTION_EVENT_ACTION_POINTER_DOWN:
                    {
                        float x = AMotionEvent_getX(event, 0);
                        float y = AMotionEvent_getY(event, 0) - app->contentRect.top;
                        MouseDown(userData, x, y);
                        break;
                    }
                    case AMOTION_EVENT_ACTION_UP:
                    case AMOTION_EVENT_ACTION_POINTER_UP:
                    {
                        float x = AMotionEvent_getX(event, 0);
                        float y = AMotionEvent_getY(event, 0) - app->contentRect.top;
                        MouseUp(userData, x, y);
                        break;
                    }
                    case AMOTION_EVENT_ACTION_MOVE:
                    {
                        float x = AMotionEvent_getX(event, 0);
                        float y = AMotionEvent_getY(event, 0) - app->contentRect.top;
                        MouseMove(userData, x, y);
                        break;
                    }
                }
            }
            break;
        }
    }

    return 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////
void HandleAppCmd(android_app* app, int32_t cmd)
{
    UserData* userData = (UserData*)app->userData;

    switch (cmd)
    {
        case APP_CMD_INIT_WINDOW:
        {
            // The window is being shown, get it ready
            if (userData->app->window)
            {
                InitDisplay(userData);
            }
            break;
        }
        case APP_CMD_CONTENT_RECT_CHANGED:
        case APP_CMD_WINDOW_RESIZED:
        {
            ResizeDisplay(userData);
            break;
        }
        case APP_CMD_WINDOW_REDRAW_NEEDED:
        {
            DrawFrame(userData);
            break;
        }
        case APP_CMD_TERM_WINDOW:
        {
            // The window is being hidden or closed, clean it up
            ShutdownDisplay(userData);
            break;
        }
        case APP_CMD_LOST_FOCUS:
        {
            // Stop animating
            userData->animating = false;
            DrawFrame(userData);
            break;
        }
        case APP_CMD_GAINED_FOCUS:
        {
            // Restart animating
            userData->animating = true;
            DrawFrame(userData);
            break;
        }
        case APP_CMD_CONFIG_CHANGED:
        {
            ResizeDisplay(userData);
            break;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////
void android_main(android_app* app)
{
    LOGI("android_main BEGIN");

    // Ensure app wrapper code is not discarded
    app_dummy();

    UserData userData;
    memset(&userData, 0, sizeof(UserData));
    app->userData = &userData;
    app->onAppCmd = HandleAppCmd;
    app->onInputEvent = HandleInput;
    userData.app = app;

    bool loop = true;
    while (loop)
    {
        // Read all pending events
        int events;
        android_poll_source* source;
        while (ALooper_pollAll(0, 0, &events, (void**)&source) >= 0)
        {
            // Process this event
            if (source)
            {
                source->process(app, source);
            }

            // Check if exiting occurs before window was created
            if (app->destroyRequested)
            {
                LOGI("App destroyed");
                loop = false;
                break;
            }
        }

        if (loop && userData.animating)
        {
            DrawFrame(&userData);
        }
    }

    LOGI("android_main END");

    exit(0);
}

Windows Phone 8.1

Follow the DirectX v11.0 guide for Windows Phone.

© 2017 Noesis Technologies