This integration guide provides information relevant for a developer wishing to extend the Vulkan® WSI Layer with support for a new windowing system. In Vulkan® a windowing system is abstracted by a WSI platform and for each platform a specific extension exists.
In the layer a Vulkan® WSI platform is represented by a WSI backend. Adding a
WSI backend means implementing a WSI extension. Two WSI extensions that are implemented
in the layer are VK_KHR_wayland_surface
and VK_EXT_headless_surface
.
Each WSI backend implementation resides in the wsi
folder and has its own
directory. A new folder should be created for a new implementation,
for example wsi/new_wsi
.
Each WSI backend implements the surface
, surface_properties
and swapchain
interfaces. A new WSI backend implementation should be structured
as follows, where a separate file is used to contain the implementation of each
interface:
wsi/new_wsi
├── surface.cpp
├── surface.hpp
├── surface_properties.cpp
├── surface_properties.hpp
├── swapchain.cpp
└── swapchain.hpp
A new build option should be added to the CMakeLists.txt file to enable support for the new WSI backend. This allows compiling the layer for platforms that do not have the necessary support for the backend.
Furthermore the extension that is implemented by the new backend must be added in
the instance_extensions
list in the
VkLayer_window_system_integration.json
file. For example, VK_KHR_wayland_surface
is added to the JSON manifest when Wayland
support is enabled.
This interface contains functions that will be called during the interception of various Vulkan® surface related entrypoints. These functions should contain the platform specific code required to support the new WSI backend. For example, some of the functions that are intercepted are:
- vkGetPhysicalDeviceSurfaceCapabilitiesKHR
- vkGetPhysicalDeviceSurfaceCapabilitiesKHR2
- vkGetPhysicalDeviceSurfaceFormatsKHR
- vkGetPhysicalDeviceSurfaceFormatsKHR2
- vkGetPhysicalDeviceSurfacePresentModesKHR
A new WSI backend should implement the functions the interface consists of, which are described in the surface_properties.hpp file.
Each WSI extension defines surface/platform specific entrypoints, which are only used by
each individual surface. These entrypoints are exposed to the application through
the surface_properties::get_proc_addr
function, which returns a function pointer
to the implementation of the requested entrypoint. For example, if the extension for
a new WSI backend defines the entrypoint vkCreateNewXyzSurface
, then get_proc_addr
should look like this.
VWL_VKAPI_CALL(VkResult) CreateNewXyzSurface(...) VWL_API_POST
{
...
}
PFN_vkVoidFunction surface_properties::get_proc_addr(const char *name)
{
if (strcmp(name, "vkCreateNewXyzSurface") == 0)
{
return reinterpret_cast<PFN_vkVoidFunction>(CreateNewXyzSurface);
}
...
}
NOTE: The VWL_* macros are defined in macros.hpp and are used to mark function signatures.
NOTE: The extensions and their entrypoints are partially implemented by the Vulkan® loader so an appropriate loader should be used.
If any of the extension's specific entrypoints needs to call the next implementation
in the chain (e.g. call the ICD's entrypoint implementation), depending on
whether it is an instance or device entrypoint either the
INSTANCE_ENTRYPOINTS_LIST
or DEVICE_ENTRYPOINTS_LIST
must be extended with
the name of the entrypoint. For example, for the vkCreateNewXyzSurface
the instance
list should be extended.
diff --git a/layer/private_data.hpp b/layer/private_data.hpp
--- a/layer/private_data.hpp
+++ b/layer/private_data.hpp
@@ -73,6 +73,9 @@ namespace layer
OPTIONAL(CreateHeadlessSurfaceEXT) \
/* VK_KHR_wayland_surface */ \
OPTIONAL(CreateWaylandSurfaceKHR) \
+ /* VK_KHR_new_wsi */ \
+ OPTIONAL(CreateNewXyzSurface) \
/* VK_KHR_get_surface_capabilities2 */ \
OPTIONAL(GetPhysicalDeviceSurfaceCapabilities2KHR) \
OPTIONAL(GetPhysicalDeviceSurfaceFormats2KHR) \
Furthermore, these lists must be extended when the implementation needs to make use of
entrypoints defined by other extensions. For example, if a WSI implementation
needs to import a fence payload from a POSIX file descriptor the vkImportFenceFdKHR
function can be used, which is provided by the VK_KHR_external_fence_fd
extension. In order for the entrypoint to become visible to the implementation,
OPTIONAL(ImportFenceFdKHR)
must be added to the DEVICE_ENTRYPOINTS_LIST
(also an ICD that implements the extension must be used).
When a new entry is added to either of these lists the disp
member variable of
the singleton instance_private_data
or device_private_data
object is extended with a
function pointer that has the same name as the one that was added in the list.
The entrypoint can be called by getting the instance_private_data
/device_private_data
object and then using the function pointer in the disp
attribute.
For example, a WSI implementation that calls down the chain for the
vkCreateNewXyzSurface
function, should do the following.
auto &instance_data = layer::instance_private_data::get(instance);
VkResult res = instance_data.disp.CreateNewXyzSurface(...);
In order for the new surface_properties
implementation to be picked up by the
common layer code the following changes must be applied to the
wsi_factory.cpp file.
- Conditionally include the header of the WSI specific
surface_properties
implementation. - Extend the
supported_wsi_extensions
with the new WSI. - Add a new case with the new WSI platform in
get_surface_properties
.
diff --git a/wsi/wsi_factory.cpp b/wsi/wsi_factory.cpp
--- a/wsi/wsi_factory.cpp
+++ b/wsi/wsi_factory.cpp
@@ -46,6 +46,10 @@
#include "wayland/surface_properties.hpp"
#endif
+#if BUILD_NEW_WSI
+#include "new_wsi/surface_properties.hpp"
+#endif /* BUILD_NEW_WSI */
+
namespace wsi
{
@@ -60,6 +64,9 @@ static struct wsi_extension
#if BUILD_WSI_WAYLAND
{ { VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, VK_KHR_WAYLAND_SURFACE_SPEC_VERSION }, VK_ICD_WSI_PLATFORM_WAYLAND },
#endif
+#if BUILD_NEW_WSI
+ { { VK_KHR_NEW_WSI_EXTENSION_NAME, VK_KHR_NEW_WSI_SPEC_VERSION }, VK_ICD_WSI_PLATFORM_NEW_WSI },
+#endif /* BUILD_NEW_WSI */
};
static surface_properties *get_surface_properties(VkIcdWsiPlatform platform)
@@ -74,6 +81,10 @@ static surface_properties *get_surface_properties(VkIcdWsiPlatform platform)
case VK_ICD_WSI_PLATFORM_WAYLAND:
return &wayland::surface_properties::get_instance();
#endif
+#if BUILD_NEW_WSI
+ case VK_ICD_WSI_PLATFORM_NEW_WSI:
+ return &new_wsi::surface_properties::get_instance();
+#endif /* BUILD_NEW_WSI */
default:
return nullptr;
}
The surface interface represents a VkSurface. Each WSI backend's implementation
of this interface represents the platform specific surface. The wsi::surface
objects are associated with the corresponding VkSurface
objects in
instance_private_data
and they should be created
and linked to the instance_data
during the specific VkSurface
creation entrypoint.
A new WSI backend should implement the functions the interface consists of, which are described in surface.hpp.
The common swapchain functionality is implemented in the swapchain_base
class.
A new WSI backend should implement the virtual functions defined in
swapchain_base.hpp.
The base swapchain implementation has support for the FIFO presentation mode, which makes use of the presentation thread. Also, it gives the option to disable the thread in order to support other present modes like Mailbox. All of the WSI implementations can use the FIFO mode without any modifications. If a WSI implementation wants to leverage the Mailbox present mode extra synchronization with the presentation engine may be needed.
The use of the presentation thread is enabled in the init_platform
function
by setting the use_presentation_thread
flag.
In the layer the swapchain images are represented by the swapchain_image
struct.
This struct has a member variable which is called data
and is of void *
type.
This member variable is used to store the unique data that are needed by the images in
each WSI implementation.
Before submitting an image to the presentation engine the swapchain must wait
for the rendering operations on this image to finish. The synchronization primitives
used for this waiting operation by the WSI implementations are abstracted by the
classes defined in the synchronization.hpp file. Currently only
Vulkan® fences and fences exportable to Sync FD are supported. The specific behaviour
that depends on the type of the synchronization primitive a backend uses is implemented
in the image_set_present_payload
and image_wait_present
functions.
Helper objects and utilities can be found in util. These helpers can
be used to aid development. For example, alternatives to the standard containers
(std::vector
, std::unordered_map
, ...) are provided to make it easier for the
Vulkan® WSI Layer code to conform to the allocation requirements of Vulkan®. Also,
logging macros have been defined there for logging messages with different priority.
A non exhaustive list follows with the useful helper objects and functions
implemented there:
util::vector
util::allocator
util::allocator::make_unique
util::fd_owner
util::ring_buffer
util::unordered_map
util::unordered_set
And macros:
TRY
WSI_LOG_*