41ef1d1dfe3e56766d1f8b72b335567eb7842d04 — Drew DeVault a month ago 975708b master
Add wayland-direct window mode

For testing DRM leasing on Wayland with a VR headset
M CMakeLists.txt => CMakeLists.txt +38 -25
@@ 145,33 145,46 @@ set(WAYLAND_PROTOCOLS_DIR "${CMAKE_CURRENT_BINARY_DIR}/wayland-protocols/")
  file(MAKE_DIRECTORY "${WAYLAND_PROTOCOLS_DIR}")
  
- set(PROTOCOL xdg-shell-unstable-v6)
- set(PROTOCOL_XML
-     "${WAYLAND_PROTOCOLS_DATADIR}/unstable/xdg-shell/${PROTOCOL}.xml")
+ set(PROTOCOLS)
+ set(PROTOCOLS_XML)
+ list(APPEND PROTOCOLS xdg-shell-unstable-v6)
+ list(APPEND PROTOCOLS_XML
+     "${WAYLAND_PROTOCOLS_DATADIR}/unstable/xdg-shell/xdg-shell-unstable-v6.xml")
+ 
+ list(APPEND PROTOCOLS drm-lease-unstable-v1)
+ list(APPEND PROTOCOLS_XML
+ 	"${CMAKE_CURRENT_SOURCE_DIR}/vitamin-k/window/drm-lease-unstable-v1.xml")
  
  set(WAYLAND_PROTOCOLS_SRC)
- set(OUTFILE "${WAYLAND_PROTOCOLS_DIR}/${PROTOCOL}.c")
- list(APPEND WAYLAND_PROTOCOLS_SRC "${OUTFILE}")
- add_custom_command(OUTPUT "${OUTFILE}"
-     COMMAND
-     ${WAYLAND_SCANNER}
-     code
-     "${PROTOCOL_XML}"
-     "${OUTFILE}"
-     VERBATIM
-     COMMENT "Generating code file with wayland-scanner")
- 
- 
- set(OUTFILE "${WAYLAND_PROTOCOLS_DIR}/${PROTOCOL}.h")
- list(APPEND WAYLAND_PROTOCOLS_SRC "${OUTFILE}")
- add_custom_command(OUTPUT "${OUTFILE}"
-     COMMAND
-     ${WAYLAND_SCANNER}
-     client-header
-     "${PROTOCOL_XML}"
-     "${OUTFILE}"
-     VERBATIM
-     COMMENT "Generating client header file with wayland-scanner")
+ list(LENGTH PROTOCOLS NPROTOCOLS)
+ math(EXPR NPROTOCOLS "${NPROTOCOLS}-1")
+ foreach(INDEX RANGE ${NPROTOCOLS})
+ 	list(GET PROTOCOLS ${INDEX} PROTOCOL)
+ 	list(GET PROTOCOLS_XML ${INDEX} PROTOCOL_XML)
+ 
+ 	set(OUTFILE "${WAYLAND_PROTOCOLS_DIR}/${PROTOCOL}.c")
+ 	list(APPEND WAYLAND_PROTOCOLS_SRC "${OUTFILE}")
+ 
+ 	add_custom_command(OUTPUT "${OUTFILE}"
+ 		COMMAND
+ 		${WAYLAND_SCANNER}
+ 		private-code
+ 		"${PROTOCOL_XML}"
+ 		"${OUTFILE}"
+ 		VERBATIM
+ 		COMMENT "Generating code file with wayland-scanner for ${PROTOCOL}")
+ 
+ 	set(OUTFILE "${WAYLAND_PROTOCOLS_DIR}/${PROTOCOL}.h")
+ 	list(APPEND WAYLAND_PROTOCOLS_SRC "${OUTFILE}")
+ 	add_custom_command(OUTPUT "${OUTFILE}"
+ 		COMMAND
+ 		${WAYLAND_SCANNER}
+ 		client-header
+ 		"${PROTOCOL_XML}"
+ 		"${OUTFILE}"
+ 		VERBATIM
+ 		COMMENT "Generating client header file with wayland-scanner for ${PROTOCOL}")
+ endforeach()
  
  add_library(wayland-protocols ${WAYLAND_PROTOCOLS_SRC})
  target_include_directories(wayland-protocols PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")

M vitamin-k/render/vikRenderer.hpp => vitamin-k/render/vikRenderer.hpp +2 -1
@@ 467,8 467,9 @@ if (count > 0) {
        std::vector<VkExtensionProperties> extensions(count);
        if (vkEnumerateInstanceExtensionProperties(nullptr, &count, &extensions.front()) == VK_SUCCESS)
-         for (auto ext : extensions)
+         for (auto ext : extensions) {
            supported_extensions.push_back(ext.extensionName);
+         }
      }
    }
  

