Please enable JavaScript to display the menu.
Compiling Universal Windows Platform + OpenGL ES 2.0 Program with CMake

ysflight.com

03/17/2017 CMake 3.8rc1 has added support for .targets file as library.  You can simply use target_link_libraries to specify a .targets file, which comes in a NuGet package and acts just like a library.  By the way, my code was used for adding that functionality  :-)

[Sample Code on this page can be downloaded from here.]

Cross-platform development is all about writing a program in a long term.

You may not be interested in such a thing.  You may be ok with writing a short program, throw away, write another one, throw away, and repeat making small programs.  If that's your passion, there's nothing wrong with it.  If you are ok with your program having shorter life than the platform, there is no point making it cross-platform and portable.

That's just not my preference.  For me, I was not educated that way.  I was taught that programming was like building a pyramid.  I first need to write a foundation library.  Then write higher-level libraries.  And then finally build an application on top of those libraries.  The pyramid can be refined and polished up over time, and the program becomes robust and reliable.

I have been maintaining YS Fligh Simulator since 1999.  Writing a flight simulator is one of my dreams.  In fact, so many people over the world are using YS Flight Simulator.  That is a big dream came true.  It is such a fun project, and I have no intention of stopping it, but one of the challenges is ever changing computing platforms.  Once it was ok if I support Windows.  It was a Microsoft world.  But, it is not any more.  Windows is only one of them.

And, Microsoft is trying to promote the new set of API called Universal Windows Platform.  Days of Win32 are numbered.  It won't be the next year, but it is unknown how long my Win32 code can be compiled.  Right now, YSFLIGHT and my other programs (programs for my research work, too) runs on Mac OSX, Linux, and Windows.  But, unless I am able to support UWP, it might become just MacOSX and Linux.

But, one of the biggest problem was graphics.  Now it is a big problem that graphics has too much influence on the software design.  Even though YSFLIGHT is not using the state-of-the-art graphics, long lines of code are for rendering.  It runs on DirectX9 and OpenGL.  But, UWP initially supported only DirectX11, which is not backward compatible with DirectX9.

Microsoft had been taking full advantage of Windows dominance forcing developers to use proprietary APIs making it difficult to port programs to other platforms.  DirectX was one of the tools to lock developers in the Microsoft environment.  It is too a daunting task to port all shaders written for OpenGL to DirectX11.  I am determined to keep my program portable and cross-platform, but I had to cry "Oh no!  Why do you do this!?" when Microsoft introduced something new.  By the way, Apple, too.

So, I was skeptical when Microsoft started talking about cross-platform development in the Build conference.  I didn't believe it until Microsoft released OpenGL ES 2.0 library.  I was flipping through the news site, and saw Microsoft saying something in the Build conference, was just thinking Microsoft saying something to lure unprepared developers, then saw OpenGL ES 2.0 for Universal Windows Platform.  Seriously?  Is it real?  How nice! My polygon editor, Polygon Crest, runs within specification of OpenGL ES 2.0.  YSFLIGHT mostly.  It opened up the possibility of porting my programs to the UWP.

Microsoft is moving to the right direction.  By losing its dominance, proprietary APIs only repelled developers.  Now they need to invite developers back to the Microsoft environment.  Supporting an open standard is exactly the right thing for that purpose.  By the way, some students taking my class thought I am a open-source person, but I am more a open-standard person.  I believe programmers should be allowed to make money.  It takes time, effort, youth, hair, and social life, to gain programming skills.  I am strongly opposing the idea of GPL.  But, the vendors who supply operating systems must support standard APIs.  Many industroies very well adapted the standards.  For some reason, software industries are slow in doing so.

So, if Microsoft supports OpenGL ES, I like it.  I'm on board.

