This is the 5th post of the OpenGL and Vulkan interoperability series where I describe some use cases for the EXT_external_objects and EXT_external_objects_fd extensions. These use cases have been implemented inside Piglit as part of my work for Igalia‘s graphics team using a Vulkan framework I’ve written for this purpose.
And in this 5th post, we are going to see a case where a pixel buffer is allocated and filled by Vulkan and its data are used as source data for an OpenGL texture image.
The code of vk-buf-exchange
test can be found in tests/spec/ext_external_objects/
Piglit directory, inside vk_buf_exchange.c
. The test:
- Creates an external Vulkan buffer with the same stripped pattern we’ve used in previous tests.
- Imports the pixel buffer in OpenGL.
- Uses this buffer as pixel storage for an OpenGL texture and displays its content on the screen.
- Validates the displayed result.
Step 1: External buffer creation from Vulkan
We’ve already seen in the previous OpenGL and Vulkan Interoperability posts that all tests have a piglit_init
function where we initialize the Vulkan objects calling vk_init
and the GL objects calling gl_init
and we usually call some functions to import the shared objects in OpenGL.
In this test’s initialization we need to create an external Vulkan buffer, import it from OpenGL and then use it. As we already have some code that renders to Vulkan images in the framework, we can fill it by copying to it pixels from a Vulkan image. And so, we first render the stripped pattern we’ve used in most previous examples using Vulkan on an image, and then we copy the image pixels to the external buffer we’ve just allocated.
External buffer allocation in vk_init
:
1 2 3 4 |
if (!vk_create_ext_buffer(&vk_core, w * h * 4 * sizeof(float), vk_bo_usage, &vk_bo)) { fprintf(stderr, "Failed to create Vulkan buffer.\n"); goto fail; } |
This framework (vk.[hc]
) function is creating a buffer but links a VkExternalMemoryBufferCreateInfo
struct in VkBufferCreateInfo
‘s pNext
pointer (see also vk_create_buffer
in vk.[hc]
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
bool vk_create_ext_buffer(struct vk_ctx *ctx, uint32_t sz, VkBufferUsageFlagBits usage, struct vk_buf *bo) { VkExternalMemoryBufferCreateInfo ext_bo_info; memset(&ext_bo_info, 0, sizeof ext_bo_info); ext_bo_info.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO; ext_bo_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR; if (!vk_create_buffer(ctx, sz, usage, &ext_bo_info, bo)) { fprintf(stderr, "Failed to allocate external buffer.\n"); return false; } return true; } |
By setting the VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR
bit the memory can be used by OpenGL.
Step 2: Importing the buffer in OpenGL
First (we are still in piglit_init
) we import the memory object as we’ve done in all previous examples:
1 2 3 4 |
if (!gl_create_mem_obj_from_vk_mem(&vk_core, &vk_bo.mobj, &gl_memobj)) { fprintf(stderr, "Failed to create GL memory object from Vulkan memory.\n"); piglit_report_result(PIGLIT_FAIL); } |
and then we create a buffer from the memory object calling:
1 2 3 4 |
if (!gl_gen_buf_from_mem_obj(gl_memobj, GL_PIXEL_UNPACK_BUFFER, vk_bo.mobj.mem_sz, 0, &gl_bo)) { fprintf(stderr, "Failed to create GL buffer from memory object.\n"); piglit_report_result(PIGLIT_FAIL); } |
This function is very similar to gl_gen_tex_from_mem_obj
we’ve seen in Parts 2, 3, and 4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
bool gl_gen_buf_from_mem_obj(GLuint mem_obj, GLenum gl_target, size_t sz, uint32_t offset, GLuint *bo) { glGenBuffers(1, bo); glBindBuffer(gl_target, *bo); glBufferStorageMemEXT(gl_target, sz, mem_obj, 0); glBindBuffer(gl_target, 0); return glGetError() == GL_NO_ERROR; } |
We called it with GL_PIXEL_UNPACK_BUFFER
as target because as we are going to see later this particular pixel buffer will be the data source for an OpenGL texture.
The EXT_external_objects glBufferStorageMemEXT
function is very similar to glBufferStorage
but creates the storage from the imported memory object mem_obj
.
Step 3: Filling the Vulkan buffer from Vulkan
An easy way to fill the pixel buffer from Vulkan is to render to a color image (like in Parts 3 and 4) and then copy the pixels from the image to the buffer. We do so in piglit_init
:
1 2 3 4 |
vk_draw(&vk_core, 0, &vk_rnd, vk_fb_color, 4, 0, false, false, NULL, 0, 0, 0, w, h); vk_copy_image_to_buffer(&vk_core, &vk_color_att, &vk_bo, w, h); |
(Both vk_draw
and vk_copy_image_to_buffer
can be found in vk.[hc]
).
The pattern that we’ve rendered is again the colored bars image we’ve seen in Parts 3 and 4:
I used that image in all examples that render pixels because it’s convenient: I can easily get an insight of the displayed pattern by probing the middle pixel of each bar.
After that, we can use the buffer from OpenGL.
Note here that we must make sure (with barriers) that there are no pending Vulkan operations when OpenGL takes the control. We use a barrier in vk_draw
to make sure the rendering has been completed before copying the pixels and one in vk_copy_image_to_buffer
to make sure copy has also been completed before we use the buffer from OpenGL.
Step 4: Using the buffer from OpenGL
In OpenGL we are going to use the buffer pixels in a texture to display them. And as a first step we need to create that texture. In gl_init
called by piglit_init
we create that texture:
1 2 3 4 5 6 |
glGenTextures(1, &gl_tex); glBindTexture(GL_TEXTURE_2D, gl_tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
Then, we fill it calling glTexImage2D
with the buffer data. To do so we need to bind the external buffer as GL_PIXEL_UNPACK_BUFFER
as it’s the texture data source!
1 2 3 |
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, gl_bo); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, w, h, 0, GL_RGBA, GL_FLOAT, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); |
Next step is to display the texture and validate the pixel values.
In piglit_display
we map the texture on a quad and use the piglit built-in functions to probe the middle color of each band to make sure the stripped pattern had been rendered by Vulkan:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
enum piglit_result piglit_display(void) { int i; float colors[6][4] = { {1.0, 0.0, 0.0, 1.0}, {0.0, 1.0, 0.0, 1.0}, {0.0, 0.0, 1.0, 1.0}, {1.0, 1.0, 0.0, 1.0}, {1.0, 0.0, 1.0, 1.0}, {0.0, 1.0, 1.0, 1.0} }; glUseProgram(gl_prog); glBindTexture(GL_TEXTURE_2D, gl_tex); piglit_draw_rect_tex(-1, -1, 2, 2, 0, 0, 1, 1); for (i = 0; i < 6; i++) { float x = i * (float)piglit_width / 6.0 + (float)piglit_width / 12.0; float y = (float)piglit_height / 2.0; if (!piglit_probe_pixel_rgba(x, y, colors[i])) { piglit_present_results(); return PIGLIT_FAIL; } } piglit_present_results(); return PIGLIT_PASS; } |
And that was it: we’ve just allocated an external buffer, filled it with some Vulkan image’s pixel, we imported it in OpenGL and bound it as PIXEL_UNPACK_BUFFER to use it as source data for an OpenGL texture. At the end we displayed that OpenGL texture. The test was expected to display the stripped pattern and pass.
Links:
- [1] EXT_external_objects specification
- [2] EXT_external_objects_fd specification
- [3] Previous posts on interoperability:
- [OpenGL and Vulkan Interoperability on Linux] Part 1: Introduction
- [OpenGL and Vulkan Interoperability on Linux] Part 2: Using OpenGL to draw on Vulkan textures.
- [OpenGL and Vulkan Interoperability on Linux] Part 3: Using OpenGL to display Vulkan allocated textures.
- [OpenGL and Vulkan Interoperability on Linux] Part 4: Using OpenGL to overwrite Vulkan allocated textures.
- [OpenGL and Vulkan Interoperability on Linux]: The XDC 2020 presentation
What’s next:
Next post will be again about pixel buffers, a variation of this one. Follow-up posts will be about external vertex buffers, stay tuned!