[OpenGL and Vulkan Interoperability on Linux] Part 4: Using OpenGL to overwrite Vulkan allocated textures.

This is the 4th post on OpenGL and Vulkan Interoperability on Linux. The first one was an introduction to EXT_external_objects and EXT_external_objects_fd extensions, the second was describing a simple interoperability use case where a Vulkan allocated textured is filled by OpenGL, and the third was about a slightly more complex use case where a Vulkan texture was filled by Vulkan and displayed by OpenGL. In this 4th and last post about shared textures, we are going to see a use case where a Vulkan texture is filled by Vulkan, then gets overwritten by OpenGL, then is read back from Vulkan and then displayed again using OpenGL. This more complex use case has also been written for Piglit using the small Vulkan framework I’ve written to test the external objects extensions. The source code can be found inside the tests/spec/ext_external_objects directory of the mesa/piglit master branch.

The test vk-image-display-overwrite:

  • Allocates an external image using Vulkan and renders the stripped pattern we’ve seen in Part 3 again using Vulkan.
  • Creates an OpenGL texture from the external image’s memory
  • Renders another stripped pattern on the external image using OpenGL
  • Maps the external Vulkan memory from Vulkan and takes a pointer to the new texture data.
  • Validates that these data are the ones coming from the OpenGL rendering.
Vulkan pattern
OpenGL pattern

Step 1: Vulkan and OpenGL structs initialization

This step is very similar with the initialization we’ve seen in the previous test. Again we have a vk_init and a gl_init function that initialize the Vulkan and OpenGL structs that are required for each rendering. They are both called in piglit_init where we also import the external resources:

vk_init in this test is slightly different from vk_init of vk-image-display's that we’ve seen in Part 3: after creating and initializing the structs we are going to need for rendering, we need to also create a Vulkan buffer:

This buffer is only necessary to allow us reading back the Vulkan image pixels. As the external image will be stored in device memory we can’t always map it from a user’s program. In some GPUs like Intel where the device memory is RAM a direct image mapping is allowed. But when the device memory is actual GPU memory, we usually have to copy each image to a buffer to read its data back.

Step 2: Vulkan renderpass

In piglit_display (similar to FreeGLUT display callback), we render again using Vulkan (like in Part 3):

We use semaphores for the GL server to block while Vulkan is rendering the stripped image we’ve rendered in the previous example:

Step 3: OpenGL overwriting

Let us see how OpenGL reuses and overwrites the image:

First of all, remember that we have imported the object at initialization (see piglit_init:

These functions are explained in more detail in Part 1 of the series. After calling them gl_tex is a valid GLuint OpenGL texture that we can use as render target for a new rendering operation.

To do so, in gl_init we need to create a framebuffer object (FBO) whose color attachment is gl_tex:

Then, in piglit_display where the rendering takes place we can just bind the FBO and render the second stripped pattern where the middle band is gray instead of yellow to it:

The shader program used in this case gl_prog_overwrite replaces the pattern with these two shaders:

Note that it’s very important to call glFinish after rendering because we must not have any pending OpenGL commands or partially written memory when we are going to use the texture from Vulkan!

Step 4: Reading back the pixels using Vulkan

Next step is to validate that when we re-read the shared memory from Vulkan it is updated and contains the stripped pattern written by OpenGL.

In some GPUs where the Vulkan “device memory” is in reality RAM (for example the Intel integrated GPUs) it is possible to call vkMapImageMemory and take a pointer to the pixels, as the device memory is allocated using the VK_MEMORY_PROPERTY_HOST_COHERENT_BIT. This is not possible with GPUs where the device memory is located on the GPU and so to read back the pixels we first need to copy the Vulkan image to a Vulkan buffer:

In the code snippet above, we copy the image to a buffer, then we map the buffer memory and take a pointer to the pixels. From this point we can do anything with them to validate them: display them with Vulkan, dump them to a file or anything.

I used them as pixel data in a new OpenGL texture created by glTexImage2D. The reason for that is that Piglit is an OpenGL testing framework and so I can only display OpenGL images. Moreover, I needed to use the piglit functions that probe pixel colors to validate their values as I’ll explain in the next step.

Step 5: Validating the pixel values using OpenGL

So, as I said, to validate that the new image contains the second stripped pattern with the gray band I’ve rendered it and then called piglit_probe_pixel_rgba for a pixel in the middle of each band. This function is calling glReadPixels internally.

And with this final piglit_present_results call, the second stripped pattern should be displayed:

And that was it. We’ve filled an image from Vulkan, we’ve overwritten it with OpenGL, we’ve read back the new content from Vulkan and we used it in a new OpenGL texture that we mapped in a quad to validate the change took place.


What’s next:

The upcoming posts will be about exchanging pixel and vertex buffers between OpenGL and Vulkan. Stay tuned!

See you next time!

Leave a Reply

Your email address will not be published. Required fields are marked *