This is the 7th post on OpenGL and Vulkan Interoperability with EXT_external_objects. It’s about another EXT_external_objects use case implemented for Piglit as part of my work for Igalia‘s graphics team. In this case a vertex buffer is allocated and filled with data from Vulkan and then it’s used from OpenGL to render a pattern on screen.
The vk-vert-buf-update-errors
test is implemented in Piglit‘s tests/spec/ext_external_objects/vk_vert_buf_update_errors.c
and:
- Creates an external vertex buffer with Vulkan and fills it with vertices that could form the white quads of a chess board
- Imports the buffer in OpenGL and uses it as the vertex array at rendering
- Attempts to overwrite the buffer data with new geometry
- Validates that the geometry remained unchanged and renders a red-blue chessboard using OpenGL and the vertices
Step 1: An external Vulkan vertex buffer is created and filled with chess board quads
In piglit_init
we call vk_init
to initialize the Vulkan structs and there we create a Vulkan vertex buffer:
1 2 3 4 |
if (!vk_create_ext_buffer(&vk_core, WHITE_VERTS * sizeof(struct Vec2), vk_vb_usage, &vk_vb)) { fprintf(stderr, "Failed to create external Vulkan vertex buffer.\n"); return false; } |
We fill it with quads that would correspond to the white quads of a chess board:
1 2 3 4 5 6 7 8 9 |
/* Filling the Vulkan vertex buffer with vdata */ struct Vec2 *pdata; if (vkMapMemory(vk_core.dev, vk_vb.mobj.mem, 0, vk_vb.mobj.mem_sz, 0, (void**)&pdata) != VK_SUCCESS) { fprintf(stderr, "Failed to map Vulkan buffer memory.\n"); piglit_report_result(PIGLIT_FAIL); } gen_checkerboard_quads(pdata); vkUnmapMemory(vk_core.dev, vk_vb.mobj.mem); |
To do so we mapped the buffer’s memory and we generated the quads with gen_checkerboard_quads
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#define QUAD_SIZE (2.0 / 8.0) #define ADD_QUAD_VERT(a, b) do {vptr->x = a; vptr->y = b; vptr++;} while(0) static void gen_checkerboard_quads(struct Vec2 *vptr) { int i, j; struct Vec2 pos; for (i = 0; i < 8; i++) { pos.x = -1 + (i % 2) * QUAD_SIZE; pos.y = -1 + i * QUAD_SIZE; for (j = 0; j < 4; j++) { ADD_QUAD_VERT(pos.x, pos.y); ADD_QUAD_VERT(pos.x + QUAD_SIZE, pos.y); ADD_QUAD_VERT(pos.x + QUAD_SIZE, pos.y + QUAD_SIZE); ADD_QUAD_VERT(pos.x, pos.y); ADD_QUAD_VERT(pos.x + QUAD_SIZE, pos.y + QUAD_SIZE); ADD_QUAD_VERT(pos.x, pos.y + QUAD_SIZE); pos.x += QUAD_SIZE * 2.0; } } } |
Now the vertex buffer is ready to be imported in OpenGL.
Step 2: The external Vulkan quad is imported in OpenGL
To import the vertex buffer we’ll do something similar to what we did in Part 5 to import the pixel buffer, but this time the target will be GL_ARRAY_BUFFER
instead of GL_PIXEL_UNPACK_BUFFER
:
1 2 3 4 5 6 7 8 9 |
if (!gl_create_mem_obj_from_vk_mem(&vk_core, &vk_vb.mobj, &gl_memobj)) { fprintf(stderr, "Failed to create GL memory object from Vulkan memory.\n"); piglit_report_result(PIGLIT_FAIL); } if (!gl_gen_buf_from_mem_obj(gl_memobj, GL_ARRAY_BUFFER, vk_vb.mobj.mem_sz, 0, &gl_vk_vb)) { fprintf(stderr, "Failed to create GL buffer from memory object.\n"); piglit_report_result(PIGLIT_FAIL); } |
After these steps the buffer is ready to be used by OpenGL. (We’ve explained gl_gen_buf_from_mem_obj
in Part 5 and gl_create_mem_obj_from_vk_mem
in Part 2).
Step 3: Using the external vertex buffer to draw a chessboard pattern with OpenGL
We bind the buffer as GL_ARRAY_BUFFER
and we call glDrawArrays
like we would call it with an OpenGL allocated vertex array:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
glUseProgram(gl_prog); /* We are going to use the Vulkan allocated vertex buffer * in an OpenGL shader that paints the pixels blue. * As the vertices are set to render quads following a checkerboard * pattern (quad, no geometry, quad, no geometry) we should * see a checkerboard pattern where the color is blue when * we have geometry and red (framebuffer color) where there's no * geometry. */ glBindBuffer(GL_ARRAY_BUFFER, gl_vk_vb); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(0); glDrawArrays(GL_TRIANGLES, 0, WHITE_VERTS); glBindBuffer(GL_ARRAY_BUFFER, 0); |
We are using gl_prog
to paint the vertices blue after we have cleared the framebuffer to red. The vertex and pixel shaders used to build gl_prog
are quite minimal:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
static const char vs[] = "#version 130\n" "in vec2 vertex;\n" "void main()\n" "{\n" " gl_Position = vec4(vertex, 0.0, 1.0);\n" "}\n"; static const char fs[] = "#version 130\n" "out vec4 color;\n" "void main() \n" "{\n" " color = vec4(0.0, 0.0, 1.0, 1.0);\n" "}\n"; |
piglit_present_results
shows the chess board pattern on screen.
Step 4: An attempt to overwrite the buffer should return Invalid Operation Error
Just to make sure that the reused vertex buffer cannot be overwritten by OpenGL we perform a check that calling glBufferSubData
will return an error:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static float uninteresting_data[WHITE_VERTS * 2]; /* Checking that calling glBufferSubData with random vertices returns * invalid operation error. */ glBindBuffer(GL_ARRAY_BUFFER, gl_vk_vb); glBufferSubData(GL_ARRAY_BUFFER, 0, vk_vb.mobj.mem_sz, uninteresting_data); if (glGetError() != GL_INVALID_OPERATION) { fprintf(stderr, "glBufferSubData should return GL_INVALID_OPERATION error!\n"); return PIGLIT_FAIL; } |
Then, we clear the framebuffer to red and use the same vertex data to perform another drawing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/* Render again, and check that the checkerboard pattern hasn't * been changed (array was not modified) */ glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, WHITE_VERTS); glBindBuffer(GL_ARRAY_BUFFER, 0); if ((res = check_chessboard_pattern()) == PIGLIT_FAIL) { fprintf(stderr, "Vulkan buffer has been modified.\n"); return res; } piglit_present_results(); return res; |
At this point, the vertex data should be the ones written by Vulkan and piglit_present_results
must display a red-blue chess board pattern.
And that’s it!
This test case was written to validate that we can create and fill a vertex buffer from Vulkan and use it to render with OpenGL. But also to see that we aren’t allowed to overwrite the external vertex buffer’s content from OpenGL!
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] Part 5: A Vulkan pixel buffer is reused by OpenGL
- [OpenGL and Vulkan Interoperability on Linux] Part 6: About overwriting buffers
- [OpenGL and Vulkan Interoperability on Linux]: The XDC 2020 presentation
Coming soon:
Next post will be about using an external vertex buffer to draw once from OpenGL and once from Vulkan to make sure it can be re-used several times. Stay tuned!