Another problem is my build environment.  I am entirely managing my build environment with CMake.  Only iOS I am making .xcodeproj directly (and that's why my adaptation to iOS is slow.)  I cannot afford setting up another set of build environemt for UWP.  I tried how much I can do with CMake.

CMake added UWP support around version 3.0.  You can use CMake as:

 CMake <source-directory> -G "Visual Studio 14 2015" -DCMAKE_SYSTEM_NAME=WindowsStore "-DCMAKE_SYSTEM_VERSION=10.0"

to generate Visual Studio project for UWP.  In CMake version 3.8rc1 you can specify a .targets file in target_link_libraries.  With this addition, you can download a NuGet package and link OpenGL ES library all in the CMake scripts.

What's NuGet?

First, I created a OpenGL ES 2.0 cross-platform project from the template.  Yes!  OpenGL ES 2.0 is working!  But, it turned out that OpenGL ES 2.0 libraries and headers are not installed in the common directory of Visual Studio.  It needs to be downloaded per project by using a program called NuGet.  The template projcet downloads the newest version of the library called "ANGLE.WindowsStore" at the beginning of build.

I want to download this package within CMake.  I think it is good enough to download the package every time I run CMake.  It doesn't have to be checked every build.  However, CMake doesn't seem to have a command that describes the dependency to the NuGet packages.  Looks like I need to write a command more directly in CMakeLists.txt.

Another strange thing I noticed was that the command nuget.exe is not included anywhere in the Visual Studio installation.  Nuget.exe needed to be downloaded from https://nuget.org.  I have never heard about NuGet before.  It was not so comfortable to download an .exe file from a domain I've never heard of.  I wish Microsoft included nuget.exe in the standard Visual Studio installation, or made downloadable from microsoft.com domain.

After downloading nuget.exe, the file must be copied manually to the location you set PATH.

Calling NuGet from CMakeLists.txt

Then you can write a CMakeLists.txt file so that it downloads OpenGL ES 2.0 libraries on CMake.  Visual Studio template downloads and updates it before every build, but I think if you go with CMake, downloading once every generation is good enough.

You can write the following in the top-level CMakeLists.txt to do so.

[First attempt, what's so ever]

# OpenGL ES 2.0 for Windows required for compiling for Universal Windows Platform.
# It's great!  Finally Microsoft officially supports OpenGL ES 2.0!
# I'd be happier if full-scale OpenGL 2.0 is supported, but ES 2.0 is good!
if("${CMAKE_SYSTEM_NAME}" STREQUAL "WindowsStore")
    find_program(NUGET_EXE nuget)
    if(NOT NUGET_EXE)
        message("NUGET.EXE not found.")
        message("Unfortunatelly as of 09/13/2016, you cannot install NUGET.EXE as a globally-available command")
        message("from Visual Studio 2015, or from Add/Remove Programs.  I spent about 30 minutes to search for")
        message("the way, but didn't find one.")
        message("You can download NUGET.EXE from:")
        message("    https://dist.nuget.org/index.html")
        message("and copy it to a directory where PATH is pointing.")
        message(FATAL_ERROR "Please install this executable, and run CMake again.")
    endif()
    message("NUGET.EXE found at ${NUGET_EXE}")

    exec_program(${NUGET_EXE}
        ARGS install "ANGLE.WindowsStore" -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/nuget_packages)
    find_path(UWP_ANGLE_GLES_TARGETS_DIR ANGLE.WindowsStore.targets HINTS ${CMAKE_BINARY_DIR}/nuget_packages/ANGLE.WindowsStore/build/native)
    if(NOT UWP_ANGLE_GLES_TARGETS_DIR)
        message(FATAL_ERROR "OpenGL ES library not found in the package.")
    endif()

    set(UWP_ANGLE_GLES_TARGETS ${UWP_ANGLE_GLES_TARGETS_DIR}/ANGLE.WindowsStore.targets)
endif()

It needs to be done only when CMAKE_SYSTEM_NAME is "WindowsStore".  It first checks if nuget.exe exists.  If not, it gives an error and asks the user to install nuget.exe.  By default, nuget.exe adds version number in the directory name, but there is no way for CMake to know what version package was downloaded exactly.  Therefore, the version number must be omitted by adding -ExcludeVersion option. 

Then this script checks if the NuGet package is downloaded and the .targets file is in the predicted location.  When .targets file is found, it stores the full-path file name in variable UWP_ANGLE_GLES_TARGETS.  The OpenGL ES library can be linked by adding the description like:

target_link_libraries(ysgl ${UWP_ANGLE_GLES_TARGETS})

With this, the target called "ysgl" links the OpenGL ES library.  .targets file includes the information of the header path.  Therefore, you don't have to write separate target_include_directories.  Also CMake propagates the information to the depending targets.  Therefore, the CMake targets that links this library automatically can use OpenGL ES library as well.

Building a Minimum UWP App

Before using this OpenGL ES library for UWP, I need to be able to build a UWP app from CMake.  To do so, I first tried to make a very minimum application.  It doesn't have to run.  I just wanted to build a program as an application.

Looking at the OpenGL ES 2.0 Cross-Platform template, the very minimum app can be build with as little as three files.  CMakeLists.txt, main.cpp, and main.h.  Main.h turned out unnecessary, so actually you only need two files.

CMakeLists.txt

if("${CMAKE_SYSTEM_NAME}" STREQUAL "WindowsStore")
    add_definitions(-DYS_IS_UNIVERSAL_WINDOWS_APP)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /ZW")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /ZW")
endif()

add_executable(UWP_00_MinimumApp main.cpp main.h)

main.cpp

#include "main.h"

using namespace Windows::ApplicationModel::Core;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::UI::Core;
using namespace Windows::UI::Input;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Graphics::Display;

[Platform::MTAThread]
int main(Platform::Array<Platform::String^>^)
{
    return 0;
}
main.h
#ifndef MAIN_IS_INCLUDED
#define MAIN_IS_INCLUDED

#endif

This example is only for UWP, but in the future, if you isolate UWP-specific features and make it a cross-platform project, you can check whether it is for UWP by checking CMAKE_SYSTEM_NAME inside your CMakeLists.txt.  Compiler option /ZW enables Microsoft's proprietary addition to the C++.  Now I needed to cry "Oh no!  Why do you do that!"  Of course it is bad.  Microsoft has a world-class research facility.  There's no doubt about it.  But, they shouldn't be too arrogant to think they can beat Professor Bjarne Stroustrup.  C++ is by itself very well designed programming language.  So, the point is how to isolate and minimize code that actually uses this proprietary extension.  But, which is very well doable.  Much simpler to do than mixing C, C++, and Objective-C.

http://stackoverflow.com/questions/16698949/how-to-access-namespace-windows

And, I can understand their point, too.  We often want to have a handle rather than a pointer.  I often want to know if the pointer is valid or already deleted, but only way to check is to see if the program crashes or not.  If we are able to use a handle rather than a pointer, and if there is a way to check the validity of a handle without crashing the program, that's nicer.  But, at this point, if the handle added in this C++/CX is capable of doing it, or if a handle is just a reference-counted pointer.  Anyway, it is good to stay away as much as possible.  So far, I seem to be able to compile most of my libraries within C++11 standard with this /ZW option.  Looks like this option does not do any harm.

If you compile and run, you will see an error message saying something like the application was not activated.  That's ok.  What's important is I now have a skeleton of a UWP app.

Opening a window

Next step is to make it run without an error until the user closes the window.

CMakeLists.txt

if("${CMAKE_SYSTEM_NAME}" STREQUAL "WindowsStore")
    add_definitions(-DYS_IS_UNIVERSAL_WINDOWS_APP)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /ZW")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /ZW")
