This is the 8th post on OpenGL and Vulkan Interoperability with EXT_external_objects and EXT_external_objects_fd where I explain some example use cases of the extensions I’ve implemented for Piglit as part of my work for Igalia. In this example, a Vulkan vertex buffer is created and filled with vertices and then it’s used to render the same chess board pattern once with OpenGL and once with Vulkan.
The Piglit test where I’ve implemented this use case is named vk-vert-buf-reuse and it’s source code can be found in piglit/tests/spec/ext_external_objects/vk_vert_buf_reuse.c
(you must clone the Piglit master branch).
The vk-vert-buf-reuse
test:
- Allocates a Vulkan vertex buffer and fills it with vertices that could form the white quads of a chess board like the ones we’ve seen in Part 7.
- Imports and uses the vertex buffer from OpenGL to draw the pattern on screen (like in Part 7).
- Reuses the vertex buffer to draw the same image with Vulkan.
- Copies the Vulkan render target image to a buffer to take a pointer to data and displays them with OpenGL
The expected result is the same red-blue chessboard pattern we’ve seen in Part 7:
Step 1: Allocating and filling the Vulkan vertex buffer
This step is similar to the one we’ve seen in Part 7. In vk_init
of piglit_init
we create an external vertex buffer and we fill it vertices that could be used to draw the white quads of a chess board using gen_checkerboard_quads
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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; } /* 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); |
all the functions of the snippet above have already been explained in the previous post.
Step 2: Using the Vulkan vertex buffer in OpenGL rendering
We use the vertex buffer in piglit_display
to render the geometry in blue after we clear the framebuffer to red using a pair of shaders that we’ve also explained in Part 7. Then we validate that what is displayed on the screen is indeed a red-blue chessboard by calling check_red_blue_chess_pattern
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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); /* First we check that we've actually rendered a checkerboard * pattern by probing the middle pixel of each quad of the chess * image (should be red and blue) */ res = check_red_blue_chess_pattern(true); |
The function check_red_blue_chess_pattern
only probes the middle pixel’s color for each red or blue quad using built-in piglit testing functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
static enum piglit_result check_red_blue_chess_pattern(bool is_gl) { int i, j; float expected_color[2][4] = { {0.0, 0.0, 1.0, 1.0}, {1.0, 0.0, 0.0, 1.0}, }; for (i = 0; i < 8; i++) { int y = i * piglit_height / 8 + piglit_height / 16; for (j = 0; j < 8; j++) { int x = j * piglit_width / 8 + piglit_width / 16; int chess = (i & 1) ^ (j & 1); if (!piglit_probe_pixel_rgba(x, y, expected_color[chess])) { fprintf(stderr, "Wrong %s pattern.\n", is_gl ? "OpenGL" : "Vulkan"); return PIGLIT_FAIL; } } } return PIGLIT_PASS; } |
Step 3: The vertex buffer is used from Vulkan to draw the same chess board pattern
To draw with Vulkan we’ve first initialized some required Vulkan structs in vk_init
that is called inside piglit_init
. We used vk_init_vulkan_drawing
for that:
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
static bool vk_init_vulkan_drawing() { char *vs_src = 0; char *fs_src = 0; unsigned int vs_sz; unsigned int fs_sz; int w = piglit_width; int h = piglit_height; int d = 1; /* Creating external images */ /* Color image */ if (!vk_fill_ext_image_props(&vk_core, w, h, d, num_samples, num_levels, num_layers, color_format, color_tiling, color_usage, color_in_layout, color_end_layout, &vk_color_att.props)) { fprintf(stderr, "Unsupported color image properties.\n"); return false; } if (!vk_create_ext_image(&vk_core, &vk_color_att.props, &vk_color_att.obj)) { fprintf(stderr, "Failed to create color image.\n"); return false; } /* Depth image */ if (!vk_fill_ext_image_props(&vk_core, w, h, d, num_samples, num_levels, num_layers, depth_format, depth_tiling, depth_usage, depth_in_layout, depth_end_layout, &vk_depth_att.props)) { fprintf(stderr, "Unsupported depth image properties.\n"); return false; } if (!vk_create_ext_image(&vk_core, &vk_depth_att.props, &vk_depth_att.obj)) { fprintf(stderr, "Failed to create depth image.\n"); goto fail; } if (!(vs_src = load_shader(VK_BLUE_VERT, &vs_sz))) goto fail; if (!(fs_src = load_shader(VK_BLUE_FRAG, &fs_sz))) goto fail; vert_info.num_verts = WHITE_VERTS; vert_info.num_components = WHITE_TRIANGLES; vert_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; if (!vk_create_renderer(&vk_core, vs_src, vs_sz, fs_src, fs_sz, false, false, &vk_color_att, &vk_depth_att, &vert_info, &vk_rnd)) { fprintf(stderr, "Failed to create Vulkan renderer.\n"); goto fail; } if (!vk_create_buffer(&vk_core, w * h * 4 * sizeof(float), VK_BUFFER_USAGE_TRANSFER_DST_BIT, 0, &vk_tmp_buf)) { fprintf(stderr, "Failed to create buffer.\n"); goto fail; } free(vs_src); free(fs_src); return true; fail: free(vs_src); free(fs_src); return false; } |
This function creates a Vulkan “renderer” (an abstract struct I use to render different scenes with the Piglit Vulkan framework). It also creates the color and depth attachments, the shaders and the vertex buffer that will be used at drawing. The shaders do more or less the same thing the OpenGL ones did in Part 7 and their code can be found in tests/spec/ext_external_objects/vk_blue.vert
and tests/spec/ext_external_objects/vk_blue.frag
files of the piglit master branch (the test is upstream).
After this required step, the rendering can be performed by calling vk_draw_checkerboard
in piglit_display
. This function renders the checkerboard pattern to the Vulkan renderer’s render target. After it’s called we only need a pointer to its pixels and we can use it with a new OpenGL texture to display and validate that the pattern is the same.
[Note!] the reason we display the pixels with OpenGL is that this code is part of the piglit testing framework where we can only draw pixels using OpenGL. In a different application someone could simply visualize the renderer’s renderpass render target with 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 |
/* Round 2: We render the same checkerboard using Vulkan and we copy the render target * to a buffer to map the memory and take a pointer to the data. Then we compare them with * the GL image checkerboard pattern */ vk_draw_checkerboard(); vk_copy_image_to_buffer(&vk_core, &vk_color_att, &vk_tmp_buf, piglit_width, piglit_height); if (vkMapMemory(vk_core.dev, vk_tmp_buf.mobj.mem, 0, vk_tmp_buf.mobj.mem_sz, 0, &pixels) != VK_SUCCESS) { fprintf(stderr, "Failed to map Vulkan image memory.\n"); piglit_report_result(PIGLIT_FAIL); } /* Because we can't render using Vulkan on piglit, we use the * pixels we've just read from Vulkan memory as texture data * in a new OpenGL texture. */ glBindTexture(GL_TEXTURE_2D, gl_disp_tex); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, piglit_width, piglit_height, GL_RGBA, GL_FLOAT, pixels); glFinish(); vkUnmapMemory(vk_core.dev, vk_tmp_buf.mobj.mem); glUseProgram(gl_disp_vk_prog); piglit_draw_rect_tex(-1, -1, 2, 2, 0, 0, 1, 1); res = check_red_blue_chess_pattern(false); piglit_present_results(); return res; } |
The Vulkan function that draws the chess board pattern is simply a call to vk_draw
we’ve seen in previous examples:
1 2 3 4 5 6 7 8 |
static void vk_draw_checkerboard() { float fb_color[4] = {1.0, 0.0, 0.0, 1.0}; struct vk_image_att images[] = { vk_color_att, vk_depth_att }; vk_draw(&vk_core, &vk_vb, &vk_rnd, fb_color, 4, 0, 0, 0, images, 2, 0, 0, piglit_width, piglit_height); } |
And that was it!
In this test we’ve allocated a Vulkan vertex buffer, we’ve used it to draw with OpenGL and then we reused it to draw with Vulkan. At the end, we displayed the pixels of the Vulkan render target with OpenGL to validate they form a blue and red chess board pattern.
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] Part 7: Reusing a Vulkan vertex buffer from OpenGL
- [OpenGL and Vulkan Interoperability on Linux]: The XDC 2020 presentation
What’s next:
Next post will be about reusing an external Vulkan depth buffer from OpenGL and there will be one more about reusing the stencil buffer. Stay tuned!