NoesisGUI Integration Tutorial
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:
- 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);
// ...
}
- 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:
- Tick the kernel to update all the systems.
// Tick kernel
Noesis::GUI::Tick();
- 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.
- 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.
- Add path to Additional Include Directories
- Add path to Additional Library Directories
- 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();

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;
}
/* ========================================================================= */

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());
}

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.