endif()

add_executable(UWP_01_Window main.cpp main.h)

main.cpp

#include <stdio.h>
#include <time.h>

#include "main.h"

using namespace Windows::ApplicationModel::Core;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::UI::Core;
using namespace Windows::UI::Input;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Graphics::Display;

ref class SingleWindowAppView sealed : public Windows::ApplicationModel::Core::IFrameworkView
{
public:
    typedef SingleWindowAppView THISCLASS;

private:  // Cannot have a public member variable?
    bool isWindowClosed,isWindowVisible;

public:
    SingleWindowAppView();

    virtual void Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView);
    virtual void SetWindow(Windows::UI::Core::CoreWindow^ window);
    virtual void Load(Platform::String^ entryPoint);
    virtual void Run();
    virtual void Uninitialize();

    virtual void OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args);
    virtual void OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args);
    virtual void OnWindowClosed(CoreWindow^ sender, CoreWindowEventArgs^ args);
};

SingleWindowAppView::SingleWindowAppView()
{
    isWindowClosed=false;
    isWindowVisible=false;
}

void SingleWindowAppView::Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView)
{
    applicationView->Activated+=
        ref new TypedEventHandler<CoreApplicationView^, IActivatedEventArgs^>(this,&THISCLASS::OnActivated);
}
void SingleWindowAppView::SetWindow(Windows::UI::Core::CoreWindow^ window)
{
    window->VisibilityChanged+=
        ref new TypedEventHandler<CoreWindow^,VisibilityChangedEventArgs^>(this,&THISCLASS::OnVisibilityChanged);
    window->Closed+=
        ref new TypedEventHandler<CoreWindow^,CoreWindowEventArgs^>(this,&THISCLASS::OnWindowClosed);
}
void SingleWindowAppView::Load(Platform::String^ entryPoint)
{
}
void SingleWindowAppView::Run()
{
    while(!isWindowClosed)
    {
        if (isWindowVisible)
        {
            // This doesn't seem to wait for an event.  Like a PeekMessage
            CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
        }
        else
        {
            // This seems to wait until an event is thrown.
            CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
        }
    }
}
void SingleWindowAppView::Uninitialize()
{
}