M vitamin-k/system/vikApplication.hpp => vitamin-k/system/vikApplication.hpp +4 -0
@@ 31,6 31,7 @@ #endif
  
  #include "../window/vikWindowDirectMode.hpp"
+ #include "../window/vikWindowDirectWayland.hpp"
  
  #include "../render/vikTools.hpp"
  #include "../scene/vikCamera.hpp"


@@ 177,6 178,9 @@ case Settings::DIRECT_MODE:
          window = new WindowDirectMode(&settings);
          return set_and_init_window();
+       case Settings::DIRECT_WAYLAND:
+         window = new WindowDirectWayland(&settings);
+         return set_and_init_window();
        default:
          vik_log_f("Usupported Window Type %d", settings.window_type);
          return -1;

M vitamin-k/system/vikSettings.hpp => vitamin-k/system/vikSettings.hpp +3 -0
@@ 31,6 31,7 @@ WAYLAND_XDG,
      WAYLAND_SHELL,
      DIRECT_MODE,
+     DIRECT_WAYLAND,
      INVALID
    };
  


@@ 268,6 269,8 @@ return WAYLAND_SHELL;
      else if (streq(s, "direct"))
        return DIRECT_MODE;
+     else if (streq(s, "direct-wayland"))
+       return DIRECT_WAYLAND;
      else
        return INVALID;
    }

