[OpenGL and Vulkan Interoperability on Linux] Part 2: Using OpenGL to draw on Vulkan textures.

This is the second post of the OpenGL and Vulkan interoperability series, where I explain some EXT_external_objects and EXT_external_objects_fd use cases with examples taken by the Piglit tests I’ve written to test the extensions as part of my work for Igalia‘s graphics team.

We are going to see a very simple case of Vulkan/GL interoperability where an image is allocated using Vulkan and filled using OpenGL. This case is implemented in Piglit’s vk-image-overwrite test for images of different formats.

The vk-image-overwrite test:

  1. Allocates an image using Vulkan.
  2. Creates an OpenGL texture from the Vulkan allocated image memory.
  3. Fills the texture with blue color using OpenGL.
  4. Compares the texture values in a shader with blue: when the input color is blue the fragment is painted green when not it is painted red.
  5. Checks if all pixels are green and if yes the test passes, if at least one non-green pixel is found it fails
  6. The rendering/check is repeated for many images of different formats and tiling modes.

Step 1: Image allocation (Vulkan)

In tests/spec/ext_external_objects/vk_image_overwrite.c (see links below to checkout the code from Gitlab), we allocate each image in function run_subtest:

Functions prefixed as vk_ can be found in tests/spec/ext_external_objects/vk.[hc] and are Vulkan helper functions. As the name implies vk_set_image_properties just set the image properties. The important function is vk_create_ext_image that allocates an external Vulkan image (which is an image whose memory can be accessed by OpenGL):

In the snippet above I’ve used an extra Vulkan structure VkExternalMemoryImageCreateInfo when I filled VkImageCreateInfo and then I allocated the image calling the helper function alloc_memory (see vk.c) here:

Here again a secondary VkExportMemoryAllocateInfo struct was necessary in order to set the VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT to the VkMemoryAllocateInfo struct.


Step 2: Creating an OpenGL texture from the Vulkan allocated image (Interoperability)

First of all, I had to import the Vulkan memory in OpenGL, in other words create a GL memory object from a Vulkan memory object. For that I used the function gl_create_mem_obj_from_vk_mem from tests/spec/ext_external_objects/interop.[hc] (which is the file that contains all the interoperability helper functions):

In this snippet, I took the Linux file descriptor that corresponds to the Vulkan memory and its size from Vulkan, and I’ve created an OpenGL object using the EXT_memory_object_fd import function glImportMemoryFdEXT to assign the object id to the memory. Then I checked that this newly created object is a valid OpenGL memory object.

After that, I had to “tell” OpenGL that this object will correspond to a GL texture. Creating a texture from external memory is quite similar with creating a texture from some internal storage:

The GL call glTexStorageMem*DEXT that is introduced in EXT_memory_object creates a texture like glTexStorage would if the object was “internal”.

Something important when creating textures from external memory objects is to set the appropriate tiling mode calling glTexParameteri before calling glTexStorageMem*DEXT. This is because the external texture will become immutable at creation and changing the tiling mode in an immutable object is forbidden! The tiling mode should match the one used in Vulkan (default is optimal).

After this point, the texture was ready to be used by OpenGL.


Step 3: Rendering to texture (OpenGL)

I rendered to texture as I would do using an ordinary 2D texture render target. For simplicity I rendered all the pixels blue.

In the snippet above I used the Vulkan-memory texture as the color attachment of a previously generated FBO. Then I cleared the framebuffer color to blue.


Step 4: Checking that the pixels have the correct color

Next thing I did was to check if the pixels are actually blue using a pixel shader. The shader compares each pixel’s color with an expected color passed as a uniform to the shader if the colors match the fragment is painted green else red. The uniform was necessary only because I wanted to check different format images (int, float, uint) and so I couldn’t hardcode the blue. πŸ™‚