void SingleWindowAppView::OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args)
{
    CoreWindow::GetForCurrentThread()->Activate();
}
void SingleWindowAppView::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args)
{
    isWindowVisible=args->Visible;
}
void SingleWindowAppView::OnWindowClosed(CoreWindow^ sender, CoreWindowEventArgs^ args)
{
    isWindowClosed=true;
}


ref class SingleWindowAppSource sealed : Windows::ApplicationModel::Core::IFrameworkViewSource
{
public:
    virtual Windows::ApplicationModel::Core::IFrameworkView^ CreateView()
    {
        return ref new SingleWindowAppView();
    }
};


[Platform::MTAThread]
int main(Platform::Array<Platform::String^>^)
{
    auto singleWindowAppSource = ref new SingleWindowAppSource();
    CoreApplication::Run(singleWindowAppSource);
    return 0;
}
main.h
#ifndef MAIN_IS_INCLUDED
#define MAIN_IS_INCLUDED

#endif

Among the functions, the following five functions are required.  These functions are declared as a pure virtual function, and you must implement them to instantiate your view class.

And, OpenGL ES template handles events differently depending on if the window is visible or not.  To check if the window is visible or not, the view class must implement OnVisibilityChanged function, connected to the VisibilityChanged event inside SetWindow   It is connected to the view as an event handler, but not a virtual function.  So, I suppose you can give whatever name you want to call.

Enabling OpenGL ES

What I learned from the minimum UWP application is I only need three files, CMakeLists.txt, main.cpp, and main.h to build a working UWP app. In fact, main.h was unnecessary.  You can go down to two files.  All I need to do is to enable OpenGL ES.  But, to write an example, I need to write GLSL programs, compile and link the programs from C++, and do all sorts of things.  Which is not true.  There is one function that let you draw without writing a single line of GLSL program.  That is, glClear!  You can at least change the color of the background with glClearColor.  No single line of GLSL program needed, and that's good enough for checking if the OpenGL ES context is up and running.

If you set UWP_ANGLE_GLES_TARGETS in the top-level CMakeLists.txt, all you need to do is link ${UWP_ANGLE_GLES_TARGETS} from your executable project.

If you Cmake, compile, and run the following project, you will see the background painted in blue.  You can confirm that the OpenGL ES context is created and running.

CMakeLists.txt

cmake_minimum_required(VERSION 3.8)

add_definitions(-DYS_IS_UNIVERSAL_WINDOWS_APP)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /ZW")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /ZW")