A vitamin-k/window/drm-lease-unstable-v1.xml => vitamin-k/window/drm-lease-unstable-v1.xml +238 -0
@@ 0,0 1,238 @@
+ <?xml version="1.0" encoding="UTF-8"?>
+ <protocol name="drm_lease_unstable_v1">
+   <copyright>
+     Copyright © 2018 NXP
+     Copyright © 2019 Status Research &amp; Development GmbH.
+ 
+     Permission is hereby granted, free of charge, to any person obtaining a
+     copy of this software and associated documentation files (the "Software"),
+     to deal in the Software without restriction, including without limitation
+     the rights to use, copy, modify, merge, publish, distribute, sublicense,
+     and/or sell copies of the Software, and to permit persons to whom the
+     Software is furnished to do so, subject to the following conditions:
+ 
+     The above copyright notice and this permission notice (including the next
+     paragraph) shall be included in all copies or substantial portions of the
+     Software.
+ 
+     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+     THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+     DEALINGS IN THE SOFTWARE.
+   </copyright>
+ 
+   <interface name="zwp_drm_lease_manager_v1" version="1">
+     <description summary="lease manager">
+       This protocol is used by Wayland compositors which act as Direct
+       Renderering Manager (DRM) masters to lease DRM resources to Wayland
+       clients. Once leased, the compositor will not use the leased resources
+       until the lease is revoked or the client closes the file descriptor.
+ 
+       The lease manager is used to advertise connectors which are available for
+       leasing, and by the client to negotiate a lease request.
+ 
+       Warning! The protocol described in this file is experimental and
+       backward incompatible changes may be made. Backward compatible changes
+       may be added together with the corresponding interface version bump.
+       Backward incompatible changes are done by bumping the version number in
+       the protocol and interface names and resetting the interface version.
+       Once the protocol is to be declared stable, the 'z' prefix and the
+       version number in the protocol and interface names are removed and the
+       interface version number is reset.
+     </description>
+ 
+     <request name="create_lease_request">
+       <description summary="create a lease request object">
+         Creates a lease request object.
+ 
+         See the documentation for zwp_drm_lease_request_v1 for details.
+       </description>
+       <arg name="id" type="new_id" interface="zwp_drm_lease_request_v1" />
+     </request>
+ 
+     <request name="stop">
+       <description summary="stop sending events">
+         Indicates the client no longer wishes to receive connector events. The
+         compositor may still send connector events until it sends the finish
+         event, however.
+ 
+         The client must not send any requests after this one.
+       </description>
+     </request>
+ 
+     <event name="connector">
+       <description summary="advertise connectors available for leases">
+         The compositor may choose to advertise 0 or more connectors which may be
+         leased to clients, and will use this event to do so. This object may be
+         passed into a lease request to lease that connector. See
+         zwp_drm_lease_request_v1.add_connector for details.
+ 
+         When this global is bound, the compositor will send all connectors
+         available for lease, but may send additional connectors at any time.
+       </description>
+       <arg name="id" type="new_id" interface="zwp_drm_lease_connector_v1" />
+     </event>
+ 
+     <event name="finished">
+       <description summary="the compositor has finished using the manager">
+         This event indicates that the compositor is done sending connector
+         events. The compositor will destroy this object immediately after
+         sending this event, and it will become invalid. The client should
+         release any resources associated with this manager after receiving this
+         event.
+       </description>
+     </event>
+   </interface>
+ 
+   <interface name="zwp_drm_lease_connector_v1" version="1">
+     <description summary="a leasable DRM connector">
+       Represents a DRM connector which is available for lease. These objects are
+       created via zwp_drm_lease_manager_v1.connector, and should be passed into
+       lease requests via zwp_drm_lease_request_v1.add_connector.
+     </description>
+ 
+     <event name="name">
+       <description summary="name">
+         The compositor sends this event once the connector is created to
+         indicate the name of this connector. This will not change for the
+         duration of the Wayland session, but is not guaranteed to be consistent
+         between sessions.
+ 
+         If the compositor also supports zxdg_output_manager_v1 and this
+         connector corresponds to a zxdg_output_v1, this name will match the
+         name of this zxdg_output_v1 object.
+       </description>
+       <arg name="name" type="string" summary="connector name" />
+     </event>
+ 
+     <event name="description">
+       <description summary="description">
+         The compositor sends this event once the connector is created to provide
+         a human-readable description for this connector, which may be presented
+         to the user. This will not change for the duration of the Wayland
+         session, but is not guaranteed to be consistent between sessions.
+ 
+         If the compositor also supports zxdg_output_manager_v1 and this
+         connector corresponds to a zxdg_output_v1, this description will match
+         the description of this zxdg_output_v1 object.
+       </description>
+       <arg name="name" type="string" summary="connector name" />
+     </event>
+ 
+     <event name="edid">
+       <description summary="edid">
+         The compositor sends this event once the connector is created to
+         provide a file descriptor which may be memory-mapped to read the
+         connector's EDID, to assist in selecting the correct connectors
+         for lease.
+       </description>
+       <arg name="edid" type="fd" summary="EDID file descriptor" />
+       <arg name="size" type="uint" summary="EDID size, in bytes"/>
+     </event>
+ 
+     <event name="withdrawn">
+       <description summary="lease offer withdrawn">
+         Sent to indicate that the compositor will no longer honor requests for
+         DRM leases which include this connector. The client may still issue a
+         lease request including this connector, but the compositor will send
+         zwp_drm_lease_v1.finished without issuing a lease fd.
+       </description>
+     </event>
+ 
+     <request name="destroy" type="destructor">
+       <description summary="destroy connector">
+         The client may send this request to indicate that it will not issue a
+         lease request for this connector. Clients are encouraged to send this
+         after receiving the "withdrawn" request so that the server can release
+         the resources associated with this connector offer.
+       </description>
+     </request>
+   </interface>
+ 
+   <interface name="zwp_drm_lease_request_v1" version="1">
+     <description summary="DRM lease request">
+       A client that wishes to lease DRM resources will attach the list of
+       connectors advertised with zwp_drm_lease_manager_v1.connector that they
+       wish to lease, then use zwp_drm_lease_request_v1.submit to submit the
+       request.
+     </description>
+ 
+     <enum name="error">
+       <entry name="no_connectors" value="0"
+         summary="request submitted with zero connectors"/>
+       <entry name="submitted_lease" value="1"
+         summary="attempted to reuse a submitted lease"/>
+     </enum>
+ 
+     <request name="destroy" type="destructor">
+       <description summary="destroys the lease request object">
+         Indicates that the client will no longer use this lease request.
+       </description>
+     </request>
+ 
+     <request name="request_connector">
+        <description summary="request a connector for this lease">
+          Indicates that the client would like to lease the given connector.
+          This is only used as a suggestion, the compositor may choose to
+          include any resources in the lease it issues, or change the set of
+          leased resources at any time.
+        </description>
+        <arg name="connector" type="object"
+          interface="zwp_drm_lease_connector_v1" />
+     </request>
+ 
+     <request name="submit">
+        <description summary="submit the lease request">
+          Submits the lease request and creates a new zwp_drm_lease_v1 object.
+          After calling submit, issuing any other request than destroy is a
+          protocol error. Submitting a lease request with no connectors is a
+          protocol error.
+        </description>
+        <arg name="id" type="new_id" interface="zwp_drm_lease_v1" />
+     </request>
+   </interface>
+ 
+   <interface name="zwp_drm_lease_v1" version="1">
+     <description summary="a DRM lease">
+       A DRM lease object is used to transfer the DRM file descriptor to the
+       client and manage the lifetime of the lease.
+     </description>
+ 
+     <event name="lease_fd">
+       <description summary="shares the DRM file descriptor">
+         This event returns a file descriptor suitable for use with DRM-related
+         ioctls. The client should use drmModeGetLease to enumerate the DRM
+         objects which have been leased to them, which may not be the objects
+         they requested. The lease may have zero DRM objects.
+ 
+         The compositor may also issue and immediately revoke the lease if no
+         connectors are leasable, in which case this event is not sent.
+ 
+         It is a protocol error for the compositor to send this event more than
+         once for a given lease.
+       </description>
+       <arg name="leased_fd" type="fd" summary="leased DRM file descriptor" />
+     </event>
+ 
+     <event name="finished">
+       <description summary="sent when the lease has been revoked">
+         When the compositor revokes the lease, it will issue this event to
+         notify clients of the change. If the client requires a new lease, they
+         should destroy this object and submit a new lease request. The
+         compositor will send no further events for this object after sending
+         the finish event.
+       </description>
+     </event>
+ 
+     <request name="destroy" type="destructor">
+       <description summary="destroys the lease object">
+         The client should send this to indicate that it no longer wishes to use
+         this lease. The compositor should use drmModeRevokeLease on the
+         appropriate file descriptor, if necessary, then release this object.
+       </description>
+     </request>
+   </interface>
+ </protocol>

