Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / OpenGL

OpenGL Rendering to a WPF Window

5.00/5 (5 votes)
25 Jun 2019CPOL3 min read 14.8K  
OpenGL rendering to WPF window

Introduction

When using OpenGL in a WPF window, there are some well known issues due to the pixel ownership of WPF.
A lot of people are using a WinformsHost to put the context into a WPF window. The problem is that you can't put any other control on top of the WinformsHost without using some ugly hacks.

I'm creating a small engine for a simulation of mechanical engineering equipment. I wanted to connect my C++ engine to a WPF C# context so that I could use all the nice MVVM patterns and UI from WPF but also have a native C++ engine with fast rendering.

I'm not saying that my solution is perfect, but it works for me because of the reasons above.

Used Libraries

  • Glad for OpenGL calls in C++ project
  • glfw for creation of OpenGL context

Background

If you are interested in OpenGL, I would recommend that you look into the following topics:

  • Programmable OpenGL pipeline beginning with version 3.3
  • WPF pixel ownership
  • OpenGL framebuffer, renderbuffer objects

Code

The first thing we have to do is initialize the glfw library to create a hidden window for the rendering. OpenGL can't draw without a window context. I will investigate if I can strip even more here, but right now this is how my Init() method looks like:

C++
void initWindow()
{
    if (!glfwInit())
        throw std::exception();

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // create invisible window
    glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);

    // create a window with glfw
    mWindow = glfwCreateWindow(640, 480, "Hidden OpenGL window", NULL, NULL);

    if (!mWindow)
    {
        glfwTerminate();
        throw std::exception();
    }

    //load glad calls for OpenGL version 3.3
    glfwMakeContextCurrent(mWindow);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        glfwTerminate();
        throw std::exception();
    }
}

After the creation of this hidden window and context, I'm setting up a basic shader program with a vertex and a fragment shader to actually draw a triangle. I won't show code here because you can find so many excellent tutorials about that on the internet.

Just an example:

C++
void initShaders(void)
{
  // create shader program and attach shaders
}

Afterwards, I create a simple render (draw) function for my vao (Vertex Array Object):

C++
void Render(char* imgBuffer)
{
    glClearColor(0.8f, 0.8f, 0.6f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glBindVertexArray(vao);
    {
        glUseProgram(prgId);
        glEnableVertexAttribArray(0);
        glDrawArrays(GL_TRIANGLES, 0, 3);
    }
    glBindVertexArray(0);

    glReadPixels(0, 0, m_width, m_height, GL_BGRA, GL_UNSIGNED_BYTE, imgBuffer);

    glfwSwapBuffers(mWindow);
    glfwPollEvents();
}

The interesting thing about the method is that it takes a char* as an argument, I'm using that to write the data of the pointer into a WriteableBitmap of a CLR project.

So on the CLR side, it looks like this. I created a UserControl and added an Image into the UC. After every resize of the UC, I call the function below. This sets the source of the Image to the WriteableBitmap.

C++
void createWriteableBitmap(const int h, const int w)
{
    // create a new instance of a WriteableBitmap
    m_writeableImg = gcnew WriteableBitmap(w, h, 96, 96, PixelFormats::Pbgra32, nullptr);
    
    // cast the IntPtr to a char* for the native C++ engine
    m_bufferPtr = (char*)m_writeableImg->BackBuffer.ToPointer();
    
    // update the source of the Image control
    m_ImageControl->Source = m_writeableImg;
}

The only thing you need to consider is that you can't update the WriteableBitmap from a Background thread. So in my case, I'm using a separate thread to render and when everything is done, I call Lock() on the UI thread (Dispatcher.Invoke), set a dirty area and Unlock the WriteableBitmap.

With these steps, you have a nice way to connect a C++ engine to a WPF UI without the above mentioned issues.

The complete solution can be found on Github:

Final Words

I hope you've enjoyed this a bit and maybe you were able to pick up a couple of useful bits of information on the way. Like I said, my solution is not intended to be perfect but to give an idea about how it can work.

You can contact me via CodeProject or Github.

Greetings form beautiful Austria,
Max

History

  • 24.06.2019 - Version 1.0

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)