# OpenGL ES 2.0 for Windows required for compiling for Universal Windows Platform.
# It's great!  Finally Microsoft officially supports OpenGL ES 2.0!
# I'd be happier if full-scale OpenGL 2.0 is supported, but ES 2.0 is good!
if("${CMAKE_SYSTEM_NAME}" STREQUAL "WindowsStore")
    find_program(NUGET_EXE nuget)
    if(NOT NUGET_EXE)
        message("NUGET.EXE not found.")
        message("Unfortunatelly as of 09/13/2016, you cannot install NUGET.EXE as a globally-available command")
        message("from Visual Studio 2015, or from Add/Remove Programs.  I spent about 30 minutes to search for")
        message("the way, but didn't find one.")
        message("You can download NUGET.EXE from:")
        message("    https://dist.nuget.org/index.html")
        message("and copy it to a directory where PATH is pointing.")
        message(FATAL_ERROR "Please install this executable, and run CMake again.")
    endif()
    message("NUGET.EXE found at ${NUGET_EXE}")

    exec_program(${NUGET_EXE}
        ARGS install "ANGLE.WindowsStore" -ExcludeVersion -OutputDirectory ${CMAKE_BINARY_DIR}/nuget_packages)
    find_path(UWP_ANGLE_GLES_TARGETS_DIR ANGLE.WindowsStore.targets HINTS ${CMAKE_BINARY_DIR}/nuget_packages/ANGLE.WindowsStore/build/native)
    if(NOT UWP_ANGLE_GLES_TARGETS_DIR)
        message(FATAL_ERROR "OpenGL ES library not found in the package.")
    endif()

    set(UWP_ANGLE_GLES_TARGETS ${UWP_ANGLE_GLES_TARGETS_DIR}/ANGLE.WindowsStore.targets)
endif()

add_executable(UWP_02_glClear main.cpp main.h)
target_link_libraries(UWP_02_glClear ${UWP_ANGLE_GLES_TARGETS})

main.cpp

#include <stdio.h>
#include <time.h>
#include <vector>

#include <agile.h>

#if !defined GL_GLEXT_PROTOTYPES
    #define GL_GLEXT_PROTOTYPES
#endif

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#define YS_GL_ES2
typedef double GLdouble;

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>
#include <angle_windowsstore.h>


#include "main.h"

using namespace Windows::ApplicationModel::Core;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::UI::Core;
using namespace Windows::UI::Input;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Graphics::Display;
using namespace Windows::System;

using namespace Platform;



ref class SingleWindowAppView sealed : public Windows::ApplicationModel::Core::IFrameworkView
{
public:
    typedef SingleWindowAppView THISCLASS;

private:  // Cannot have a public member variable?
    bool isWindowClosed,isWindowVisible;
    Platform::Agile<CoreWindow> hWnd;

    static THISCLASS ^CurrentView;

    EGLDisplay mEglDisplay;
    EGLContext mEglContext;
    EGLSurface mEglSurface;

public:
    static THISCLASS ^GetCurrentView(void);

    SingleWindowAppView();

    virtual void Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView);
    virtual void SetWindow(Windows::UI::Core::CoreWindow^ window);
    virtual void Load(Platform::String^ entryPoint);
    virtual void Run();
    virtual void Uninitialize();

    virtual void OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args);
    virtual void OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args);
    virtual void OnWindowClosed(CoreWindow^ sender, CoreWindowEventArgs^ args);

private:
    void InitializeOpenGLES2(void);
    EGLBoolean InitializeEGLDisplayDefault(void);
    EGLBoolean InitializeEGLDisplay(int level);
    std::vector <EGLint> MakeDisplayAttribute(int level) const;
    void SetUpConfigAttrib(EGLint configAttrib[],int depthBit);
    void CleanUpOpenGLES2(void);
};

/* static */ SingleWindowAppView ^SingleWindowAppView::CurrentView;

/* static */ SingleWindowAppView ^SingleWindowAppView::GetCurrentView(void)
{
    return CurrentView;
}

SingleWindowAppView::SingleWindowAppView()
{
    isWindowClosed=false;
    isWindowVisible=false;

    mEglDisplay=EGL_NO_DISPLAY;
    mEglContext=EGL_NO_CONTEXT;
    mEglSurface=EGL_NO_SURFACE;

    CurrentView=this;
}