A vitamin-k/window/vikWindowDirectWayland.hpp => vitamin-k/window/vikWindowDirectWayland.hpp +339 -0
@@ 0,0 1,339 @@
+ /*
+  * vitamin-k
+  *
+  * Copyright 2017-2018 Collabora Ltd.
+  * Copyright 2019 Status Holdings Ltd.
+  *
+  * Authors: Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
+  * Authors: Drew DeVault <sir@cmpwn.com>
+  * SPDX-License-Identifier: MIT
+  */
+ 
+ #pragma once
+ 
+ #include <wayland-client.h>
+ #include "wayland-protocols/drm-lease-unstable-v1.h"
+ 
+ #include <vulkan/vulkan.h>
+ #include <vulkan/vulkan_wayland.h>
+ 
+ #include <string>
+ #include <vector>
+ #include <utility>
+ 
+ #include "vikWindow.hpp"
+ 
+ #include "../render/vikSwapChainVK.hpp"
+ 
+ namespace vik {
+ class WindowDirectWayland : public Window {
+   struct wl_display *wl_display = nullptr;
+   struct zwp_drm_lease_manager_v1 *manager = nullptr;
+ 
+   SwapChainVK swap_chain;
+ 
+   struct VikDisplay {
+     std::string name, description;
+     struct zwp_drm_lease_connector_v1 *connector;
+     struct zwp_drm_lease_v1 *lease;
+   };
+ 
+   std::vector<VikDisplay> displays;
+ 
+   static void lease_connector_handle_name(void *data,
+       struct zwp_drm_lease_connector_v1 *zwp_drm_lease_connector_v1,
+       const char *name) {
+     VikDisplay *d = (VikDisplay *)data;
+     d->name = std::string(name);
+   }
+ 
+   static void lease_connector_handle_description(void *data,
+       struct zwp_drm_lease_connector_v1 *zwp_drm_lease_connector_v1,
+       const char *desc) {
+     VikDisplay *d = (VikDisplay *)data;
+     d->description = std::string(desc);
+   }
+ 
+   static void lease_connector_handle_edid(void *data,
+       struct zwp_drm_lease_connector_v1 *zwp_drm_lease_connector_v1,
+       int32_t edid, uint32_t size) {
+     close(edid);
+   }
+ 
+   static void lease_connector_handle_withdrawn(void *data,
+       struct zwp_drm_lease_connector_v1 *zwp_drm_lease_connector_v1) {
+     /* This space deliberately left blank */
+   }
+ 
+   const struct zwp_drm_lease_connector_v1_listener lease_connector_listener = {
+     .name = lease_connector_handle_name,
+     .description = lease_connector_handle_description,
+     .edid = lease_connector_handle_edid,
+     .withdrawn = lease_connector_handle_withdrawn,
+   };
+ 
+   static void lease_manager_handle_connector(void *data,
+       struct zwp_drm_lease_manager_v1 *lease_manager,
+       struct zwp_drm_lease_connector_v1 *id) {
+     WindowDirectWayland *win = (WindowDirectWayland *)data;
+     VikDisplay d = {
+       .connector = id,
+     };
+     win->displays.push_back(d);
+     zwp_drm_lease_connector_v1_add_listener(id, &win->lease_connector_listener,
+         &win->displays[win->displays.size() - 1]);
+   }
+ 
+   static void lease_manager_handle_finished(void *data,
+       struct zwp_drm_lease_manager_v1 *lease_manager) {
+     /* This space deliberately left blank */
+   }
+ 
+   const struct zwp_drm_lease_manager_v1_listener lease_manager_listener = {
+     .connector = lease_manager_handle_connector,
+     .finished = lease_manager_handle_finished,
+   };
+ 
+   static void registry_handle_global(
+       void *data, struct wl_registry *wl_registry, uint32_t name,
+       const char *interface, uint32_t version) {
+     WindowDirectWayland *win = (WindowDirectWayland *)data;
+     if (strcmp(interface, zwp_drm_lease_manager_v1_interface.name) == 0) {
+       win->manager = (struct zwp_drm_lease_manager_v1 *)wl_registry_bind(
+           wl_registry, name, &zwp_drm_lease_manager_v1_interface, 1);
+     }
+   }
+ 
+   static void registry_handle_global_remove(
+       void *data, struct wl_registry *wl_registry, uint32_t name) {
+     // Who cares
+   }
+ 
+   const struct wl_registry_listener registry_listener = {
+     .global = registry_handle_global,
+     .global_remove = registry_handle_global_remove,
+   };
+ 
+  public:
+   explicit WindowDirectWayland(Settings *s) : Window(s) {
+     name = "direct-wayland";
+   }
+ 
+   ~WindowDirectWayland() {
+     if (manager)
+       zwp_drm_lease_manager_v1_destroy(manager);
+     if (wl_display)
+       wl_display_disconnect(wl_display);
+   }
+ 
+   int init() {
+     wl_display = wl_display_connect(NULL);
+     if (wl_display == NULL)
+       return -1;
+ 
+     struct wl_registry *wl_registry = wl_display_get_registry(wl_display);
+     wl_registry_add_listener(wl_registry, &registry_listener, this);
+     wl_display_roundtrip(wl_display);
+     if (manager == nullptr) {
+       vik_log_f("Wayland compositor does not support drm-lease-unstable-v1");
+       return -1;
+     }
+     zwp_drm_lease_manager_v1_add_listener(
+         manager, &lease_manager_listener, this);
+     wl_display_roundtrip(wl_display);
+     /* Again to get connector details */
+     wl_display_roundtrip(wl_display);
+ 
+     if (settings->list_screens_and_exit) {
+       int display_i = 0;
+       for (VikDisplay d : displays) {
+         vik_log_i("%d: %s %s",
+                   display_i,
+                   d.name.c_str(),
+                   d.description.c_str());
+         display_i++;
+       }
+       exit(0);
+     }
+ 
+     if (settings->display > (int) displays.size() - 1) {
+       vik_log_w("Requested display %d, but only %d displays are available.",
+                 settings->display, displays.size());
+ 
+       settings->display = 0;
+       VikDisplay *d = current_display();
+       vik_log_w("Selecting '%s' instead.", d->name.c_str());
+     }
+ 
+     if (settings->display < 0) {
+       settings->display = 0;
+       VikDisplay *d = current_display();
+       vik_log_w("Selecting '%s' first display.", d->name.c_str());
+     }
+ 
+     settings->size.first = 2160;
+     settings->size.second = 1200;
+     size_only_cb(settings->size.first, settings->size.second);
+ 
+     return 0;
+   }
+ 
+   VikDisplay* current_display() {
+     return &displays[settings->display];
+   }
+ 
+   void iterate() {
+     render_frame_cb();
+   }
+ 
+   VkDisplayModeKHR get_primary_display_mode (VkDisplayKHR display) {
+     uint32_t mode_count;
+     VkResult res =
+       vkGetDisplayModePropertiesKHR(swap_chain.physical_device, display,
+                                    &mode_count, nullptr);
+     vik_log_f_if(res != VK_SUCCESS,
+                  "Could not vkGetDisplayModePropertiesKHR: %s",
+                  Log::result_string(res).c_str());
+ 
+     vik_log_d("Found %d modes", mode_count);
+ 
+     VkDisplayModePropertiesKHR* mode_properties;
+     mode_properties = new VkDisplayModePropertiesKHR[mode_count];
+     res = vkGetDisplayModePropertiesKHR(swap_chain.physical_device, display,
+                                        &mode_count, mode_properties);
+     vik_log_f_if(res != VK_SUCCESS,
+                  "Could not vkGetDisplayModePropertiesKHR: %s",
+                  Log::result_string(res).c_str());
+ 
+     VkDisplayModePropertiesKHR props = mode_properties[0];
+ 
+     vik_log_d("found dispkay mode %d %d",
+       props.parameters.visibleRegion.width,
+       props.parameters.visibleRegion.height);
+ 
+     delete[] mode_properties;
+ 
+     return props.displayMode;
+   }
+ 
+   VkDisplayPlaneAlphaFlagBitsKHR
+   choose_alpha_mode (VkDisplayPlaneAlphaFlagsKHR flags) {
+     if (flags & VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR)
+       return VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR;
+     else if (flags & VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR)
+       return VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR;
+     else
+       return VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR;
+   }
+ 
+   void init_swap_chain(uint32_t width, uint32_t height) {
+     VikDisplay *d = current_display();
+ 
+     vik_log_i("Will use display: %s", d->name.c_str());
+ 
+     VkDisplayKHR display = acquire_wl_display(current_display());
+ 
+     // Get plane properties
+     uint32_t plane_property_count;
+     VkResult res =
+       vkGetPhysicalDeviceDisplayPlanePropertiesKHR(swap_chain.physical_device,
+                                                   &plane_property_count,
+                                                    nullptr);
+     vik_log_f_if(res != VK_SUCCESS,
+                  "Could not vkGetPhysicalDeviceDisplayPlanePropertiesKHR: %s",
+                  Log::result_string(res).c_str());
+ 
+     vik_log_i("Found %d plane properites.", plane_property_count);
+ 
+     VkDisplayPlanePropertiesKHR* plane_properties =
+       new VkDisplayPlanePropertiesKHR[plane_property_count];
+ 
+     res =
+       vkGetPhysicalDeviceDisplayPlanePropertiesKHR(swap_chain.physical_device,
+                                                   &plane_property_count,
+                                                    plane_properties);
+     vik_log_f_if(res != VK_SUCCESS,
+                  "Could not vkGetPhysicalDeviceDisplayPlanePropertiesKHR: %s",
+                  Log::result_string(res).c_str());
+ 
+     uint32_t plane_index = 0;
+ 
+     VkDisplayModeKHR display_mode = get_primary_display_mode (display);
+ 
+     VkDisplayPlaneCapabilitiesKHR plane_caps;
+     vkGetDisplayPlaneCapabilitiesKHR(swap_chain.physical_device, display_mode,
+                                      plane_index, &plane_caps);
+ 
+     VkDisplaySurfaceCreateInfoKHR surface_info = {
+       .sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR,
+       .flags = 0,
+       .displayMode = display_mode,
+       .planeIndex = plane_index,
+       .planeStackIndex = plane_properties[plane_index].currentStackIndex,
+       .transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
+       .globalAlpha = 1.0,
+       .alphaMode = choose_alpha_mode(plane_caps.supportedAlpha),
+       .imageExtent = {
+         .width = width,
+         .height = height
+       }
+     };
+ 
+     VkResult result = vkCreateDisplayPlaneSurfaceKHR(swap_chain.instance,
+                                                      &surface_info, nullptr,
+                                                      &swap_chain.surface);
+     vik_log_f_if(result !=VK_SUCCESS, "Failed to create surface!");
+ 
+     delete[] plane_properties;
+ 
+     swap_chain.set_settings(settings);
+     swap_chain.select_surface_format();
+     swap_chain.create(width, height);
+   }
+ 
+   SwapChain* get_swap_chain() {
+     return (SwapChain*) &swap_chain;
+   }
+ 
+   VkDisplayKHR acquire_wl_display(VikDisplay *dpy) {
+     VkWaylandLeaseConnectorEXT connectors[] = {
+       {
+         .pConnectorIn = dpy->connector,
+       }
+     };
+ 
+     PFN_vkAcquireWaylandDisplayEXT fun =
+       (PFN_vkAcquireWaylandDisplayEXT)
+         vkGetInstanceProcAddr(swap_chain.instance, "vkAcquireWaylandDisplayEXT");
+     vik_log_f_if(fun == nullptr,
+                  "Could not Get Device Proc Addr vkAcquireWaylandDisplayEXT.");
+ 
+     VkResult res = fun(swap_chain.physical_device,
+         wl_display, manager, 1, connectors);
+     vik_log_f_if(res != VK_SUCCESS,
+                  "Could not acquire Wayland display: %s",
+                  Log::result_string(res).c_str());
+     return connectors[0].displayOut;
+   }
+ 
+  public:
+   const std::vector<const char*> required_extensions() {
+     return {
+       VK_KHR_DISPLAY_EXTENSION_NAME,
+       VK_EXT_DIRECT_MODE_DISPLAY_EXTENSION_NAME,
+       VK_EXT_ACQUIRE_WL_DISPLAY_EXTENSION_NAME,
+     };
+   }
+ 
+   const std::vector<const char*> required_device_extensions() {
+     return {};
+   }
+ 
+   VkBool32 check_support(VkPhysicalDevice physical_device) {
+     return true;
+   }
+ 
+   void update_window_title(const std::string& title) {}
+ 
+ };
+ }  // namespace vik