Implements VK_KHR_display, VK_EXT_direct_mode_display and VK_EXT_acquire_xlib_display using DRM/KMS.
This uses X leases to provide the same API as nVidia, allowing an application to discover available display resources and acquire one from the X server using RandR leases. Because Linux DRM does not allow access to KMS resources through a render node, applications must use vkGetRandROutputDisplayEXT to access a suitable VkDisplayKHR object as vkGetPhysicalDeviceDisplayPropertiesKHR will not be able to see the required kernel objects. Contrarywise, nVidia masks off some RandR outputs from X (such as head-mounted-displays), those are *only* visible through vkGetPhysicalDeviceDisplayPropertiesKHR. Hence, an application wanting to work with both systems may need to use both mechanisms to locate the desired output device. v2: Rework VK_KEITHP_kms_display extension to just use a data structure. v3: Restructure to provide KMS access using the existing VK_EXT_acquire_xlib_display instead of creating a new extension. Rebase on more recent mesa master Signed-off-by: Keith Packard <kei...@keithp.com> --- configure.ac | 12 + src/amd/vulkan/Makefile.am | 17 + src/amd/vulkan/Makefile.sources | 3 + src/amd/vulkan/radv_extensions.py | 3 + src/amd/vulkan/radv_private.h | 1 + src/amd/vulkan/radv_wsi.c | 3 +- src/amd/vulkan/radv_wsi_display.c | 185 ++++ src/intel/Makefile.vulkan.am | 17 + src/intel/vulkan/anv_wsi.c | 3 +- src/vulkan/Makefile.am | 15 + src/vulkan/Makefile.sources | 4 + src/vulkan/wsi/wsi_common.c | 19 +- src/vulkan/wsi/wsi_common.h | 5 +- src/vulkan/wsi/wsi_common_display.c | 1782 +++++++++++++++++++++++++++++++++++ src/vulkan/wsi/wsi_common_display.h | 92 ++ src/vulkan/wsi/wsi_common_private.h | 10 + 16 files changed, 2166 insertions(+), 5 deletions(-) create mode 100644 src/amd/vulkan/radv_wsi_display.c create mode 100644 src/vulkan/wsi/wsi_common_display.c create mode 100644 src/vulkan/wsi/wsi_common_display.h diff --git a/configure.ac b/configure.ac index 79f275d3914..0666b9d2021 100644 --- a/configure.ac +++ b/configure.ac @@ -1494,6 +1494,10 @@ fi AC_SUBST([GL_LIB]) AC_SUBST([OSMESA_LIB]) +# Check for RandR leases +PKG_CHECK_MODULES([RANDRPROTO], [randrproto >= 1.6.0], + [have_xlease=yes], [have_xlease=no]) + # Check for libdrm PKG_CHECK_MODULES([LIBDRM], [libdrm >= $LIBDRM_REQUIRED], [have_libdrm=yes], [have_libdrm=no]) @@ -1533,6 +1537,7 @@ AM_CONDITIONAL(HAVE_APPLEDRI, test "x$enable_dri" = xyes -a "x$dri_platform" = x AM_CONDITIONAL(HAVE_LMSENSORS, test "x$enable_lmsensors" = xyes ) AM_CONDITIONAL(HAVE_GALLIUM_EXTRA_HUD, test "x$enable_gallium_extra_hud" = xyes ) AM_CONDITIONAL(HAVE_WINDOWSDRI, test "x$enable_dri" = xyes -a "x$dri_platform" = xwindows ) +AM_CONDITIONAL(HAVE_XLEASE, test "x$have_xlease" = xyes ) AC_ARG_ENABLE([shared-glapi], [AS_HELP_STRING([--enable-shared-glapi], @@ -1832,9 +1837,16 @@ if test x"$enable_dri3" = xyes; then PKG_CHECK_MODULES([XCB_DRI3], [$dri3_modules]) fi +if test x"$have_xlease" = xyes; then + DEFINES="$DEFINES -DHAVE_XLEASE" + randr_modules="x11-xcb xcb-randr" + PKG_CHECK_MODULES([XCB_RANDR], [$randr_modules]) +fi + AM_CONDITIONAL(HAVE_PLATFORM_X11, echo "$platforms" | grep -q 'x11') AM_CONDITIONAL(HAVE_PLATFORM_WAYLAND, echo "$platforms" | grep -q 'wayland') AM_CONDITIONAL(HAVE_PLATFORM_DRM, echo "$platforms" | grep -q 'drm') +AM_CONDITIONAL(HAVE_PLATFORM_DISPLAY, echo "$platforms" | grep -q 'drm') AM_CONDITIONAL(HAVE_PLATFORM_SURFACELESS, echo "$platforms" | grep -q 'surfaceless') AM_CONDITIONAL(HAVE_PLATFORM_ANDROID, echo "$platforms" | grep -q 'android') diff --git a/src/amd/vulkan/Makefile.am b/src/amd/vulkan/Makefile.am index 6b352aebf98..f80e041fbe7 100644 --- a/src/amd/vulkan/Makefile.am +++ b/src/amd/vulkan/Makefile.am @@ -76,6 +76,23 @@ VULKAN_LIB_DEPS = \ $(DLOPEN_LIBS) \ -lm +if HAVE_PLATFORM_DISPLAY +AM_CPPFLAGS += \ + -DVK_USE_PLATFORM_DISPLAY_KHR \ + -DVK_USE_PLATFORM_XLIB_XRANDR_EXT + +VULKAN_SOURCES += $(VULKAN_WSI_DISPLAY_FILES) + +if HAVE_PLATFORM_X11 +if HAVE_XLEASE +AM_CPPFLAGS += \ + $(XCB_RANDR_CFLAGS) +VULKAN_LIB_DEPS += $(XCB_RANDR_LIBS) +endif +endif + +endif + if HAVE_PLATFORM_X11 AM_CPPFLAGS += \ $(XCB_DRI3_CFLAGS) \ diff --git a/src/amd/vulkan/Makefile.sources b/src/amd/vulkan/Makefile.sources index c9d172c3b1b..4321f7ddea3 100644 --- a/src/amd/vulkan/Makefile.sources +++ b/src/amd/vulkan/Makefile.sources @@ -75,6 +75,9 @@ VULKAN_WSI_WAYLAND_FILES := \ VULKAN_WSI_X11_FILES := \ radv_wsi_x11.c +VULKAN_WSI_DISPLAY_FILES := \ + radv_wsi_display.c + VULKAN_GENERATED_FILES := \ radv_entrypoints.c \ radv_entrypoints.h \ diff --git a/src/amd/vulkan/radv_extensions.py b/src/amd/vulkan/radv_extensions.py index 9af941fab35..fc27ff8734c 100644 --- a/src/amd/vulkan/radv_extensions.py +++ b/src/amd/vulkan/radv_extensions.py @@ -80,6 +80,9 @@ EXTENSIONS = [ Extension('VK_KHR_wayland_surface', 6, 'VK_USE_PLATFORM_WAYLAND_KHR'), Extension('VK_KHR_xcb_surface', 6, 'VK_USE_PLATFORM_XCB_KHR'), Extension('VK_KHR_xlib_surface', 6, 'VK_USE_PLATFORM_XLIB_KHR'), + Extension('VK_KHR_display', 1, 'VK_USE_PLATFORM_DISPLAY_KHR'), + Extension('VK_EXT_direct_mode_display', 1, 'VK_USE_PLATFORM_DISPLAY_KHR'), + Extension('VK_EXT_acquire_xlib_display', 1, 'VK_USE_PLATFORM_XLIB_XRANDR_EXT'), Extension('VK_KHX_multiview', 1, True), Extension('VK_EXT_external_memory_dma_buf', 1, True), Extension('VK_EXT_global_priority', 1, 'device->rad_info.has_ctx_priority'), diff --git a/src/amd/vulkan/radv_private.h b/src/amd/vulkan/radv_private.h index 244ab8967b6..bf0643a6a5e 100644 --- a/src/amd/vulkan/radv_private.h +++ b/src/amd/vulkan/radv_private.h @@ -73,6 +73,7 @@ typedef uint32_t xcb_window_t; #include "radv_entrypoints.h" #include "wsi_common.h" +#include "wsi_common_display.h" #define ATI_VENDOR_ID 0x1002 diff --git a/src/amd/vulkan/radv_wsi.c b/src/amd/vulkan/radv_wsi.c index e016e837102..5ec872a63d0 100644 --- a/src/amd/vulkan/radv_wsi.c +++ b/src/amd/vulkan/radv_wsi.c @@ -41,7 +41,8 @@ radv_init_wsi(struct radv_physical_device *physical_device) return wsi_device_init(&physical_device->wsi_device, radv_physical_device_to_handle(physical_device), radv_wsi_proc_addr, - &physical_device->instance->alloc); + &physical_device->instance->alloc, + physical_device->local_fd); } void diff --git a/src/amd/vulkan/radv_wsi_display.c b/src/amd/vulkan/radv_wsi_display.c new file mode 100644 index 00000000000..6bfce5f37ed --- /dev/null +++ b/src/amd/vulkan/radv_wsi_display.c @@ -0,0 +1,185 @@ +/* + * Copyright © 2017 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include "radv_private.h" +#include "radv_cs.h" +#include "util/disk_cache.h" +#include "util/strtod.h" +#include "vk_util.h" +#include <xf86drm.h> +#include <xf86drmMode.h> +#include <amdgpu.h> +#include <amdgpu_drm.h> +#include "winsys/amdgpu/radv_amdgpu_winsys_public.h" +#include "ac_llvm_util.h" +#include "vk_format.h" +#include "sid.h" +#include "util/debug.h" +#include "wsi_common_display.h" + +#define MM_PER_PIXEL (1.0/96.0 * 25.4) + +VkResult +radv_GetPhysicalDeviceDisplayPropertiesKHR(VkPhysicalDevice physical_device, + uint32_t *property_count, + VkDisplayPropertiesKHR *properties) +{ + RADV_FROM_HANDLE(radv_physical_device, pdevice, physical_device); + + return wsi_display_get_physical_device_display_properties(physical_device, + &pdevice->wsi_device, + property_count, + properties); +} + +VkResult +radv_GetPhysicalDeviceDisplayPlanePropertiesKHR(VkPhysicalDevice physical_device, + uint32_t *property_count, + VkDisplayPlanePropertiesKHR *properties) +{ + RADV_FROM_HANDLE(radv_physical_device, pdevice, physical_device); + + return wsi_display_get_physical_device_display_plane_properties(physical_device, + &pdevice->wsi_device, + property_count, + properties); +} + +VkResult +radv_GetDisplayPlaneSupportedDisplaysKHR(VkPhysicalDevice physical_device, + uint32_t plane_index, + uint32_t *display_count, + VkDisplayKHR *displays) +{ + RADV_FROM_HANDLE(radv_physical_device, pdevice, physical_device); + + return wsi_display_get_display_plane_supported_displays(physical_device, + &pdevice->wsi_device, + plane_index, + display_count, + displays); +} + + +VkResult +radv_GetDisplayModePropertiesKHR(VkPhysicalDevice physical_device, + VkDisplayKHR display, + uint32_t *property_count, + VkDisplayModePropertiesKHR *properties) +{ + RADV_FROM_HANDLE(radv_physical_device, pdevice, physical_device); + + return wsi_display_get_display_mode_properties(physical_device, + &pdevice->wsi_device, + display, + property_count, + properties); +} + +VkResult +radv_CreateDisplayModeKHR(VkPhysicalDevice physical_device, + VkDisplayKHR display, + const VkDisplayModeCreateInfoKHR *create_info, + const VkAllocationCallbacks *allocator, + VkDisplayModeKHR *mode) +{ + return VK_ERROR_INITIALIZATION_FAILED; +} + + +VkResult +radv_GetDisplayPlaneCapabilitiesKHR(VkPhysicalDevice physical_device, + VkDisplayModeKHR mode_khr, + uint32_t plane_index, + VkDisplayPlaneCapabilitiesKHR *capabilities) +{ + RADV_FROM_HANDLE(radv_physical_device, pdevice, physical_device); + + return wsi_get_display_plane_capabilities(physical_device, + &pdevice->wsi_device, + mode_khr, + plane_index, + capabilities); +} + +VkResult +radv_ReleaseDisplayEXT(VkPhysicalDevice physical_device, + VkDisplayKHR display) +{ + RADV_FROM_HANDLE(radv_physical_device, pdevice, physical_device); + + return wsi_release_display(physical_device, + &pdevice->wsi_device, + display); +} + +#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT +VkResult +radv_AcquireXlibDisplayEXT(VkPhysicalDevice physical_device, + Display *dpy, + VkDisplayKHR display) +{ + RADV_FROM_HANDLE(radv_physical_device, pdevice, physical_device); + + return wsi_acquire_xlib_display(physical_device, + &pdevice->wsi_device, + dpy, + display); +} + +VkResult +radv_GetRandROutputDisplayEXT(VkPhysicalDevice physical_device, + Display *dpy, + RROutput output, + VkDisplayKHR *display) +{ + RADV_FROM_HANDLE(radv_physical_device, pdevice, physical_device); + + return wsi_get_randr_output_display(physical_device, + &pdevice->wsi_device, + dpy, + output, + display); +} + +#endif /* VK_USE_PLATFORM_XLIB_XRANDR_EXT */ + +VkResult +radv_CreateDisplayPlaneSurfaceKHR(VkInstance _instance, + const VkDisplaySurfaceCreateInfoKHR *create_info, + const VkAllocationCallbacks *allocator, + VkSurfaceKHR *surface) +{ + RADV_FROM_HANDLE(radv_instance, instance, _instance); + const VkAllocationCallbacks *alloc; + + if (allocator) + alloc = allocator; + else + alloc = &instance->alloc; + + return wsi_create_display_surface(_instance, alloc, create_info, surface); +} diff --git a/src/intel/Makefile.vulkan.am b/src/intel/Makefile.vulkan.am index 811faab556e..b2559327de2 100644 --- a/src/intel/Makefile.vulkan.am +++ b/src/intel/Makefile.vulkan.am @@ -175,6 +175,23 @@ VULKAN_SOURCES += $(VULKAN_WSI_WAYLAND_FILES) VULKAN_LIB_DEPS += $(WAYLAND_CLIENT_LIBS) endif +if HAVE_PLATFORM_DISPLAY +VULKAN_CPPFLAGS += \ + -DVK_USE_PLATFORM_DISPLAY_KHR \ + -DVK_USE_PLATFORM_XLIB_XRANDR_EXT + +VULKAN_SOURCES += $(VULKAN_WSI_DISPLAY_FILES) + +if HAVE_PLATFORM_X11 +if HAVE_XLEASE +VULKAN_CPPFLAGS += \ + $(XCB_RANDR_CFLAGS) +VULKAN_LIB_DEPS += $(XCB_RANDR_LIBS) +endif +endif + +endif + noinst_LTLIBRARIES += vulkan/libvulkan_common.la vulkan_libvulkan_common_la_SOURCES = $(VULKAN_SOURCES) vulkan_libvulkan_common_la_CFLAGS = $(VULKAN_CFLAGS) diff --git a/src/intel/vulkan/anv_wsi.c b/src/intel/vulkan/anv_wsi.c index 6082c3dd093..f86d83589ea 100644 --- a/src/intel/vulkan/anv_wsi.c +++ b/src/intel/vulkan/anv_wsi.c @@ -39,7 +39,8 @@ anv_init_wsi(struct anv_physical_device *physical_device) return wsi_device_init(&physical_device->wsi_device, anv_physical_device_to_handle(physical_device), anv_wsi_proc_addr, - &physical_device->instance->alloc); + &physical_device->instance->alloc, + physical_device->local_fd); } void diff --git a/src/vulkan/Makefile.am b/src/vulkan/Makefile.am index 8766952eafb..4a18e997005 100644 --- a/src/vulkan/Makefile.am +++ b/src/vulkan/Makefile.am @@ -54,6 +54,21 @@ AM_CPPFLAGS += \ VULKAN_WSI_SOURCES += $(VULKAN_WSI_X11_FILES) endif +if HAVE_PLATFORM_DISPLAY +AM_CPPFLAGS += \ + -DVK_USE_PLATFORM_DISPLAY_KHR + +VULKAN_WSI_SOURCES += $(VULKAN_WSI_DISPLAY_FILES) + +if HAVE_PLATFORM_X11 +if HAVE_XLEASE +AM_CPPFLAGS += \ + -DVK_USE_PLATFORM_XLIB_XRANDR_EXT +endif +endif + +endif + BUILT_SOURCES += $(VULKAN_WSI_WAYLAND_GENERATED_FILES) CLEANFILES = $(BUILT_SOURCES) diff --git a/src/vulkan/Makefile.sources b/src/vulkan/Makefile.sources index 6e9a09ce78b..2b509d811fd 100644 --- a/src/vulkan/Makefile.sources +++ b/src/vulkan/Makefile.sources @@ -17,6 +17,10 @@ VULKAN_WSI_X11_FILES := \ wsi/wsi_common_x11.c \ wsi/wsi_common_x11.h +VULKAN_WSI_DISPLAY_FILES := \ + wsi/wsi_common_display.c \ + wsi/wsi_common_display.h + VULKAN_UTIL_FILES := \ util/vk_alloc.h \ util/vk_util.c \ diff --git a/src/vulkan/wsi/wsi_common.c b/src/vulkan/wsi/wsi_common.c index 2de5f154c8a..99f4c607666 100644 --- a/src/vulkan/wsi/wsi_common.c +++ b/src/vulkan/wsi/wsi_common.c @@ -29,7 +29,8 @@ VkResult wsi_device_init(struct wsi_device *wsi, VkPhysicalDevice pdevice, WSI_FN_GetPhysicalDeviceProcAddr proc_addr, - const VkAllocationCallbacks *alloc) + const VkAllocationCallbacks *alloc, + int device_fd) { VkResult result; @@ -89,6 +90,19 @@ wsi_device_init(struct wsi_device *wsi, } #endif +#ifdef VK_USE_PLATFORM_DISPLAY_KHR + result = wsi_display_init_wsi(wsi, alloc, pdevice, device_fd); + if (result != VK_SUCCESS) { +#ifdef VK_USE_PLATFORM_WAYLAND_KHR + wsi_wayland_finish_wsi(wsi, alloc); +#endif +#ifdef VK_USE_PLATFORM_XCB_KHR + wsi_x11_finish_wsi(wsi, alloc); +#endif + return result; + } +#endif + return VK_SUCCESS; } @@ -96,6 +110,9 @@ void wsi_device_finish(struct wsi_device *wsi, const VkAllocationCallbacks *alloc) { +#ifdef VK_USE_PLATFORM_DISPLAY_KHR + wsi_display_finish_wsi(wsi, alloc); +#endif #ifdef VK_USE_PLATFORM_WAYLAND_KHR wsi_wl_finish_wsi(wsi, alloc); #endif diff --git a/src/vulkan/wsi/wsi_common.h b/src/vulkan/wsi/wsi_common.h index 3e0d3be1c24..1cb6aaebca0 100644 --- a/src/vulkan/wsi/wsi_common.h +++ b/src/vulkan/wsi/wsi_common.h @@ -50,7 +50,7 @@ struct wsi_memory_allocate_info { struct wsi_interface; -#define VK_ICD_WSI_PLATFORM_MAX 5 +#define VK_ICD_WSI_PLATFORM_MAX 6 struct wsi_device { VkPhysicalDeviceMemoryProperties memory_props; @@ -93,7 +93,8 @@ VkResult wsi_device_init(struct wsi_device *wsi, VkPhysicalDevice pdevice, WSI_FN_GetPhysicalDeviceProcAddr proc_addr, - const VkAllocationCallbacks *alloc); + const VkAllocationCallbacks *alloc, + int device_fd); void wsi_device_finish(struct wsi_device *wsi, diff --git a/src/vulkan/wsi/wsi_common_display.c b/src/vulkan/wsi/wsi_common_display.c new file mode 100644 index 00000000000..05b019cf957 --- /dev/null +++ b/src/vulkan/wsi/wsi_common_display.c @@ -0,0 +1,1782 @@ +/* + * Copyright © 2017 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "util/macros.h" +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <poll.h> +#include <stdbool.h> +#include <math.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT +#include <xcb/randr.h> +#include <X11/Xlib-xcb.h> +#endif +#include "util/hash_table.h" +#include "util/list.h" + +#include "vk_util.h" +#include "wsi_common_private.h" +#include "wsi_common_display.h" +#include "wsi_common_queue.h" + +#if 0 +#define wsi_display_debug(...) fprintf(stderr, __VA_ARGS__) +#define wsi_display_debug_code(...) __VA_ARGS__ +#else +#define wsi_display_debug(...) +#define wsi_display_debug_code(...) +#endif + +/* These have lifetime equal to the instance, so they effectively + * never go away. This means we must keep track of them separately + * from all other resources. + */ +typedef struct wsi_display_mode { + struct list_head list; + struct wsi_display_connector *connector; + bool valid; /* was found in most recent poll */ + bool preferred; + uint32_t clock; + uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew; + uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan; + uint32_t flags; +} wsi_display_mode; + +typedef struct wsi_display_connector { + struct list_head list; + struct wsi_display *wsi; + uint32_t id; + uint32_t crtc_id; + char *name; + bool connected; + bool active; + wsi_display_mode *current_mode; + drmModeModeInfo current_drm_mode; +#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT + xcb_randr_output_t output; +#endif +} wsi_display_connector; + +struct wsi_display { + struct wsi_interface base; + + const VkAllocationCallbacks *alloc; + VkPhysicalDevice physical_device; + + const struct wsi_callbacks *cbs; + + int master_fd; + int render_fd; + + pthread_mutex_t wait_mutex; + pthread_cond_t wait_cond; + pthread_t wait_thread; + + struct list_head connectors; + + struct list_head display_modes; +}; + +enum wsi_image_state { + wsi_image_idle = 0, + wsi_image_drawing = 1, + wsi_image_flipping = 2, + wsi_image_displaying = 3 +}; + +struct wsi_display_image { + struct wsi_image base; + struct wsi_display_swapchain *chain; + enum wsi_image_state state; + unsigned int frame; + unsigned int sec; + unsigned int usec; + uint32_t fb_id; +}; + +struct wsi_display_swapchain { + struct wsi_swapchain base; + struct wsi_display *wsi; + VkIcdSurfaceDisplay *surface; + struct wsi_display_image images[0]; +}; + +ICD_DEFINE_NONDISP_HANDLE_CASTS(wsi_display_mode, VkDisplayModeKHR) +ICD_DEFINE_NONDISP_HANDLE_CASTS(wsi_display_connector, VkDisplayKHR) + +static bool +wsi_display_mode_matches_drm(wsi_display_mode *wsi, + drmModeModeInfoPtr drm) +{ + return wsi->clock == drm->clock && + wsi->hdisplay == drm->hdisplay && + wsi->hsync_start == drm->hsync_start && + wsi->hsync_end == drm->hsync_end && + wsi->htotal == drm->htotal && + wsi->hskew == drm->hskew && + wsi->vdisplay == drm->vdisplay && + wsi->vsync_start == drm->vsync_start && + wsi->vsync_end == drm->vsync_end && + wsi->vtotal == drm->vtotal && + wsi->vscan == drm->vscan && + wsi->flags == drm->flags; +} + +static bool +wsi_display_mode_matches_x(struct wsi_display_mode *wsi, + xcb_randr_mode_info_t *xcb) +{ + return wsi->clock == xcb->dot_clock && + wsi->hdisplay == xcb->width && + wsi->hsync_start == xcb->hsync_start && + wsi->hsync_end == xcb->hsync_end && + wsi->htotal == xcb->htotal && + wsi->hskew == xcb->hskew && + wsi->vdisplay == xcb->height && + wsi->vsync_start == xcb->vsync_start && + wsi->vsync_end == xcb->vsync_end && + wsi->vtotal == xcb->vtotal && + wsi->flags == xcb->mode_flags; +} + +static double +wsi_display_mode_refresh(struct wsi_display_mode *wsi) +{ + return (double) wsi->clock / ((double) wsi->htotal * (double) wsi->vtotal * (double) (wsi->vscan + 1)); +} + +static uint64_t wsi_get_current_realtime(void) +{ + struct timespec tv; + + clock_gettime(CLOCK_REALTIME, &tv); + return tv.tv_nsec + tv.tv_sec*1000000000ull; +} + +static int +wsi_display_wait_for_event(struct wsi_display *wsi, + uint64_t timeout_ns); + +static struct wsi_display_mode * +wsi_display_find_drm_mode(struct wsi_device *wsi_device, + struct wsi_display_connector *connector, + drmModeModeInfoPtr mode) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_mode *display_mode; + + LIST_FOR_EACH_ENTRY(display_mode, &wsi->display_modes, list) { + if (display_mode->connector == connector && + wsi_display_mode_matches_drm(display_mode, mode)) + return display_mode; + } + return NULL; +} + +static struct wsi_display_mode * +wsi_display_find_x_mode(struct wsi_device *wsi_device, + struct wsi_display_connector *connector, + xcb_randr_mode_info_t *mode) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_mode *display_mode; + + LIST_FOR_EACH_ENTRY(display_mode, &wsi->display_modes, list) { + if (display_mode->connector == connector && + wsi_display_mode_matches_x(display_mode, mode)) + return display_mode; + } + return NULL; +} + +static void +wsi_display_invalidate_connector_modes(struct wsi_device *wsi_device, + struct wsi_display_connector *connector) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_mode *display_mode; + + LIST_FOR_EACH_ENTRY(display_mode, &wsi->display_modes, list) + if (display_mode->connector == connector) + display_mode->valid = false; +} + +static VkResult +wsi_display_register_drm_mode(struct wsi_device *wsi_device, + struct wsi_display_connector *connector, + drmModeModeInfoPtr drm_mode) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_mode *display_mode; + + display_mode = wsi_display_find_drm_mode(wsi_device, connector, drm_mode); + + if (display_mode) { + display_mode->valid = true; + return VK_SUCCESS; + } + + display_mode = vk_alloc(wsi->alloc, sizeof (struct wsi_display_mode), 8, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); + if (!display_mode) + return VK_ERROR_OUT_OF_HOST_MEMORY; + + display_mode->connector = connector; + display_mode->valid = true; + display_mode->preferred = (drm_mode->type & DRM_MODE_TYPE_PREFERRED) != 0; + display_mode->clock = drm_mode->clock; + display_mode->hdisplay = drm_mode->hdisplay; + display_mode->hsync_start = drm_mode->hsync_start; + display_mode->hsync_end = drm_mode->hsync_end; + display_mode->htotal = drm_mode->htotal; + display_mode->hskew = drm_mode->hskew; + display_mode->vdisplay = drm_mode->vdisplay; + display_mode->vsync_start = drm_mode->vsync_start; + display_mode->vsync_end = drm_mode->vsync_end; + display_mode->vtotal = drm_mode->vtotal; + display_mode->vscan = drm_mode->vscan; + display_mode->flags = drm_mode->flags; + + LIST_ADDTAIL(&display_mode->list, &wsi->display_modes); + return VK_SUCCESS; +} + +static VkResult +wsi_display_register_x_mode(struct wsi_device *wsi_device, + struct wsi_display_connector *connector, + xcb_randr_mode_info_t *x_mode, + bool preferred) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_mode *display_mode; + + display_mode = wsi_display_find_x_mode(wsi_device, connector, x_mode); + + if (display_mode) { + display_mode->valid = true; + return VK_SUCCESS; + } + + display_mode = vk_alloc(wsi->alloc, sizeof (struct wsi_display_mode), 8, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); + if (!display_mode) + return VK_ERROR_OUT_OF_HOST_MEMORY; + + display_mode->connector = connector; + display_mode->valid = true; + display_mode->preferred = preferred; + display_mode->clock = x_mode->dot_clock / 1000; + display_mode->hdisplay = x_mode->width; + display_mode->hsync_start = x_mode->hsync_start; + display_mode->hsync_end = x_mode->hsync_end; + display_mode->htotal = x_mode->htotal; + display_mode->hskew = x_mode->hskew; + display_mode->vdisplay = x_mode->height; + display_mode->vsync_start = x_mode->vsync_start; + display_mode->vsync_end = x_mode->vsync_end; + display_mode->vtotal = x_mode->vtotal; + display_mode->vscan = 0; + if (x_mode->mode_flags & XCB_RANDR_MODE_FLAG_DOUBLE_SCAN) + display_mode->vscan = 1; + display_mode->flags = x_mode->mode_flags; + + LIST_ADDTAIL(&display_mode->list, &wsi->display_modes); + return VK_SUCCESS; +} + +/* + * Update our information about a specific connector + */ + +static struct wsi_display_connector * +wsi_display_find_connector(struct wsi_device *wsi_device, + uint32_t connector_id) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_connector *connector; + + connector = NULL; + LIST_FOR_EACH_ENTRY(connector, &wsi->connectors, list) { + if (connector->id == connector_id) + return connector; + } + + return NULL; +} + +static struct wsi_display_connector * +wsi_display_alloc_connector(struct wsi_display *wsi, + uint32_t connector_id) +{ + struct wsi_display_connector *connector; + + connector = vk_alloc(wsi->alloc, sizeof (struct wsi_display_connector), 8, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); + memset(connector, '\0', sizeof (*connector)); + connector->id = connector_id; + connector->wsi = wsi; + connector->active = false; + /* XXX use EDID name */ + connector->name = "monitor"; + return connector; +} + +static struct wsi_display_connector * +wsi_display_get_connector(struct wsi_device *wsi_device, + uint32_t connector_id) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_connector *connector; + drmModeConnectorPtr drm_connector; + VkResult result; + int m; + + if (wsi->master_fd < 0) + return NULL; + + drm_connector = drmModeGetConnector(wsi->master_fd, connector_id); + if (!drm_connector) + return NULL; + + connector = wsi_display_find_connector(wsi_device, connector_id); + + if (!connector) { + connector = wsi_display_alloc_connector(wsi, connector_id); + if (!connector) { + drmModeFreeConnector(drm_connector); + return NULL; + } + LIST_ADDTAIL(&connector->list, &wsi->connectors); + } + + connector->connected = drm_connector->connection != DRM_MODE_DISCONNECTED; + + /* Mark all connector modes as invalid */ + wsi_display_invalidate_connector_modes(wsi_device, connector); + + /* + * List current modes, adding new ones and marking existing ones as + * valid + */ + for (m = 0; m < drm_connector->count_modes; m++) { + result = wsi_display_register_drm_mode(wsi_device, + connector, + &drm_connector->modes[m]); + if (result != VK_SUCCESS) { + drmModeFreeConnector(drm_connector); + return NULL; + } + } + + drmModeFreeConnector(drm_connector); + + return connector; +} + +#define MM_PER_PIXEL (1.0/96.0 * 25.4) + +static void +wsi_display_fill_in_display_properties(struct wsi_device *wsi_device, + struct wsi_display_connector *connector, + VkDisplayPropertiesKHR *properties) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_mode *display_mode, *preferred_mode = NULL;; + + properties->display = wsi_display_connector_to_handle(connector); + properties->displayName = connector->name; + + /* Find the preferred mode and assume that's the physical resolution */ + + LIST_FOR_EACH_ENTRY(display_mode, &wsi->display_modes, list) { + if (display_mode->valid && display_mode->connector == connector && display_mode->preferred) { + preferred_mode = display_mode; + break; + } + } + + if (preferred_mode) { + properties->physicalResolution.width = preferred_mode->hdisplay; + properties->physicalResolution.height = preferred_mode->vdisplay; + } else { + properties->physicalResolution.width = 1024; + properties->physicalResolution.height = 768; + } + + /* Make up physical size based on 96dpi */ + properties->physicalDimensions.width = floor(properties->physicalResolution.width * MM_PER_PIXEL + 0.5); + properties->physicalDimensions.height = floor(properties->physicalResolution.height * MM_PER_PIXEL + 0.5); + + properties->supportedTransforms = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + properties->persistentContent = 0; +} + +/* + * Implement vkGetPhysicalDeviceDisplayPropertiesKHR (VK_KHR_display) + */ +VkResult +wsi_display_get_physical_device_display_properties(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + uint32_t *property_count, + VkDisplayPropertiesKHR *properties) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_connector *connector; + int c; + uint32_t connected; + uint32_t property_count_requested = *property_count; + drmModeResPtr mode_res; + + if (wsi->master_fd < 0) + return VK_ERROR_INITIALIZATION_FAILED; + + mode_res = drmModeGetResources(wsi->master_fd); + + if (!mode_res) + return VK_ERROR_INITIALIZATION_FAILED; + + connected = 0; + + /* Get current information */ + for (c = 0; c < mode_res->count_connectors; c++) { + connector = wsi_display_get_connector(wsi_device, mode_res->connectors[c]); + + if (!connector) { + drmModeFreeResources(mode_res); + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + if (connector->connected) + connected++; + } + + /* Fill in property information if requested */ + if (properties != NULL) { + connected = 0; + + for (c = 0; c < mode_res->count_connectors; c++) { + connector = wsi_display_find_connector(wsi_device, mode_res->connectors[c]); + + if (connector && connector->connected) { + if (connected < property_count_requested) { + wsi_display_fill_in_display_properties(wsi_device, + connector, + &properties[connected]); + } + connected++; + } + } + } + + drmModeFreeResources(mode_res); + + *property_count = connected; + + if (connected > property_count_requested && properties != NULL) + return VK_INCOMPLETE; + + return VK_SUCCESS; +} + +/* + * Implement vkGetPhysicalDeviceDisplayPlanePropertiesKHR (VK_KHR_display + */ +VkResult +wsi_display_get_physical_device_display_plane_properties(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + uint32_t *property_count, + VkDisplayPlanePropertiesKHR *properties) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_connector *connector; + uint32_t property_count_requested = *property_count; + int c; + + if (!properties) + property_count_requested = 0; + + c = 0; + LIST_FOR_EACH_ENTRY(connector, &wsi->connectors, list) { + if (c < property_count_requested) { + if (connector && connector->active) { + properties[c].currentDisplay = wsi_display_connector_to_handle(connector); + properties[c].currentStackIndex = c; + } else { + properties[c].currentDisplay = NULL; + properties[c].currentStackIndex = 0; + } + } + c++; + } + + *property_count = c; + + if (c > property_count_requested && properties != NULL) + return VK_INCOMPLETE; + + return VK_SUCCESS; +} + +/* + * Implement vkGetDisplayPlaneSupportedDisplaysKHR (VK_KHR_display) + */ + +VkResult +wsi_display_get_display_plane_supported_displays(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + uint32_t plane_index, + uint32_t *display_count, + VkDisplayKHR *displays) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_connector *connector; + int c; + + + if (displays == NULL) { + *display_count = 1; + return VK_SUCCESS; + } + + if (*display_count < 1) + return VK_INCOMPLETE; + + c = 0; + LIST_FOR_EACH_ENTRY(connector, &wsi->connectors, list) { + if (c == plane_index) { + *displays = wsi_display_connector_to_handle(connector); + *display_count = 1; + return VK_SUCCESS; + } + c++; + } + + *displays = 0; + *display_count = 0; + + return VK_SUCCESS; +} + +/* + * Implement vkGetDisplayModePropertiesKHR (VK_KHR_display) + */ + +VkResult +wsi_display_get_display_mode_properties(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + VkDisplayKHR display, + uint32_t *property_count, + VkDisplayModePropertiesKHR *properties) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_connector *connector = wsi_display_connector_from_handle(display); + int i; + struct wsi_display_mode *display_mode; + uint32_t property_count_requested = *property_count; + + i = 0; + + if (properties == NULL) + property_count_requested = 0; + + LIST_FOR_EACH_ENTRY(display_mode, &wsi->display_modes, list) { + if (display_mode->valid && display_mode->connector == connector) { + if (i < property_count_requested) { + properties[i].displayMode = wsi_display_mode_to_handle(display_mode); + properties[i].parameters.visibleRegion.width = display_mode->hdisplay; + properties[i].parameters.visibleRegion.height = display_mode->vdisplay; + properties[i].parameters.refreshRate = (uint32_t) (wsi_display_mode_refresh(display_mode) * 1000 + 0.5); + } + i++; + } + } + + *property_count = i; + + if (i > property_count_requested && properties != NULL) + return VK_INCOMPLETE; + + return VK_SUCCESS; + +} + +/* + * Implement vkGetDisplayPlaneCapabilities + */ +VkResult +wsi_get_display_plane_capabilities(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + VkDisplayModeKHR mode_khr, + uint32_t plane_index, + VkDisplayPlaneCapabilitiesKHR *capabilities) +{ + struct wsi_display_mode *mode = wsi_display_mode_from_handle(mode_khr); + + /* XXX use actual values */ + capabilities->supportedAlpha = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR; + capabilities->minSrcPosition.x = 0; + capabilities->minSrcPosition.y = 0; + capabilities->maxSrcPosition.x = 0; + capabilities->maxSrcPosition.y = 0; + capabilities->minSrcExtent.width = mode->hdisplay; + capabilities->minSrcExtent.height = mode->vdisplay; + capabilities->maxSrcExtent.width = mode->hdisplay; + capabilities->maxSrcExtent.height = mode->vdisplay; + capabilities->minDstPosition.x = 0; + capabilities->minDstPosition.y = 0; + capabilities->maxDstPosition.x = 0; + capabilities->maxDstPosition.y = 0; + capabilities->minDstExtent.width = mode->hdisplay; + capabilities->minDstExtent.height = mode->vdisplay; + capabilities->maxDstExtent.width = mode->hdisplay; + capabilities->maxDstExtent.height = mode->vdisplay; + return VK_SUCCESS; +} + +/* + * Implement vkReleaseDisplay + */ +VkResult +wsi_release_display(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + VkDisplayKHR display) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + + pthread_mutex_lock(&wsi->wait_mutex); + if (wsi->wait_thread) { + pthread_cancel(wsi->wait_thread); + pthread_join(wsi->wait_thread, NULL); + } + pthread_mutex_unlock(&wsi->wait_mutex); + pthread_mutex_destroy(&wsi->wait_mutex); + pthread_cond_destroy(&wsi->wait_cond); + + if (wsi->master_fd >= 0) + close(wsi->master_fd); + + return VK_SUCCESS; +} + +#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT +static struct wsi_display_connector * +wsi_display_find_output(struct wsi_device *wsi_device, + RROutput output) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_connector *connector; + + connector = NULL; + LIST_FOR_EACH_ENTRY(connector, &wsi->connectors, list) { + if (connector->output == output) + return connector; + } + + return NULL; +} + +/* + * Given a RandR output, find the associated kernel connector_id by + * looking at the CONNECTOR_ID property provided by the X server + */ + +static uint32_t +wsi_display_output_to_connector_id(xcb_connection_t *connection, + xcb_atom_t *connector_id_atom_p, + RROutput output) +{ + uint32_t connector_id = 0; + xcb_atom_t connector_id_atom = *connector_id_atom_p; + + if (connector_id_atom == 0) { + /* Go dig out the CONNECTOR_ID property */ + xcb_intern_atom_cookie_t ia_c = xcb_intern_atom(connection, + true, + 12, + "CONNECTOR_ID"); + xcb_intern_atom_reply_t *ia_r = xcb_intern_atom_reply(connection, + ia_c, + NULL); + if (ia_r) { + *connector_id_atom_p = connector_id_atom = ia_r->atom; + free(ia_r); + } + } + + /* If there's an CONNECTOR_ID atom in the server, then there may be a CONNECTOR_ID property. Otherwise, + * there will not be and we don't even need to bother. + */ + if (connector_id_atom) { + + xcb_randr_query_version_cookie_t qv_c = xcb_randr_query_version(connection, 1, 6); + xcb_randr_get_output_property_cookie_t gop_c = xcb_randr_get_output_property(connection, + output, + connector_id_atom, + 0, + 0, + 0xffffffffUL, + 0, + 0); + xcb_randr_query_version_reply_t *qv_r = xcb_randr_query_version_reply(connection, qv_c, NULL); + free(qv_r); + xcb_randr_get_output_property_reply_t *gop_r = xcb_randr_get_output_property_reply(connection, + gop_c, + NULL); + if (gop_r) { + if (gop_r->num_items == 1 && gop_r->format == 32) + memcpy(&connector_id, xcb_randr_get_output_property_data(gop_r), 4); + free(gop_r); + } + } + return connector_id; +} + +static bool +wsi_display_check_randr_version(xcb_connection_t *connection) +{ + xcb_randr_query_version_cookie_t qv_c = xcb_randr_query_version(connection, 1, 6); + xcb_randr_query_version_reply_t *qv_r = xcb_randr_query_version_reply(connection, qv_c, NULL); + bool ret = false; + + if (!qv_r) + return false; + + /* Check for version 1.6 or newer */ + ret = qv_r->major_version > 1 || (qv_r->major_version == 1 && qv_r->minor_version >= 6); + + free(qv_r); + return ret; +} + +/* + * Given a kernel connector id, find the associated RandR output using the + * CONNECTOR_ID property + */ + +static xcb_randr_output_t +wsi_display_connector_id_to_output(xcb_connection_t *connection, + uint32_t connector_id) +{ + if (!wsi_display_check_randr_version(connection)) + return 0; + + const xcb_setup_t *setup = xcb_get_setup(connection); + + xcb_atom_t connector_id_atom = 0; + xcb_randr_output_t output = 0; + + /* Search all of the screens for the provided output */ + xcb_screen_iterator_t iter; + for (iter = xcb_setup_roots_iterator(setup); output == 0 && iter.rem; xcb_screen_next(&iter)) { + + xcb_randr_get_screen_resources_cookie_t gsr_c = xcb_randr_get_screen_resources(connection, iter.data->root); + xcb_randr_get_screen_resources_reply_t *gsr_r = xcb_randr_get_screen_resources_reply(connection, gsr_c, NULL); + + if (!gsr_r) + return 0; + + xcb_randr_output_t *ro = xcb_randr_get_screen_resources_outputs(gsr_r); + int o; + + for (o = 0; o < gsr_r->num_outputs; o++) { + if (wsi_display_output_to_connector_id(connection, &connector_id_atom, ro[o]) == connector_id) { + output = ro[o]; + break; + } + } + free(gsr_r); + } + return output; +} + +/* + * Given a RandR output, find out which screen it's associated with + */ +static xcb_window_t +wsi_display_output_to_root(xcb_connection_t *connection, + xcb_randr_output_t output) +{ + if (!wsi_display_check_randr_version(connection)) + return 0; + + const xcb_setup_t *setup = xcb_get_setup(connection); + xcb_window_t root = 0; + + /* Search all of the screens for the provided output */ + xcb_screen_iterator_t iter; + for (iter = xcb_setup_roots_iterator(setup); root == 0 && iter.rem; xcb_screen_next(&iter)) { + xcb_randr_get_screen_resources_cookie_t gsr_c = xcb_randr_get_screen_resources(connection, iter.data->root); + xcb_randr_get_screen_resources_reply_t *gsr_r = xcb_randr_get_screen_resources_reply(connection, gsr_c, NULL); + + if (!gsr_r) + return 0; + + xcb_randr_output_t *ro = xcb_randr_get_screen_resources_outputs(gsr_r); + int o; + + for (o = 0; o < gsr_r->num_outputs; o++) { + if (ro[o] == output) { + root = iter.data->root; + break; + } + } + free(gsr_r); + } + return root; +} + +static struct wsi_display_connector * +wsi_display_get_output(struct wsi_device *wsi_device, + xcb_connection_t *connection, + RROutput output) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + struct wsi_display_connector *connector; + uint32_t connector_id; + xcb_window_t root; + xcb_randr_get_screen_resources_cookie_t src; + xcb_randr_get_screen_resources_reply_t *srr; + xcb_randr_get_output_info_cookie_t oic; + xcb_randr_get_output_info_reply_t *oir; + xcb_randr_mode_t *x_modes; + int m; + + root = wsi_display_output_to_root(connection, output); + if (!root) + return NULL; + + src = xcb_randr_get_screen_resources(connection, root); + oic = xcb_randr_get_output_info(connection, output, XCB_CURRENT_TIME); + srr = xcb_randr_get_screen_resources_reply(connection, src, NULL); + oir = xcb_randr_get_output_info_reply(connection, oic, NULL); + + /* See if we already have a connector for this output */ + connector = wsi_display_find_output(wsi_device, output); + + if (!connector) { + xcb_atom_t connector_id_atom = 0; + + /* + * Go get the kernel connector ID for this X output + */ + connector_id = wsi_display_output_to_connector_id(connection, &connector_id_atom, output); + + /* Any X server with lease support will have this atom */ + if (!connector_id) { + free(oir); + free(srr); + return NULL; + } + + if (!connector) { + /* See if we already have a connector for this id */ + connector = wsi_display_find_connector(wsi_device, connector_id); + + if (connector) + connector->output = output; + } + } + + if (!connector) { + connector = wsi_display_alloc_connector(wsi, connector_id); + if (!connector) { + free(oir); + free(srr); + return NULL; + } + LIST_ADDTAIL(&connector->list, &wsi->connectors); + connector->output = output; + } + + if (oir && srr) { + /* Get X modes and add them */ + + connector->connected = oir->connection != XCB_RANDR_CONNECTION_DISCONNECTED; + + wsi_display_invalidate_connector_modes(wsi_device, connector); + + x_modes = xcb_randr_get_output_info_modes(oir); + for (m = 0; m < oir->num_modes; m++) { + xcb_randr_mode_info_iterator_t i = xcb_randr_get_screen_resources_modes_iterator(srr); + while (i.rem) { + xcb_randr_mode_info_t *mi = i.data; + if (mi->id == x_modes[m]) { + VkResult result = wsi_display_register_x_mode(wsi_device, connector, mi, m < oir->num_preferred); + if (result != VK_SUCCESS) { + free(oir); + free(srr); + return NULL; + } + break; + } + xcb_randr_mode_info_next(&i); + } + } + } + + free(oir); + free(srr); + return connector; +} + +static xcb_randr_crtc_t +wsi_display_find_crtc_for_output(xcb_connection_t *connection, + xcb_window_t root, + xcb_randr_output_t output) +{ + xcb_randr_get_screen_resources_cookie_t gsr_c = xcb_randr_get_screen_resources(connection, root); + xcb_randr_get_screen_resources_reply_t *gsr_r = xcb_randr_get_screen_resources_reply(connection, gsr_c, NULL); + + if (!gsr_r) + return 0; + + xcb_randr_crtc_t *rc = xcb_randr_get_screen_resources_crtcs(gsr_r); + xcb_randr_crtc_t idle_crtc = 0; + xcb_randr_crtc_t active_crtc = 0; + + /* Find either a crtc already connected to the desired output or idle */ + int c; + for (c = 0; active_crtc == 0 && c < gsr_r->num_crtcs; c++) { + xcb_randr_get_crtc_info_cookie_t gci_c = xcb_randr_get_crtc_info(connection, rc[c], gsr_r->config_timestamp); + xcb_randr_get_crtc_info_reply_t *gci_r = xcb_randr_get_crtc_info_reply(connection, gci_c, NULL); + if (gci_r) { + if (gci_r->mode) { + int num_outputs = xcb_randr_get_crtc_info_outputs_length(gci_r); + xcb_randr_output_t *outputs = xcb_randr_get_crtc_info_outputs(gci_r); + for (int o = 0; o < num_outputs; o++) + if (outputs[o] == output && num_outputs == 1) { + active_crtc = rc[c]; + break; + } + } else if (idle_crtc == 0) { + int num_possible = xcb_randr_get_crtc_info_possible_length(gci_r); + xcb_randr_output_t *possible = xcb_randr_get_crtc_info_possible(gci_r); + for (int p = 0; p < num_possible; p++) + if (possible[p] == output) { + idle_crtc = rc[c]; + break; + } + } + free(gci_r); + } + } + free(gsr_r); + + if (active_crtc) + return active_crtc; + return idle_crtc; +} + +VkResult +wsi_acquire_xlib_display(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + Display *dpy, + VkDisplayKHR display) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + xcb_connection_t *connection = XGetXCBConnection(dpy); + struct wsi_display_connector *connector = wsi_display_connector_from_handle(display); + xcb_window_t root; + + if (!connector->output) { + connector->output = wsi_display_connector_id_to_output(connection, connector->id); + + /* Check and see if we found the output */ + if (!connector->output) + return VK_ERROR_OUT_OF_DATE_KHR; + } + + root = wsi_display_output_to_root(connection, connector->output); + if (!root) + return VK_ERROR_OUT_OF_DATE_KHR; + + xcb_randr_crtc_t crtc = wsi_display_find_crtc_for_output(connection, + root, + connector->output); + + if (!crtc) + return VK_ERROR_OUT_OF_DATE_KHR; + + xcb_randr_lease_t lease = xcb_generate_id(connection); + xcb_randr_create_lease_cookie_t cl_c = xcb_randr_create_lease(connection, + root, + lease, + 1, + 1, + &crtc, + &connector->output); + xcb_randr_create_lease_reply_t *cl_r = xcb_randr_create_lease_reply(connection, cl_c, NULL); + if (!cl_r) + return VK_ERROR_OUT_OF_DATE_KHR; + + int fd = -1; + if (cl_r->nfd > 0) { + int *rcl_f = xcb_randr_create_lease_reply_fds(connection, cl_r); + + fd = rcl_f[0]; + } + free (cl_r); + if (fd < 0) + return VK_ERROR_OUT_OF_DATE_KHR; + + wsi->master_fd = fd; + + return VK_SUCCESS; +} + +VkResult +wsi_get_randr_output_display(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + Display *dpy, + RROutput output, + VkDisplayKHR *display) +{ + xcb_connection_t *connection = XGetXCBConnection(dpy); + struct wsi_display_connector *connector = wsi_display_get_output(wsi_device, connection, output); + + if (connector) + *display = wsi_display_connector_to_handle(connector); + else + *display = NULL; + return VK_SUCCESS; +} +#endif /* VK_USE_PLATFORM_XLIB_XRANDR_EXT */ + +VkResult +wsi_create_display_surface(VkInstance instance, + const VkAllocationCallbacks *allocator, + const VkDisplaySurfaceCreateInfoKHR *create_info, + VkSurfaceKHR *surface_khr) +{ + VkIcdSurfaceDisplay *surface; + + surface = vk_alloc(allocator, sizeof *surface, 8, + VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); + if (surface == NULL) + return VK_ERROR_OUT_OF_HOST_MEMORY; + + surface->base.platform = VK_ICD_WSI_PLATFORM_DISPLAY; + + surface->displayMode = create_info->displayMode; + surface->planeIndex = create_info->planeIndex; + surface->planeStackIndex = create_info->planeStackIndex; + surface->transform = create_info->transform; + surface->globalAlpha = create_info->globalAlpha; + surface->alphaMode = create_info->alphaMode; + surface->imageExtent = create_info->imageExtent; + + *surface_khr = VkIcdSurfaceBase_to_handle(&surface->base); + return VK_SUCCESS; +} + + +static VkResult +wsi_display_surface_get_support(VkIcdSurfaceBase *surface, + struct wsi_device *wsi_device, + const VkAllocationCallbacks *allocator, + uint32_t queueFamilyIndex, + int local_fd, + VkBool32* pSupported) +{ + *pSupported = VK_TRUE; + return VK_SUCCESS; +} + +static VkResult +wsi_display_surface_get_capabilities(VkIcdSurfaceBase *surface_base, + VkSurfaceCapabilitiesKHR* caps) +{ + VkIcdSurfaceDisplay *surface = (VkIcdSurfaceDisplay *) surface_base; + wsi_display_mode *mode = wsi_display_mode_from_handle(surface->displayMode); + + caps->currentExtent.width = mode->hdisplay; + caps->currentExtent.height = mode->vdisplay; + + /* XXX Figure out extents based on driver capabilities */ + caps->maxImageExtent = caps->minImageExtent = caps->currentExtent; + + caps->supportedCompositeAlpha = (VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR | + VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR); + + caps->minImageCount = 2; + caps->maxImageCount = 0; + + caps->supportedTransforms = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + caps->currentTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + caps->maxImageArrayLayers = 1; + caps->supportedUsageFlags = + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + return VK_SUCCESS; +} + +static VkResult +wsi_display_surface_get_capabilities2(VkIcdSurfaceBase *icd_surface, + const void *info_next, + VkSurfaceCapabilities2KHR *caps) +{ + assert(caps->sType == VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR); + + return wsi_display_surface_get_capabilities(icd_surface, &caps->surfaceCapabilities); +} + +static const VkFormat available_surface_formats[] = { + VK_FORMAT_B8G8R8A8_SRGB, + VK_FORMAT_B8G8R8A8_UNORM, +}; + +static VkResult +wsi_display_surface_get_formats(VkIcdSurfaceBase *icd_surface, + struct wsi_device *wsi_device, + uint32_t *surface_format_count, + VkSurfaceFormatKHR *surface_formats) +{ + VK_OUTARRAY_MAKE(out, surface_formats, surface_format_count); + + for (unsigned i = 0; i < ARRAY_SIZE(available_surface_formats); i++) { + vk_outarray_append(&out, f) { + f->format = available_surface_formats[i]; + f->colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + } + } + + return vk_outarray_status(&out); +} + +static VkResult +wsi_display_surface_get_formats2(VkIcdSurfaceBase *surface, + struct wsi_device *wsi_device, + const void *info_next, + uint32_t *surface_format_count, + VkSurfaceFormat2KHR *surface_formats) +{ + VK_OUTARRAY_MAKE(out, surface_formats, surface_format_count); + + for (unsigned i = 0; i < ARRAY_SIZE(available_surface_formats); i++) { + vk_outarray_append(&out, f) { + assert(f->sType == VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR); + f->surfaceFormat.format = available_surface_formats[i]; + f->surfaceFormat.colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + } + } + + return vk_outarray_status(&out); +} + +static const VkPresentModeKHR available_present_modes[] = { + VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_FIFO_KHR, +}; + +static VkResult +wsi_display_surface_get_present_modes(VkIcdSurfaceBase *surface, + uint32_t *present_mode_count, + VkPresentModeKHR *present_modes) +{ + if (present_modes == NULL) { + *present_mode_count = ARRAY_SIZE(available_present_modes); + return VK_SUCCESS; + } + + *present_mode_count = MIN2(*present_mode_count, ARRAY_SIZE(available_present_modes)); + typed_memcpy(present_modes, available_present_modes, *present_mode_count); + + if (*present_mode_count < ARRAY_SIZE(available_present_modes)) + return VK_INCOMPLETE; + return VK_SUCCESS; +} + +static VkResult +wsi_display_image_init(VkDevice device_h, + struct wsi_swapchain *drv_chain, + const VkSwapchainCreateInfoKHR *create_info, + const VkAllocationCallbacks *allocator, + struct wsi_display_image *image) +{ + struct wsi_display_swapchain *chain = (struct wsi_display_swapchain *) drv_chain; + struct wsi_display *wsi = chain->wsi; + VkResult result; + int ret; + uint32_t image_handle; + + if (chain->base.use_prime_blit) + result = wsi_create_prime_image(&chain->base, create_info, &image->base); + else + result = wsi_create_native_image(&chain->base, create_info, &image->base); + if (result != VK_SUCCESS) + return result; + + ret = drmPrimeFDToHandle(wsi->master_fd, image->base.fd, &image_handle); + + close(image->base.fd); + image->base.fd = -1; + + if (ret < 0) + goto fail_handle; + + image->state = wsi_image_idle; + image->frame = 0; + image->sec = 0; + image->usec = 0; + + /* XXX extract depth and bpp from image somehow */ + ret = drmModeAddFB(wsi->master_fd, create_info->imageExtent.width, create_info->imageExtent.height, + 24, 32, image->base.row_pitch, image_handle, &image->fb_id); + + if (ret) + goto fail_fb; + + return VK_SUCCESS; + +fail_fb: + /* fall through */ + +fail_handle: + wsi_destroy_image(&chain->base, &image->base); + + return VK_ERROR_OUT_OF_HOST_MEMORY; +} + +static void +wsi_display_image_finish(struct wsi_swapchain *drv_chain, + const VkAllocationCallbacks *allocator, + struct wsi_display_image *image) +{ + struct wsi_display_swapchain *chain = (struct wsi_display_swapchain *) drv_chain; + + wsi_destroy_image(&chain->base, &image->base); +} + +static VkResult +wsi_display_swapchain_destroy(struct wsi_swapchain *drv_chain, + const VkAllocationCallbacks *allocator) +{ + struct wsi_display_swapchain *chain = (struct wsi_display_swapchain *) drv_chain; + + for (uint32_t i = 0; i < chain->base.image_count; i++) + wsi_display_image_finish(drv_chain, allocator, &chain->images[i]); + vk_free(allocator, chain); + return VK_SUCCESS; +} + +static struct wsi_image * +wsi_display_get_wsi_image(struct wsi_swapchain *drv_chain, + uint32_t image_index) +{ + struct wsi_display_swapchain *chain = (struct wsi_display_swapchain *) drv_chain; + + return &chain->images[image_index].base; +} + +static void +wsi_display_idle_old_displaying(struct wsi_display_image *active_image) +{ + struct wsi_display_swapchain *chain = active_image->chain; + + wsi_display_debug("idle everyone but %ld\n", active_image - &(chain->images[0])); + for (uint32_t i = 0; i < chain->base.image_count; i++) + if (chain->images[i].state == wsi_image_displaying && &chain->images[i] != active_image) { + wsi_display_debug("idle %d\n", i); + chain->images[i].state = wsi_image_idle; + } +} + +static void +wsi_display_page_flip_handler2(int fd, + unsigned int frame, + unsigned int sec, + unsigned int usec, + uint32_t crtc_id, + void *data) +{ + struct wsi_display_image *image = data; + + wsi_display_debug("image %ld displayed at %d\n", image - &(image->chain->images[0]), frame); + image->state = wsi_image_displaying; + image->sec = sec; + image->usec = usec; + wsi_display_idle_old_displaying(image); +} + +static void wsi_display_page_flip_handler(int fd, unsigned int frame, + unsigned int sec, unsigned int usec, void *data) +{ + wsi_display_page_flip_handler2(fd, frame, sec, usec, 0, data); +} + +static drmEventContext event_context = { + .version = DRM_EVENT_CONTEXT_VERSION, + .page_flip_handler = wsi_display_page_flip_handler, +#if DRM_EVENT_CONTEXT_VERSION >= 3 + .page_flip_handler2 = wsi_display_page_flip_handler2, +#endif +}; + +static void * +wsi_display_wait_thread(void *data) +{ + struct wsi_display *wsi = data; + struct pollfd pollfd = { + .fd = wsi->master_fd, + .events = POLLIN + }; + int ret; + + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + for (;;) { + ret = poll(&pollfd, 1, -1); + if (ret > 0) { + pthread_mutex_lock(&wsi->wait_mutex); + (void) drmHandleEvent(wsi->master_fd, &event_context); + pthread_mutex_unlock(&wsi->wait_mutex); + pthread_cond_broadcast(&wsi->wait_cond); + } + } + return NULL; +} + +static int +wsi_display_wait_for_event(struct wsi_display *wsi, + uint64_t timeout_ns) +{ + int ret; + + if (!wsi->wait_thread) { + ret = pthread_create(&wsi->wait_thread, NULL, wsi_display_wait_thread, wsi); + if (ret) + return ret; + } + + struct timespec abs_timeout = { + .tv_sec = timeout_ns / ((uint64_t) 1000 * (uint64_t) 1000 * (uint64_t) 1000), + .tv_nsec = timeout_ns % ((uint64_t) 1000 * (uint64_t) 1000 * (uint64_t) 1000) + }; + + ret = pthread_cond_timedwait(&wsi->wait_cond, &wsi->wait_mutex, &abs_timeout); + + wsi_display_debug("%9ld done waiting for event %d\n", pthread_self(), ret); + return ret; +} + +static VkResult +wsi_display_acquire_next_image(struct wsi_swapchain *drv_chain, + uint64_t timeout, + VkSemaphore semaphore, + uint32_t *image_index) +{ + struct wsi_display_swapchain *chain = (struct wsi_display_swapchain *)drv_chain; + struct wsi_display *wsi = chain->wsi; + int ret = 0; + + if (timeout != 0 && timeout != UINT64_MAX) + timeout += wsi_get_current_realtime(); + + for (;;) { + for (uint32_t i = 0; i < chain->base.image_count; i++) { + if (chain->images[i].state == wsi_image_idle) { + *image_index = i; + wsi_display_debug("image %d available\n", i); + chain->images[i].state = wsi_image_drawing; + return VK_SUCCESS; + } + wsi_display_debug("image %d state %d\n", i, chain->images[i].state); + } + + if (ret == ETIMEDOUT) + return VK_TIMEOUT; + + ret = wsi_display_wait_for_event(wsi, timeout); + + if (ret && ret != ETIMEDOUT) + return VK_ERROR_OUT_OF_DATE_KHR; + } +} + +/* + * Check whether there are any other connectors driven by this crtc + */ +static bool +wsi_display_crtc_solo(struct wsi_display *wsi, + drmModeResPtr mode_res, + drmModeConnectorPtr connector, + uint32_t crtc_id) +{ + int c, e; + + /* See if any other connectors share the same encoder */ + for (c = 0; c < mode_res->count_connectors; c++) { + if (mode_res->connectors[c] == connector->connector_id) + continue; + + drmModeConnectorPtr other_connector = drmModeGetConnector(wsi->master_fd, mode_res->connectors[c]); + if (other_connector) { + bool match = (other_connector->encoder_id == connector->encoder_id); + drmModeFreeConnector(other_connector); + if (match) + return false; + } + } + + /* See if any other encoders share the same crtc */ + for (e = 0; e < mode_res->count_encoders; e++) { + if (mode_res->encoders[e] == connector->encoder_id) + continue; + + drmModeEncoderPtr other_encoder = drmModeGetEncoder(wsi->master_fd, mode_res->encoders[e]); + if (other_encoder) { + bool match = (other_encoder->crtc_id == crtc_id); + drmModeFreeEncoder(other_encoder); + if (match) + return false; + } + } + return true; +} + +/* + * Pick a suitable CRTC to drive this connector. Prefer a CRTC which is + * currently driving this connector and not any others. Settle for a CRTC + * which is currently idle. + */ +static uint32_t +wsi_display_select_crtc(struct wsi_display_connector *connector, + drmModeResPtr mode_res, + drmModeConnectorPtr drm_connector) +{ + struct wsi_display *wsi = connector->wsi; + int c; + uint32_t crtc_id; + + /* See what CRTC is currently driving this connector */ + if (drm_connector->encoder_id) { + drmModeEncoderPtr encoder = drmModeGetEncoder(wsi->master_fd, drm_connector->encoder_id); + if (encoder) { + crtc_id = encoder->crtc_id; + drmModeFreeEncoder(encoder); + if (crtc_id) { + if (wsi_display_crtc_solo(wsi, mode_res, drm_connector, crtc_id)) + return crtc_id; + } + } + } + crtc_id = 0; + for (c = 0; crtc_id == 0 && c < mode_res->count_crtcs; c++) { + drmModeCrtcPtr crtc = drmModeGetCrtc(wsi->master_fd, mode_res->crtcs[c]); + if (crtc && crtc->buffer_id == 0) + crtc_id = crtc->crtc_id; + drmModeFreeCrtc(crtc); + } + return crtc_id; +} + +static void +wsi_display_wait_flip_idle(struct wsi_swapchain *drv_chain) +{ + struct wsi_display_swapchain *chain = (struct wsi_display_swapchain *) drv_chain; + struct wsi_display *wsi = chain->wsi; + int ret; + uint64_t timeout = 0; + + wsi_display_debug("%9lu wait flip idle\n", pthread_self()); + + wsi_display_debug_code(uint64_t start_ns = wsi_get_current_realtime()); + pthread_mutex_lock(&wsi->wait_mutex); + for (;;) { + bool busy = false; + for (uint32_t i = 0; i < chain->base.image_count; i++) + if (chain->images[i].state == wsi_image_flipping) { + wsi_display_debug("image %d flipping\n", i); + busy = true; + break; + } + if (!busy) + break; + + /* wait for at most 100ms; a flip shouldn't take that long... */ + if (!timeout) + timeout = wsi_get_current_realtime() + 100000000ull; + + ret = wsi_display_wait_for_event(wsi, timeout); + + if (ret != 0) { + wsi_display_debug("wait for flip idle failed %d %s\n", ret, strerror(ret)); + break; + } + } + + wsi_display_debug("%9lu wait flip idle %f ms\n", pthread_self(), ((int64_t) (wsi_get_current_realtime() - start_ns)) / 1.0e6); + pthread_mutex_unlock(&wsi->wait_mutex); +} + +static VkResult +wsi_display_setup_connector(wsi_display_connector *connector, + wsi_display_mode *display_mode) +{ + struct wsi_display *wsi = connector->wsi; + drmModeModeInfoPtr drm_mode; + drmModeConnectorPtr drm_connector; + drmModeResPtr mode_res; + VkResult result; + int m; + + if (connector->current_mode == display_mode && connector->crtc_id) + return VK_SUCCESS; + + mode_res = drmModeGetResources(wsi->master_fd); + if (!mode_res) { + result = VK_ERROR_INITIALIZATION_FAILED; + goto bail; + } + + drm_connector = drmModeGetConnectorCurrent(wsi->master_fd, connector->id); + if (!drm_connector) { + result = VK_ERROR_INITIALIZATION_FAILED; + goto bail_mode_res; + } + + /* Pick a CRTC if we don't have one */ + if (!connector->crtc_id) { + connector->crtc_id = wsi_display_select_crtc(connector, mode_res, drm_connector); + if (!connector->crtc_id) { + result = VK_ERROR_OUT_OF_DATE_KHR; + goto bail_connector; + } + } + + if (connector->current_mode != display_mode) { + + /* Find the drm mode cooresponding to the requested VkDisplayMode */ + drm_mode = NULL; + for (m = 0; m < drm_connector->count_modes; m++) { + drm_mode = &drm_connector->modes[m]; + if (wsi_display_mode_matches_drm(display_mode, drm_mode)) + break; + drm_mode = NULL; + } + + if (!drm_mode) { + result = VK_ERROR_OUT_OF_DATE_KHR; + goto bail_connector; + } + + connector->current_mode = display_mode; + connector->current_drm_mode = *drm_mode; + } + + result = VK_SUCCESS; + +bail_connector: + drmModeFreeConnector(drm_connector); +bail_mode_res: + drmModeFreeResources(mode_res); +bail: + return result; + +} + +static VkResult +wsi_display_queue_present(struct wsi_swapchain *drv_chain, + uint32_t image_index, + const VkPresentRegionKHR *damage) +{ + struct wsi_display_swapchain *chain = (struct wsi_display_swapchain *) drv_chain; + struct wsi_display *wsi = chain->wsi; + struct wsi_display_image *image = &chain->images[image_index]; + VkIcdSurfaceDisplay *surface = chain->surface; + wsi_display_mode *display_mode = wsi_display_mode_from_handle(surface->displayMode); + wsi_display_connector *connector = display_mode->connector; + int ret; + VkResult result; + + if (wsi->master_fd < 0) + return VK_ERROR_INITIALIZATION_FAILED; + + if (display_mode != connector->current_mode) + connector->active = false; + + assert(image->state == wsi_image_drawing); + wsi_display_debug("present %d\n", image_index); + for (;;) { + if (connector->active) { + wsi_display_wait_flip_idle(drv_chain); + ret = drmModePageFlip(wsi->master_fd, connector->crtc_id, image->fb_id, + DRM_MODE_PAGE_FLIP_EVENT, image); + if (ret == 0) { + image->state = wsi_image_flipping; + return VK_SUCCESS; + } + wsi_display_debug("page flip err %d %s\n", ret, strerror(-ret)); + } else + ret = -EINVAL; + + if (ret) { + switch(-ret) { + case EINVAL: + + result = wsi_display_setup_connector(connector, display_mode); + + if (result != VK_SUCCESS) { + image->state = wsi_image_idle; + return result; + } + + /* XXX allow setting of position */ + + ret = drmModeSetCrtc(wsi->master_fd, connector->crtc_id, image->fb_id, 0, 0, + &connector->id, 1, &connector->current_drm_mode); + + if (ret == 0) { + image->state = wsi_image_displaying; + wsi_display_idle_old_displaying(image); + connector->active = true; + return VK_SUCCESS; + } + break; + case EACCES: + usleep(1000 * 1000); + connector->active = false; + break; + default: + connector->active = false; + image->state = wsi_image_idle; + return VK_ERROR_OUT_OF_DATE_KHR; + } + } + } +} + +static VkResult +wsi_display_surface_create_swapchain(VkIcdSurfaceBase *icd_surface, + VkDevice device, + struct wsi_device *wsi_device, + int local_fd, + const VkSwapchainCreateInfoKHR *create_info, + const VkAllocationCallbacks *allocator, + struct wsi_swapchain **swapchain_out) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + VkResult result; + + assert(create_info->sType == VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR); + + struct wsi_display_swapchain *chain; + const unsigned num_images = create_info->minImageCount; + size_t size = sizeof(*chain) + num_images * sizeof(chain->images[0]); + + chain = vk_alloc(allocator, size, 8, + VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); + + if (chain == NULL) + return VK_ERROR_OUT_OF_HOST_MEMORY; + + result = wsi_swapchain_init(wsi_device, &chain->base, device, + create_info, allocator); + + chain->base.destroy = wsi_display_swapchain_destroy; + chain->base.get_wsi_image = wsi_display_get_wsi_image; + chain->base.acquire_next_image = wsi_display_acquire_next_image; + chain->base.queue_present = wsi_display_queue_present; + chain->base.present_mode = create_info->presentMode; + chain->base.image_count = num_images; + + chain->wsi = wsi; + + chain->surface = (VkIcdSurfaceDisplay *) icd_surface; + + for (uint32_t image = 0; image < chain->base.image_count; image++) { + result = wsi_display_image_init(device, &chain->base, create_info, allocator, + &chain->images[image]); + if (result != VK_SUCCESS) + goto fail_init_images; + } + + *swapchain_out = &chain->base; + + return VK_SUCCESS; + +fail_init_images: + return result; +} + +VkResult +wsi_display_init_wsi(struct wsi_device *wsi_device, + const VkAllocationCallbacks *alloc, + VkPhysicalDevice physical_device, + int device_fd) +{ + struct wsi_display *wsi; + VkResult result; + + wsi = vk_alloc(alloc, sizeof(*wsi), 8, + VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (!wsi) { + result = VK_ERROR_OUT_OF_HOST_MEMORY; + goto fail; + } + memset(wsi, '\0', sizeof (*wsi)); + + wsi->master_fd = -1; + wsi->render_fd = device_fd; + + pthread_mutex_init(&wsi->wait_mutex, NULL); + wsi->physical_device = physical_device; + wsi->alloc = alloc; + + LIST_INITHEAD(&wsi->display_modes); + LIST_INITHEAD(&wsi->connectors); + + pthread_mutex_init(&wsi->wait_mutex, NULL); + pthread_cond_init(&wsi->wait_cond, NULL); + + wsi->base.get_support = wsi_display_surface_get_support; + wsi->base.get_capabilities = wsi_display_surface_get_capabilities; + wsi->base.get_capabilities2 = wsi_display_surface_get_capabilities2; + wsi->base.get_formats = wsi_display_surface_get_formats; + wsi->base.get_formats2 = wsi_display_surface_get_formats2; + wsi->base.get_present_modes = wsi_display_surface_get_present_modes; + wsi->base.create_swapchain = wsi_display_surface_create_swapchain; + + wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY] = &wsi->base; + + return VK_SUCCESS; + +fail: + return result; +} + +void +wsi_display_finish_wsi(struct wsi_device *wsi_device, + const VkAllocationCallbacks *alloc) +{ + struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; + + if (wsi) { + vk_free(alloc, wsi); + } +} diff --git a/src/vulkan/wsi/wsi_common_display.h b/src/vulkan/wsi/wsi_common_display.h new file mode 100644 index 00000000000..2e47a21fb92 --- /dev/null +++ b/src/vulkan/wsi/wsi_common_display.h @@ -0,0 +1,92 @@ +/* + * Copyright © 2017 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef WSI_COMMON_DISPLAY_H +#define WSI_COMMON_DISPLAY_H + +#include "wsi_common.h" +#include <xf86drm.h> +#include <xf86drmMode.h> + +#define typed_memcpy(dest, src, count) ({ \ + STATIC_ASSERT(sizeof(*src) == sizeof(*dest)); \ + memcpy((dest), (src), (count) * sizeof(*(src))); \ +}) + +VkResult +wsi_display_get_physical_device_display_properties(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + uint32_t *property_count, + VkDisplayPropertiesKHR *properties); +VkResult +wsi_display_get_physical_device_display_plane_properties(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + uint32_t *property_count, + VkDisplayPlanePropertiesKHR *properties); + +VkResult +wsi_display_get_display_plane_supported_displays(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + uint32_t plane_index, + uint32_t *display_count, + VkDisplayKHR *displays); +VkResult +wsi_display_get_display_mode_properties(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + VkDisplayKHR display, + uint32_t *property_count, + VkDisplayModePropertiesKHR *properties); + +VkResult +wsi_get_display_plane_capabilities(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + VkDisplayModeKHR mode_khr, + uint32_t plane_index, + VkDisplayPlaneCapabilitiesKHR *capabilities); +VkResult +wsi_release_display(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + VkDisplayKHR display); + +#if VK_USE_PLATFORM_XLIB_XRANDR_EXT +VkResult +wsi_acquire_xlib_display(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + Display *dpy, + VkDisplayKHR display); + +VkResult +wsi_get_randr_output_display(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + Display *dpy, + RROutput output, + VkDisplayKHR *display); + +#endif /* VK_USE_PLATFORM_XLIB_XRANDR_EXT */ + +VkResult +wsi_create_display_surface(VkInstance instance, + const VkAllocationCallbacks *pAllocator, + const VkDisplaySurfaceCreateInfoKHR *pCreateInfo, + VkSurfaceKHR *pSurface); + +#endif diff --git a/src/vulkan/wsi/wsi_common_private.h b/src/vulkan/wsi/wsi_common_private.h index 503b2a015dc..d38d2efa116 100644 --- a/src/vulkan/wsi/wsi_common_private.h +++ b/src/vulkan/wsi/wsi_common_private.h @@ -135,6 +135,16 @@ void wsi_wl_finish_wsi(struct wsi_device *wsi_device, const VkAllocationCallbacks *alloc); +VkResult +wsi_display_init_wsi(struct wsi_device *wsi_device, + const VkAllocationCallbacks *alloc, + VkPhysicalDevice physical_device, + int device_fd); + +void +wsi_display_finish_wsi(struct wsi_device *wsi_device, + const VkAllocationCallbacks *alloc); + #define WSI_DEFINE_NONDISP_HANDLE_CASTS(__wsi_type, __VkType) \ \ static inline struct __wsi_type * \ -- 2.15.1 _______________________________________________ mesa-dev mailing list mesa-dev@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/mesa-dev