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:
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);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
mWindow = glfwCreateWindow(640, 480, "Hidden OpenGL window", NULL, NULL);
if (!mWindow)
{
glfwTerminate();
throw std::exception();
}
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:
void initShaders(void)
{
}
Afterwards, I create a simple render
(draw) function for my vao (Vertex Array Object):
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
.
void createWriteableBitmap(const int h, const int w)
{
m_writeableImg = gcnew WriteableBitmap(w, h, 96, 96, PixelFormats::Pbgra32, nullptr);
m_bufferPtr = (char*)m_writeableImg->BackBuffer.ToPointer();
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