void SingleWindowAppView::Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView)
{
    applicationView->Activated+=
        ref new TypedEventHandler<CoreApplicationView^, IActivatedEventArgs^>(this,&THISCLASS::OnActivated);
}
void SingleWindowAppView::SetWindow(Windows::UI::Core::CoreWindow^ window)
{
    hWnd=window;

    window->VisibilityChanged+=
        ref new TypedEventHandler<CoreWindow^,VisibilityChangedEventArgs^>(this,&THISCLASS::OnVisibilityChanged);
    window->Closed+=
        ref new TypedEventHandler<CoreWindow^,CoreWindowEventArgs^>(this,&THISCLASS::OnWindowClosed);

    InitializeOpenGLES2(); // Example initializes OpenGL ES context in here.
}
void SingleWindowAppView::Load(Platform::String^ entryPoint)
{
    // Example creates renderers here.
}
void SingleWindowAppView::Run()
{
    while(!isWindowClosed)
    {
        if (isWindowVisible)
        {
            // This doesn't seem to wait for an event.  Like a PeekMessage
            CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

            glClearColor(0,0,1,0);
            glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

            if (eglSwapBuffers(mEglDisplay, mEglSurface) != GL_TRUE)
            {
                CleanUpOpenGLES2();
                InitializeOpenGLES2();
                // Example is cleaning and then creating renderers here.
            }
        }
        else
        {
            // This seems to wait until an event is thrown.
            CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
        }
    }

    CleanUpOpenGLES2(); // Example deletes OpenGL context here.
}
void SingleWindowAppView::Uninitialize()
{
}

void SingleWindowAppView::OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args)
{
    CoreWindow::GetForCurrentThread()->Activate();
}
void SingleWindowAppView::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args)
{
    isWindowVisible=args->Visible;
}
void SingleWindowAppView::OnWindowClosed(CoreWindow^ sender, CoreWindowEventArgs^ args)
{
    isWindowClosed=true;
}

void SingleWindowAppView::InitializeOpenGLES2(void)
{
    // Cut & Pasted from OpenGL ES2 example.
    // Modified so that it selects the maximum depth bits.
    EGLint configAttributes[]=
    {
        EGL_RED_SIZE,8,
        EGL_GREEN_SIZE,8,
        EGL_BLUE_SIZE,8,
        EGL_ALPHA_SIZE,8,
        EGL_DEPTH_SIZE,24,
        EGL_STENCIL_SIZE,8,
        EGL_NONE
    };

    const EGLint contextAttributes[]=
    {
        EGL_CONTEXT_CLIENT_VERSION,2,
        EGL_NONE
    };

    const EGLint surfaceAttributes[]=
    {
        EGL_ANGLE_SURFACE_RENDER_TO_BACK_BUFFER,EGL_TRUE,
        EGL_NONE
    };

    EGLConfig config = NULL;

    // According to the example, the program should try to initialize OpenGL ES context with
    // EGL to D3D11 Feature Level 10_0+ first.  If it fails, try EGL to D3D11 Feature Level 9_3.
    // If it fails, try EGL to D3D11 Feature Level 11_0 on WARP.
    if(InitializeEGLDisplayDefault()!=EGL_TRUE &&
       InitializeEGLDisplay(0)!=EGL_TRUE &&
       InitializeEGLDisplay(1)!=EGL_TRUE &&
       InitializeEGLDisplay(2)!=EGL_TRUE)
    {
        throw Exception::CreateException(E_FAIL, L"Failed to initialize EGL");
    }

    // The sample code given with the OpenGL ES template uses 8-bit Z-buffer, 
    // which is pretty much useless.
    // Ideallly want to have 64-bit (or as precise as coordinates, in many cases 32-bit), 
    // but here get whatever available.
    EGLint numConfigs = 0;
    for(int depthBit=32; 8<=depthBit; depthBit-=8)
    {
        SetUpConfigAttrib(configAttributes,depthBit);
        if(eglChooseConfig(mEglDisplay,configAttributes,&config,1,&numConfigs)==EGL_TRUE && 0!=numConfigs)
        {
            printf("%d Depth Bits\n",depthBit);
            break;
        }
    }
    if(0==numConfigs)
    {
        throw Exception::CreateException(E_FAIL,L"Failed to choose first EGLConfig");
    }

    PropertySet^ surfaceCreationProperties = ref new PropertySet();
    surfaceCreationProperties->Insert(ref new String(EGLNativeWindowTypeProperty), hWnd.Get());

    mEglSurface=eglCreateWindowSurface(mEglDisplay,config,reinterpret_cast<IInspectable*>(surfaceCreationProperties),surfaceAttributes);
    if(mEglSurface==EGL_NO_SURFACE)
    {
        throw Exception::CreateException(E_FAIL,L"Failed to create EGL fullscreen surface");
    }

    mEglContext=eglCreateContext(mEglDisplay,config,EGL_NO_CONTEXT,contextAttributes);
    if(mEglContext==EGL_NO_CONTEXT)
    {
        throw Exception::CreateException(E_FAIL,L"Failed to create EGL context");
    }

    if(eglMakeCurrent(mEglDisplay,mEglSurface,mEglSurface,mEglContext)==EGL_FALSE)
    {
        throw Exception::CreateException(E_FAIL,L"Failed to make fullscreen EGLSurface current");
    }
}

