In this 9th post on OpenGL and Vulkan interoperability on Linux with EXT_external_objects and EXT_external_objects_fd we are going to see another extensions use case where a Vulkan depth buffer is used to render a pattern with OpenGL. Like every other example use case described in these posts, it was implemented for Piglit as part of my work for Igalia‘s graphics team to check the extensions implementation of various mesa drivers.
The vk-depth-display
test is implemented in tests/spec/ext_external_objects/vk_depth_display.c
in piglit.
[UPDATE]
The code is not upstream yet, but it can found in Piglit’s MR!329 that is (at the time of writing) under review. The code can be found in Piglit’s master branch, and has been
1 |
Reviewed-by: Tapani Pälli <tapani.palli@intel.com> |
I’ve accidentally merged the 2 test-related commits without credits to the reviewer and so I thank him here…
Tapani Pälli reviewed all the other tests as well and wrote several patches for the EXT_external_objects(_fd) implementation on iris.
In vk-depth-display
test:
- A quad is rendered at a specific depth using Vulkan to fill the z buffer with a recognizable pattern.
- This Vulkan z buffer is imported from OpenGL where we clear the framebuffer to green and render a blue fullscreen quad positioned on the z axis further away than the original quad.
- We check if the z buffer has been imported correctly. If so, the blue quad should be partially obscured due to the depth test failing in the middle.
- We repeat for images of different depth, or depth/stencil formats.
Step 1: An external z image is allocated and used in a Vulkan renderpass
We’ve seen how we allocate external images from Vulkan in Parts 2 and 3.
As in this test we check different depth|stencil formats, we have a subtest per format where we allocate external Vulkan images and use them in OpenGL rendering. Everything is implemented in run_subtest
.
The images that are used in each subtests are allocated in vk_subtest_init
. The external allocation has already been explained in Parts 2 and 3.
What is different in this test is the Vulkan renderer (remember that the renderer is the abstraction I use in the Vulkan framework in vk.[hc]
to wrap all the pipeline/renderpass elements and operations that are required to render a scene). This test’s renderer can write values in the depth buffer attachment. To enable depth writes we need the value enable_depth
to be set to true when the renderer is created with a call to vk_create_renderer
:
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 |
static bool vk_subtest_init(int case_num) { 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; } if (!vk_fill_ext_image_props(&vk_core, w, h, d, num_samples, num_levels, num_layers, depth_stencil_formats[case_num].vk_ds_fmt, depth_tiling, depth_usage, depth_in_layout, depth_end_layout, &vk_depth_att.props)) { fprintf(stderr, "Unsupported depth image properties.\n"); vk_destroy_ext_image(&vk_core, &vk_color_att.obj); 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"); vk_destroy_ext_image(&vk_core, &vk_color_att.obj); return false; } if (!vk_create_renderer(&vk_core, vs_src, vs_sz, fs_src, fs_sz, true, false, &vk_color_att, &vk_depth_att, 0, &vk_rnd)) { fprintf(stderr, "Failed to create Vulkan renderer.\n"); vk_destroy_ext_image(&vk_core, &vk_color_att.obj); vk_destroy_ext_image(&vk_core, &vk_depth_att.obj); return false; } return true; } |
With enable_depth
set to true when creating the renderer and its renderpass and pipeline we also enable these depth/stencil settings in the pipeline (vk.[hc]
):
1 2 3 4 5 |
if (enable_depth) { ds_info.depthTestEnable = VK_TRUE; ds_info.depthWriteEnable = VK_TRUE; ds_info.depthCompareOp = VK_COMPARE_OP_LESS; } |
VK_COMPARE_OP_LESS
will write the depth to the z buffer only when it’s less than the value we used to clear it. Since clear the depth buffer to 1.0
when the renderpass begins (vk.[hc]
) any value less than 1.0 will be overwritten.
Let’s see how the depth pattern is created:
The shaders used for rendering (see vk_create_renderer
in the snippet above) are used to render something that is not important at all as the color image will not be used anywhere but is required for the Vulkan renderpass to begin. So the fragment shader is only painting a color. What is important though is the depth where we render the geometry. And so in the vertex shader we select a depth that is between 0 and 1 (here 0.33) and we draw a 2D quad in that depth.
1 2 3 4 5 6 7 8 9 |
const vec2 vdata[] = vec2[] ( vec2(0.5, 0.5), vec2(0.5, -0.5), vec2(-0.5, 0.5), vec2(-0.5, -0.5)); void main() { gl_Position = vec4(vdata[gl_VertexIndex], 0.33, 1.0); } |
When the Vulkan renderpass is over, the depth buffer will be an image with ones everywhere except the middle where we will have a quad of 0.33. Something like that:
.
.
.
... 1.00 1.00 1.00 1.00 ... 1.00 1.00 1.00 1.00 ...
... 1.00 1.00 1.00 1.00 ... 1.00 1.00 1.00 1.00 ...
... 1.00 1.00 1.00 1.00 ... 1.00 1.00 1.00 1.00 ...
... 1.00 1.00 1.00 1.00 ... 1.00 1.00 1.00 1.00 ...
... 1.00 1.00 0.33 0.33 ... 0.33 0.33 1.00 1.00 ...
... 1.00 1.00 0.33 0.33 ... 0.33 0.33 1.00 1.00 ...
.
.
.
... 1.00 1.00 0.33 0.33 ... 0.33 0.33 1.00 1.00 ...
... 1.00 1.00 0.33 0.33 ... 0.33 0.33 1.00 1.00 ...
... 1.00 1.00 1.00 1.00 ... 1.00 1.00 1.00 1.00 ...
... 1.00 1.00 1.00 1.00 ... 1.00 1.00 1.00 1.00 ...
... 1.00 1.00 1.00 1.00 ... 1.00 1.00 1.00 1.00 ...
... 1.00 1.00 1.00 1.00 ... 1.00 1.00 1.00 1.00 ...
.
.
.
Step 2: The external z buffer is used from OpenGL
In order to use the z-buffer from OpenGL we must first import the Vulkan depth attachment. This is similar to what we did in Parts 2, 3 and 4) to import the color attachment:
1 2 3 4 5 6 7 8 9 10 11 12 |
if (!gl_create_mem_obj_from_vk_mem(&vk_core, &vk_depth_att.obj.mobj, &gl_mem_obj)) { fprintf(stderr, "Failed to create GL memory object from Vulkan memory.\n"); goto fail; } if (!gl_gen_tex_from_mem_obj(&vk_depth_att.props, depth_stencil_formats[case_num].gl_ds_fmt, gl_mem_obj, 0, &vkgl_depth_tex)) { fprintf(stderr, "Failed to create GL texture from Vulkan memory object.\n"); goto fail; } |
Then, we need to render something that can help us test if the import was successful. And so,
- We render a blue full screen quad in depth 1.0 with the depth test enabled and using the external depth buffer as depth attachment to the target framebuffer.
- We display the result with depth test enabled. We expect to see a blue quad that has a small quad with the framebuffer color in the middle because in the middle we had
depth = 0.33 < 1
and so the blue color of the quad must be "obscured" by the framebuffer color.
To render from OpenGL using the Vulkan depth buffer we need to attach it to an OpenGL FBO that will serve as the OpenGL render target. We've created such an FBO in gl_init
(called from piglit_init
) and we can call gl_subtest_init
per subtest to attach each subtest's depth image (we run a subtest for each depth/stencil format supported in the driver) and then render a quad to its color attachment with the depth test enabled at a depth that is further away from the original 0.33
(here I used 0.66
):
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 |
static bool gl_subtest_init(void) { glBindFramebuffer(GL_FRAMEBUFFER, gl_fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, vkgl_depth_tex, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl_color_tex, 0); if (!check_bound_fbo_status()) return false; /* clear the render target using green */ glClearColor(0.0, 1.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); /* render a blue fullscreen green quad using * the imported z buffer */ glEnable(GL_DEPTH_TEST); glUseProgram(gl_rnd2fbo_prog); piglit_draw_rect_z(0.66, -1, -1, 2, 2); glBindFramebuffer(GL_FRAMEBUFFER, 0); glUseProgram(0); glBindTexture(GL_TEXTURE_2D, 0); return glGetError() == GL_NO_ERROR; } |
After this step, we are ready to display the color attachment to validate that the import was successful.
Step 3: Results validation
Back to run_subtest
we display the result and probe the color value in a few points to see if we detect green (framebuffer color) in the middle of the image and blue elsewhere:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
glBindFramebuffer(GL_FRAMEBUFFER, 0); glClearColor(1.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); /* having the z test disabled render a fullscreen * quad using the render target of gl_init */ glDisable(GL_DEPTH_TEST); glUseProgram(gl_prog); glBindTexture(GL_TEXTURE_2D, gl_color_tex); piglit_draw_rect_tex(-1, -1, 2, 2, 0, 0, 1, 1); /* the result must be: a blue quad that has the green framebuffer * color in the middle */ if (!piglit_probe_pixel_rgb(w / 2, h / 2, green)) goto fail; if (!piglit_probe_pixel_rgb(0, 0, blue) || !piglit_probe_pixel_rgb(piglit_width - 1, 0, blue) || !piglit_probe_pixel_rgb(0, piglit_height - 1, blue) || !piglit_probe_pixel_rgb(piglit_width - 1, piglit_height - 1, blue)) goto fail; piglit_present_results(); |
And that's it. In this test we enabled the depth test in a Vulkan renderpass and we imported the renderpass's depth attachment in OpenGL. We used the imported depth buffer as depth attachment in an OpenGL FBO and we've rendered a pattern with the depth test enabled. At the end we've validated that the pixels we expected to be obscured were obscured which means the z-buffer was imported successfully.
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] Part 8: Using a Vulkan vertex buffer from OpenGL and then from Vulkan
- [OpenGL and Vulkan Interoperability on Linux]: The XDC 2020 presentation
What's next:
Next post will be about reusing an external Vulkan stencil buffer from OpenGL. Stay tuned!