James Clarke

I work at Microsoft Corp in Redmond, WA as a PM Architect on the Azure engineering team. In my free time I enjoy learning new tools and technologies. All opinions expressed here are my own and do not represent my employer.

A common OpenGL renderer for UWP and Win32 using Angle and Win.UI.Composition

24 Feb 2020 » angle

For reasons that I’ll cover in a future post, I’ve been experimenting with LibAngle of late. Angle, otherwise known as Almost Native Graphics Layer, is a translation layer that emulates OpenGL calls on top of various graphics backends such as DirectX, Vulcan etc. From the repo description:

The goal of ANGLE is to allow users of multiple operating systems to seamlessly run WebGL and other OpenGL ES content by translating OpenGL ES API calls to one of the hardware-supported APIs available for that platform. ANGLE currently provides translation from OpenGL ES 2.0, 3.0 and 3.1 to Vulkan, desktop OpenGL, OpenGL ES, Direct3D 9, and Direct3D 11. Future plans include ES 3.2, translation to Metal and MacOS, Chrome OS, and Fuchsia support.

Microsoft is an official contributor to the project and back in 2018, I submitted a patch to add Windows.UI.Composition support as a type of “nativewindow” to enable Angle content to be hosted on Windows using a SpriteVisual instead of an HWND or CoreWindow.

Angle Patch

I was keen to submit this upstream as the Microsoft fork of Angle has got pretty stale at this point and is not actively maintained as far as I can tell.

Why?

The reasoning for this change is explained in more detail in the writeup I did supporing the patch but can be summarized as follows:

An increasing number of WinRT API’s work in both a Win32 full trust context as well as in AppContainer partial trust environments such as a UWP application. The benefit to developers is they can reuse the same code in either WIN32 or UWP contexts by leveraging SpriteVisual hosting as a converged approach. This will become more seemless over time as the team realizes the vision of unification of UWP and Win32 but can still be very beneficial in todays world.

By enabling Windows.UI.Composition.SpriteVisual to be used as a hosting mechanism, it is possible to build a renderer using OpenGL and have it work everywhere on Windows: a native win32 app (User32, Winforms, WPF), Win32 XAML islands and UWP.

Completing the work

Although my initial change worked, there was an issue with the mechanism used to detect if the feature is available on a particular OS version since it relied on the calling executable to be manifested. There was a simpler and more robust approach that I adopted in the updated version. There were also issues with the converged hosting codepath actually working in a UWP which was a bit of a woopse. Basically when compiled for Windows Store, the delayload mechanism I used to ensure that Angle worked downlevel on Win7 was not needed.. I could simply link in windowsapp.lib.

Here is the branch with the final changes https://github.com/clarkezone/angle/tree/UniversalCompositorNativeWindow

and the diff to make things work:

https://github.com/clarkezone/angle/commit/4c8370f69f6c5f4b9b6747a05929e2b7e8c7a407

The Test Project

To prove that we could indeed have a converged rendering implementation that could be deployed to / shared between a number of environments, I build the following test project:

https://github.com/clarkezone/anglehosting

converged hosting

The hosting code looks like this which is almost identical apart from the type and creation mechanism of the window targets:

    // Win32..
    Compositor compositor;
    m_target = CreateDesktopWindowTarget(compositor, m_window); //m_window is the hwnd

    auto root = compositor.CreateSpriteVisual();
    root.Brush(compositor.CreateColorBrush({ 0xFF, 0xEF, 0xE4 , 0xB0 }));
    m_target.Root(root);
	m_visuals = root.Children();

    AddAngleRenderer(m_visuals, 100.0f, 100.0f);

    // UWP..
    Compositor compositor;
    m_target = compositor.CreateTargetForCurrentView();

    ContainerVisual root = compositor.CreateContainerVisual();
    m_target.Root(root);
    m_visuals = root.Children();

	AddAngleRenderer({ 10.0f, 10.0f });

and in both cases the content is applied in the same way using the converged AngleSpriteRenderer implementation that is linked in from the AngleRender lib:

    AngleSpriteRenderer m_render;

    void AddAngleRenderer(VisualCollection const& visuals, float x, float y)
    {
        Compositor compositor = m_visuals.Compositor();
        SpriteVisual visual = compositor.CreateSpriteVisual();

        visual.Size(
            {
                600.0f,
                600.0f
            });

        m_visuals.InsertAtTop(visual);
        m_render.Start(visual);
    }

Here are some examples of this running in a few different contexts: on Desktop (Win32), XBOX (UWP), X10 (UWP) and Desktop in a XAML island (Win32).

Win32 Desktop no XAML

DesktopWin32

UWP XBOX

UWP Windows 10X

Running natively as a UWP rather than in the Win32 container:

angie in 10X emulator

Win32 Desktop Xaml Islands

angie in islands

All that is remaining is to clean up and submit an updated patch to Angle upstream. This will occur once I have completed the project that depends on this of which more later. If you have any feedback please let me know.