EGLBoolean SingleWindowAppView::InitializeEGLDisplayDefault(void)
{
    // The sample code tells that eglGetPlatformDisplayEXT is use in place for eglGetDisplay to pass display attributes.
    // But, why not trying eglGetDisplay first?
    mEglDisplay=eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if(EGL_NO_DISPLAY!=mEglDisplay)
    {
        auto b=eglInitialize(mEglDisplay,NULL,NULL);
        printf("Looks like the default eglGetDisplay is good enough.  Isn't it?");
        return b;
    }
    return EGL_FALSE;
}

EGLBoolean SingleWindowAppView::InitializeEGLDisplay(int level)
{
    PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT=
        reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(eglGetProcAddress("eglGetPlatformDisplayEXT"));
    if (!eglGetPlatformDisplayEXT)
    {
        throw Exception::CreateException(E_FAIL, L"Failed to get function eglGetPlatformDisplayEXT");
    }

    auto attrib=MakeDisplayAttribute(level);

    mEglDisplay=eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,EGL_DEFAULT_DISPLAY,attrib.data());
    if(mEglDisplay!=EGL_NO_DISPLAY)
    {
        return eglInitialize(mEglDisplay,NULL,NULL);
    }
    return EGL_FALSE;
}

std::vector <EGLint> SingleWindowAppView::MakeDisplayAttribute(int level) const
{
    const EGLint commonDisplayAttributePre[]=
    {
        EGL_PLATFORM_ANGLE_TYPE_ANGLE,EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
    };
    const EGLint commonDisplayAttributePost[]=
    {
        EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER,EGL_TRUE,
        EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE,EGL_TRUE,
        EGL_NONE,
    };

    std::vector <EGLint> attrib;
    for(auto v : commonDisplayAttributePre)
    {
        attrib.push_back(v);
    }
    switch(level)
    {
    case 0:
        break;
    case 1:  // FL9.  I don't know exactly what FL9 stands for, but as example says.
        attrib.push_back(EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE);
        attrib.push_back(9);
        attrib.push_back(EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE);
        attrib.push_back(3);
        break;
    case 2:  // warp.  I don't know exactly what it means by "warp", but as example says.
        attrib.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE);
        attrib.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE);
        break;
    }
    for(auto v : commonDisplayAttributePost)
    {
        attrib.push_back(v);
    }
    return attrib;
}

void SingleWindowAppView::SetUpConfigAttrib(EGLint configAttrib[],int depthBit)
{
    for(int i=0; configAttrib[i]!=EGL_NONE; i+=2)
    {
        if(EGL_DEPTH_SIZE==configAttrib[i])
        {
            configAttrib[i+1]=depthBit;
        }
    }
}

void SingleWindowAppView::CleanUpOpenGLES2(void)
{
    // Cut & Pasted from OpenGL ES2 Example.
    if(mEglDisplay!=EGL_NO_DISPLAY && mEglSurface!=EGL_NO_SURFACE)
    {
        eglDestroySurface(mEglDisplay,mEglSurface);
        mEglSurface=EGL_NO_SURFACE;
    }
    if(mEglDisplay!=EGL_NO_DISPLAY && mEglContext!=EGL_NO_CONTEXT)
    {
        eglDestroyContext(mEglDisplay,mEglContext);
        mEglContext=EGL_NO_CONTEXT;
    }
    if(mEglDisplay!=EGL_NO_DISPLAY)
    {
        eglTerminate(mEglDisplay);
        mEglDisplay=EGL_NO_DISPLAY;
    }
}

ref class SingleWindowAppSource sealed : Windows::ApplicationModel::Core::IFrameworkViewSource
{
public:
    virtual Windows::ApplicationModel::Core::IFrameworkView^ CreateView()
    {
        return ref new SingleWindowAppView();
    }
};

