wpfopenglopengl-esdirect3dd3dimage

Is it possible to use OpenGL ES code with a WPF application via a D3DImage and ANGLE?


Summary (TL:DR version)

Ultimately our goal is to be able to utilize OpenGL ES code in a WPF application natively (i.e. not SharpGL, etc.) and without Airspace or driver issues, possible using Google's ANGLE project.

Background:

One of the things I like about OpenGL over DirectX is its cross-platform capability. It has excellent support on both OS X and Linux, and also on Android and iOS via ES. However, on Windows, using it is marred with driver issues, or worse, a lot of cards simply don't implement it properly.

Enter Google's ANGLE project, or Almost-Native-Graphics-Layer-Engine.

ANGLE is an OpenGL ES 2.0 wrapper around a Direct3D implementation, meaning you can write OpenGL ES 2.0 code to be run on Windows without the need for actual OpenGL drivers. It not only passes the ES compatibility tests, but it's actually how Chrome does all of its graphics rendering, including WebGL so it definitely is a proven technology.

The Question:

We know WPF has a D3DImage control which lets you host Direct3D rendering within WPF and it supposedly does away with the airspace issues by properly composting its output to the WPF render thread. My question is since ANGLE is implemented via Direct3D, and D3DImage is a target for Direct3D rendering, is it possible to combine the two, allowing us to write OpenGL ES code and host it in a WPF application on Windows, all without driver or airspace issues?

This would be the 'Holy Grail' for us.

However, I keep hitting a wall around getting ANGLE to target its rendering on the D3D surface created by the D3DImage control since ANGLE wants to use its own. I'm not sure this is even possible. I can't find a single article or reference anywhere of anyone even discussing this, let alone attempting it.

Again to be clear though, the goal is to get our shared, cross-platform OpenGL (or ES) code to work in a WPF application without airspace issues or OpenGL driver requirements. My suggestion of using ANGLE/D3DImage is just that... an attempt. It's the 'means' I've come up with so far, but that's just a potential means to our goal, not the goal itself. Anything else that would get us to the same solution would be more than welcome.


Solution

  • I have uploaded a github project that demonstrates how to integrate OpenGL rendering into a WPF application via OpenTK.GLControl.

    The code is surprisingly simple:

    1. Add a WindowsFormsHost to the WPF application
    2. Construct an OpenTK.GLControl and attach it to the WindowsFormsHost
      • Pass GraphicsContextFlags.Default for a desktop OpenGL context
      • Pass GraphicsContextFlags.Embedded for an OpenGL ES (ANGLE) context
    3. Render using normal OpenGL or OpenGL ES commands

    Hint: OpenTK and OpenTK.GLControl are also available as NuGet packages. There is a new release due tomorrow with improved ANGLE support.

    Note that the WindowsFormsHost approach is subject to airspace restrictions. If this is an issue, see my answer here for a solution.

    // This code is public domain
    #define USE_ANGLE
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Forms;
    using System.Windows.Forms.Integration;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    using OpenTK;
    using OpenTK.Graphics;
    
    #if USE_ANGLE
    using OpenTK.Graphics.ES20;
    #else
    using OpenTK.Graphics.OpenGL;
    #endif
    
    namespace WPF.Angle
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            GLControl glControl;
    
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private void WindowsFormsHost_Initialized(object sender, EventArgs e)
            {
                var flags = GraphicsContextFlags.Default;
    #if USE_ANGLE
                flags = GraphicsContextFlags.Embedded;
    #endif
                glControl = new GLControl(new GraphicsMode(32, 24), 2, 0, flags);
                glControl.MakeCurrent();
                glControl.Paint += GLControl_Paint;
                glControl.Dock = DockStyle.Fill;
                (sender as WindowsFormsHost).Child = glControl;
            }
    
            private void GLControl_Paint(object sender, PaintEventArgs e)
            {
                GL.ClearColor(
                    (float)Red.Value,
                    (float)Green.Value,
                    (float)Blue.Value,
                    1);
                GL.Clear(
                    ClearBufferMask.ColorBufferBit |
                    ClearBufferMask.DepthBufferBit |
                    ClearBufferMask.StencilBufferBit);
    
                glControl.SwapBuffers();
            }
    
            private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
            {
                glControl.Invalidate();
            }
        }
    }