(I used the MAKE_FS macro to allow different formats and I used different expected colors depending on the number of bits and if they were int or float and signed or unsigned (see run_subtest of vk_image_overwrite.c for more).


Step 5: Checking the result with some piglit helper functions

After having rendered a green or green/red quad in step 4, I used some simple piglit helper functions to check if all pixels are green. That was necessary because the purpose of the test was to validate that we can overwrite a Vulkan allocated image with OpenGL. I used one of the piglit_probe_* functions that can probe the color of one or more pixels for that. When the color was green the test passed when it was red it was failing. And that was all. Below I am going to explain some other parts of the test because they might be useful to understand parts of the follow-up examples.


Other details of the piglit test

All interoperability tests of the series are inside the tests/spec/ext_external_objects directory. The Vulkan helper functions used above (of the form vk_) can be found in tests/spec/ext_external_objects/vk.[hc] and the interoperability helper functions in tests/spec/ext_external_objects/interop.[hc]

Piglit provides some callbacks that are very similar in concept to the freeglut callbacks. In most tests, I only fill piglit_init that initializes the OpenGL context, and piglit_display that is similar to GlutDisplayFunc callback and contains the operations that are related to the rendering.

Functions vk_init and gl_init initialize Vulkan and OpenGL data structures we need for all the subtests of the test respectively. piglit_require_extension is set to enable the other potentially required extensions.

To check if the OpenGL and Vulkan devices are compatible, we used the extended GetUnsignedBytei_v and GetUnsignedBytev to query the IDs of the device and the driver and see if they match each other as it is specified in EXT_external_objects. (I think that code was written by Juan A. Suarez Romero who also helped with the tests, credits!)

Note that for simplicity here we assumed that the user has only one Vulkan device. In later versions I detect the first device that supports graphics pipelines.


Links:

[1]: EXT_external_objects extension: https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_external_objects.txt
[2]: EXT_external_objects_fd extension: https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_external_objects_fd.txt
[3]: Piglit Code: https://gitlab.freedesktop.org/mesa/piglit
[4]: Samuel Iglesias GonsΓ‘lvez’s posts on Piglit: https://blogs.igalia.com/siglesias/2015/06/12/piglit-v-how-to-contribute-to-piglit-and-table-of-contents/


Previous posts in these series:
[1]: [OpenGL and Vulkan Interoperability on Linux] Part 1: Introduction https://eleni.mutantstargoat.com/hikiko/gl-vk-interop-intro/

See you next time!

8 thoughts on “[OpenGL and Vulkan Interoperability on Linux] Part 2: Using OpenGL to draw on Vulkan textures.”

  1. Another great article. I wish I had this the last time I was setting interoperability code.

    Because of immutability, tiling needs to be set before glTexStorageMem*DEXT is called. What about properties like GL_TEXTURE_WRAP_S?

    1. Properties like GL_TEXTURE_WRAP_S are not changed by this extension. This particular property is related to sampling (how OpenGL will sample the texture), so you can use what you would use if the texture was a native OpenGL texture.

  2. Any comments regarding using dedicated versus non dedicated memory in the context of interoperability between OpenGLES and Vulkan?

    1. In some GPUs for example some AMD GPUs (that use radv, radeonsi) the external Vulkan memory is dedicated instead of suballocated. If you check vk.c of Piglit when we allocate image memory we can tell if dedicated memory is required (see alloc_image_memory) by checking the memory requirements. The dedicated memory support in the framework comes from a patch from Bas Nieuwenhuizen who works for AMD.

  3. If glIsMemoryObjectEXT returns false, we would expect that glGetError() != GL_NO_ERROR right after glIsMemoryObjectEXT , right?

    1. If glIsMemoryObjectEXT returns false, we would expect that glGetError() != GL_NO_ERROR right after glIsMemoryObjectEXT , right?

      I use the GL_NO_ERROR check in every function, it’s not to check a particular command but to check that the whole block is error free. glIsMemoryObjectEXT returns false if the uid doesn’t correspond to a memory object so we will have already tested it when glGetError runs. The checks are unrelated to each other.

  4. I meant to say right after glImportMemoryFdEXT

    The idea is: we try to import the object. Then we check if it’s a valid memory object. At the end of the function (and each function) we check that there were no OpenGL errors (any type of errors).

Leave a Reply

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