[Platform::MTAThread]
int main(Platform::Array<Platform::String^>^)
{
    auto singleWindowAppSource = ref new SingleWindowAppSource();
    CoreApplication::Run(singleWindowAppSource);
    return 0;
}

main.h (It was actually unnecessary.)

#ifndef MAIN_IS_INCLUDED
#define MAIN_IS_INCLUDED

#endif

Cope with CMake 3.6.2. bug

The above CMakeLists.txt is good if it is the top-level.  I should say if your application project is in the top-level CMakeLists.txt.  But, practically, you have a bunch of library projects, and application projects.  Therefore your application is in one of the sub-directories.  Unfortunately, CMake 3.6.2 comes with a bug, and if you put your application project in one of the sub-directories, you will see an error saying "resources.pri not found" whey you try to run your app.

The cause of the error is a line in .vcxproj file generated by CMake:

<ProjectPriFullPath>$(TargetDir)resources.pri</ProjectPriFullPath>

This tag uses a variable $(TargetDir), which does not exist.  Therefore it is empty, and Visual Studio considers the file to be in the current working directory.  If your app project happens to be at the top level, this file happens to be created in the current working directory.  Otherwise, you have no luck.

Correct description seems to be:

<ProjectPriFullPath>$(ProjectDir)$(IntDir)resources.pri</ProjectPriFullPath>

The CMake developers seem to be aware of this problem, and hopefully the issue is fixed in the next release.  But, until then, all you can do is modify .vcxproj files by yourself.  It can be done by an easy Python script:

correctCMakeBug.py

import os
import sys
import subprocess


def CorrectVCXProj(vcxprojFn):
    fp=open(vcxprojFn,"r",encoding="utf-8")
    fileContent=[]
    changed=False
    for s in fp:
        s=s.replace("\n","")
        if "<PROJECTPRIFULLPATH>$(TARGETDIR)RESOURCES.PRI</PROJECTPRIFULLPATH>" in s.upper():
            s="<ProjectPriFullPath>$(ProjectDir)$(IntDir)resources.pri</ProjectPriFullPath>"
            changed=True
        fileContent.append(s)
    fp.close()

    if(True==changed):
        fp=open(vcxprojFn,"w",encoding="utf-8")
        for s in fileContent:
            fp.write(s+"\n")
        fp.close()
        print("Corrected")


def CorrectCMake3_6_3Bug(buildDir):
    files=os.listdir(buildDir)
    for f in files:
        ful=os.path.join(buildDir,f)
        if os.path.isfile(ful):
            (main,ext)=os.path.splitext(f)
            if ext.upper()==".VCXPROJ":
                print("    Found: "+ful)
                CorrectVCXProj(ful)
        elif os.path.isdir(ful):
            if f.startswith("."):
                print("    Skipping "+ful)
            else:
                CorrectCMake3_6_3Bug(os.path.join(buildDir,f))


CorrectCMake3_6_3Bug(".")

Run this Python script from the build directory.  It looks for the wrong <ProjectPriFullPath> tag and replace with a correct description.

Bouncing Ball

Well, changing the background color is not so fun.  I add one more project called bouncing ball.  This is a sample code that I use in my programming course, ported for the UWP.  Make sure to set up build files by the command shown above.  Then, run the above Python script to correct .vcxproj errors.  Then you can compile and run the app.

 

What's next?

With above examples, you can work on a project for UWP.  Being able to manage projects with CMake, instead of proprietary .sln and .vcxproj is good.  But, it is no much different than working directly on Visual Studio unless you make it a portable and cross-platform project.

My goal is to port YSFLIGHT, Polygon Crest, and Scenery Editor to UWP.  I need to port FsLazyWindow framework in my Polygon Crest code (available from here.) to isolate the UWP-specific code from portable code.

But, above sample code can be the starting point of your cross-platform project.  If you are trying to do something similar, I hope the sample code helps you.

By the way, if you need to render to the texture, use Win2D.uwp library.  If you Google for keywords like "render" "text" "uwp", you will be directed to a class called TextBlock, which is unusable in IFrameworkView class.  You need Xaml support, which is good for some types of applications, but it makes your program UWP specific, and certainly not good for cross-platform projects.  It took almost 48 hours to figure it out.

Win2D is clean and independent and very well designed.

Comments are welcome.  Send E-Mail to: 

Back to http://www.ysflight.com