diff options
-rw-r--r-- | src/amd/vulkan/radv_device.c | 8 | ||||
-rw-r--r-- | src/amd/vulkan/radv_private.h | 1 | ||||
-rw-r--r-- | src/amd/vulkan/radv_wsi.c | 3 | ||||
-rw-r--r-- | src/intel/vulkan/anv_device.c | 4 | ||||
-rw-r--r-- | src/intel/vulkan/anv_private.h | 1 | ||||
-rw-r--r-- | src/intel/vulkan/anv_wsi.c | 3 | ||||
-rw-r--r-- | src/vulkan/Makefile.am | 7 | ||||
-rw-r--r-- | src/vulkan/Makefile.sources | 4 | ||||
-rw-r--r-- | src/vulkan/wsi/meson.build | 8 | ||||
-rw-r--r-- | src/vulkan/wsi/wsi_common.c | 12 | ||||
-rw-r--r-- | src/vulkan/wsi/wsi_common.h | 5 | ||||
-rw-r--r-- | src/vulkan/wsi/wsi_common_display.c | 1477 | ||||
-rw-r--r-- | src/vulkan/wsi/wsi_common_display.h | 85 | ||||
-rw-r--r-- | src/vulkan/wsi/wsi_common_private.h | 9 |
14 files changed, 1622 insertions, 5 deletions
diff --git a/src/amd/vulkan/radv_device.c b/src/amd/vulkan/radv_device.c index cf9cf437b3d..056043a9169 100644 --- a/src/amd/vulkan/radv_device.c +++ b/src/amd/vulkan/radv_device.c @@ -225,6 +225,7 @@ radv_physical_device_init(struct radv_physical_device *device, VkResult result; drmVersionPtr version; int fd; + int master_fd = -1; fd = open(path, O_RDWR | O_CLOEXEC); if (fd < 0) { @@ -247,6 +248,8 @@ radv_physical_device_init(struct radv_physical_device *device, if (strcmp(version->name, "amdgpu")) { drmFreeVersion(version); + if (master_fd != -1) + close(master_fd); close(fd); if (instance->debug_flags & RADV_DEBUG_STARTUP) @@ -271,6 +274,7 @@ radv_physical_device_init(struct radv_physical_device *device, goto fail; } + device->master_fd = master_fd; device->local_fd = fd; device->ws->query_info(device->ws, &device->rad_info); @@ -349,6 +353,8 @@ radv_physical_device_init(struct radv_physical_device *device, fail: close(fd); + if (master_fd != -1) + close(master_fd); return result; } @@ -359,6 +365,8 @@ radv_physical_device_finish(struct radv_physical_device *device) device->ws->destroy(device->ws); disk_cache_destroy(device->disk_cache); close(device->local_fd); + if (device->master_fd != -1) + close(device->master_fd); } static void * diff --git a/src/amd/vulkan/radv_private.h b/src/amd/vulkan/radv_private.h index cc336499c73..a76d771e4a5 100644 --- a/src/amd/vulkan/radv_private.h +++ b/src/amd/vulkan/radv_private.h @@ -285,6 +285,7 @@ struct radv_physical_device { uint8_t cache_uuid[VK_UUID_SIZE]; int local_fd; + int master_fd; struct wsi_device wsi_device; bool has_rbplus; /* if RB+ register exist */ diff --git a/src/amd/vulkan/radv_wsi.c b/src/amd/vulkan/radv_wsi.c index 927650480a6..2840b666727 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->master_fd); } void diff --git a/src/intel/vulkan/anv_device.c b/src/intel/vulkan/anv_device.c index 4e63f0c46fa..294a145c1a8 100644 --- a/src/intel/vulkan/anv_device.c +++ b/src/intel/vulkan/anv_device.c @@ -278,6 +278,7 @@ anv_physical_device_init(struct anv_physical_device *device, { VkResult result; int fd; + int master_fd = -1; brw_process_intel_debug_variable(); @@ -449,10 +450,13 @@ anv_physical_device_init(struct anv_physical_device *device, &device->supported_extensions); device->local_fd = fd; + device->master_fd = master_fd; return VK_SUCCESS; fail: close(fd); + if (master_fd != -1) + close(master_fd); return result; } diff --git a/src/intel/vulkan/anv_private.h b/src/intel/vulkan/anv_private.h index 6af2a0bd3f3..edc5317bac4 100644 --- a/src/intel/vulkan/anv_private.h +++ b/src/intel/vulkan/anv_private.h @@ -876,6 +876,7 @@ struct anv_physical_device { struct wsi_device wsi_device; int local_fd; + int master_fd; }; struct anv_instance { diff --git a/src/intel/vulkan/anv_wsi.c b/src/intel/vulkan/anv_wsi.c index 20094f93e1f..a7efb1416fa 100644 --- a/src/intel/vulkan/anv_wsi.c +++ b/src/intel/vulkan/anv_wsi.c @@ -48,7 +48,8 @@ anv_init_wsi(struct anv_physical_device *physical_device) result = 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->master_fd); if (result != VK_SUCCESS) return result; diff --git a/src/vulkan/Makefile.am b/src/vulkan/Makefile.am index 3da03ed09e1..352cfacab31 100644 --- a/src/vulkan/Makefile.am +++ b/src/vulkan/Makefile.am @@ -54,6 +54,13 @@ AM_CPPFLAGS += \ VULKAN_WSI_SOURCES += $(VULKAN_WSI_X11_FILES) endif +if HAVE_PLATFORM_DRM +AM_CPPFLAGS += \ + -DVK_USE_PLATFORM_DISPLAY_KHR + +VULKAN_WSI_SOURCES += $(VULKAN_WSI_DISPLAY_FILES) +endif + CLEANFILES = \ $(VULKAN_UTIL_GENERATED_FILES) \ $(VULKAN_WSI_WAYLAND_GENERATED_FILES) diff --git a/src/vulkan/Makefile.sources b/src/vulkan/Makefile.sources index 101a94349c6..f0f6bcd4fd8 100644 --- a/src/vulkan/Makefile.sources +++ b/src/vulkan/Makefile.sources @@ -19,6 +19,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_debug_report.c \ diff --git a/src/vulkan/wsi/meson.build b/src/vulkan/wsi/meson.build index 223c8ca357e..53306295ee3 100644 --- a/src/vulkan/wsi/meson.build +++ b/src/vulkan/wsi/meson.build @@ -59,6 +59,14 @@ if with_platform_wayland ] endif +if with_platform_drm + vulkan_wsi_args += '-DVK_USE_PLATFORM_DISPLAY_KHR' + files_vulkan_wsi += files( + 'wsi_common_display.c', + 'wsi_common_display.h', + ) +endif + libvulkan_wsi = static_library( 'vulkan_wsi', files_vulkan_wsi, diff --git a/src/vulkan/wsi/wsi_common.c b/src/vulkan/wsi/wsi_common.c index 87e508ddf85..142c5d8fe58 100644 --- a/src/vulkan/wsi/wsi_common.c +++ b/src/vulkan/wsi/wsi_common.c @@ -32,7 +32,8 @@ VkResult wsi_device_init(struct wsi_device *wsi, VkPhysicalDevice pdevice, WSI_FN_GetPhysicalDeviceProcAddr proc_addr, - const VkAllocationCallbacks *alloc) + const VkAllocationCallbacks *alloc, + int display_fd) { VkResult result; @@ -91,6 +92,12 @@ wsi_device_init(struct wsi_device *wsi, goto fail; #endif +#ifdef VK_USE_PLATFORM_DISPLAY_KHR + result = wsi_display_init_wsi(wsi, alloc, display_fd); + if (result != VK_SUCCESS) + goto fail; +#endif + return VK_SUCCESS; fail: @@ -102,6 +109,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 6cf729ba025..61b1de59d7f 100644 --- a/src/vulkan/wsi/wsi_common.h +++ b/src/vulkan/wsi/wsi_common.h @@ -68,7 +68,7 @@ struct wsi_format_modifier_properties_list { struct wsi_interface; -#define VK_ICD_WSI_PLATFORM_MAX 5 +#define VK_ICD_WSI_PLATFORM_MAX (VK_ICD_WSI_PLATFORM_DISPLAY + 1) struct wsi_device { VkPhysicalDevice pdevice; @@ -116,7 +116,8 @@ VkResult wsi_device_init(struct wsi_device *wsi, VkPhysicalDevice pdevice, WSI_FN_GetPhysicalDeviceProcAddr proc_addr, - const VkAllocationCallbacks *alloc); + const VkAllocationCallbacks *alloc, + int display_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..fd2a4ad9930 --- /dev/null +++ b/src/vulkan/wsi/wsi_common_display.c @@ -0,0 +1,1477 @@ +/* + * 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> +#include <drm_fourcc.h> +#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; /* in kHz */ + 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; + struct list_head display_modes; + wsi_display_mode *current_mode; + drmModeModeInfo current_drm_mode; +} wsi_display_connector; + +struct wsi_display { + struct wsi_interface base; + + const VkAllocationCallbacks *alloc; + + int fd; + + pthread_mutex_t wait_mutex; + pthread_cond_t wait_cond; + pthread_t wait_thread; + + struct list_head connectors; +}; + +#define wsi_for_each_display_mode(_mode, _conn) \ + list_for_each_entry_safe(struct wsi_display_mode, _mode, \ + &(_conn)->display_modes, list) + +#define wsi_for_each_connector(_conn, _dev) \ + list_for_each_entry_safe(struct wsi_display_connector, _conn, \ + &(_dev)->connectors, list) + +enum wsi_image_state { + WSI_IMAGE_IDLE, + WSI_IMAGE_DRAWING, + WSI_IMAGE_QUEUED, + WSI_IMAGE_FLIPPING, + WSI_IMAGE_DISPLAYING +}; + +struct wsi_display_image { + struct wsi_image base; + struct wsi_display_swapchain *chain; + enum wsi_image_state state; + uint32_t fb_id; + uint32_t buffer[4]; + uint64_t flip_sequence; +}; + +struct wsi_display_swapchain { + struct wsi_swapchain base; + struct wsi_display *wsi; + VkIcdSurfaceDisplay *surface; + uint64_t flip_sequence; + VkResult status; + 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 && + MAX2(wsi->vscan, 1) == MAX2(drm->vscan, 1) && + wsi->flags == drm->flags; +} + +static double +wsi_display_mode_refresh(struct wsi_display_mode *wsi) +{ + return (double) wsi->clock * 1000.0 / ((double) wsi->htotal * + (double) wsi->vtotal * + (double) MAX2(wsi->vscan, 1)); +} + +static uint64_t wsi_get_current_monotonic(void) +{ + struct timespec tv; + + clock_gettime(CLOCK_MONOTONIC, &tv); + return tv.tv_nsec + tv.tv_sec*1000000000ull; +} + +static uint64_t wsi_rel_to_abs_time(uint64_t rel_time) +{ + uint64_t current_time = wsi_get_current_monotonic(); + + /* check for overflow */ + if (rel_time > UINT64_MAX - current_time) + return UINT64_MAX; + + return current_time + rel_time; +} + +static struct wsi_display_mode * +wsi_display_find_drm_mode(struct wsi_device *wsi_device, + struct wsi_display_connector *connector, + drmModeModeInfoPtr mode) +{ + wsi_for_each_display_mode(display_mode, connector) { + if (wsi_display_mode_matches_drm(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) +{ + wsi_for_each_display_mode(display_mode, 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 = + wsi_display_find_drm_mode(wsi_device, connector, drm_mode); + + if (display_mode) { + display_mode->valid = true; + return VK_SUCCESS; + } + + display_mode = vk_zalloc(wsi->alloc, sizeof (struct wsi_display_mode), + 8, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + 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; /* kHz */ + 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, &connector->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]; + + wsi_for_each_connector(connector, wsi) { + 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 = + vk_zalloc(wsi->alloc, sizeof (struct wsi_display_connector), + 8, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + + connector->id = connector_id; + connector->wsi = wsi; + connector->active = false; + /* XXX use EDID name */ + connector->name = "monitor"; + list_inithead(&connector->display_modes); + 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]; + + if (wsi->fd < 0) + return NULL; + + drmModeConnectorPtr drm_connector = + drmModeGetConnector(wsi->fd, connector_id); + + if (!drm_connector) + return NULL; + + struct wsi_display_connector *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 (int m = 0; m < drm_connector->count_modes; m++) { + VkResult 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 uint32_t +mode_size(struct wsi_display_mode *mode) +{ + /* fortunately, these are both uint16_t, so this is easy */ + return (uint32_t) mode->hdisplay * (uint32_t) mode->vdisplay; +} + +static void +wsi_display_fill_in_display_properties(struct wsi_device *wsi_device, + struct wsi_display_connector *connector, + VkDisplayPropertiesKHR *properties) +{ + properties->display = wsi_display_connector_to_handle(connector); + properties->displayName = connector->name; + + /* Find the first preferred mode and assume that's the physical + * resolution. If there isn't a preferred mode, find the largest mode and + * use that. + */ + + struct wsi_display_mode *preferred_mode = NULL, *largest_mode = NULL; + wsi_for_each_display_mode(display_mode, connector) { + if (!display_mode->valid) + continue; + if (display_mode->preferred) { + preferred_mode = display_mode; + break; + } + if (largest_mode == NULL || + mode_size(display_mode) > mode_size(largest_mode)) + { + largest_mode = display_mode; + } + } + + if (preferred_mode) { + properties->physicalResolution.width = preferred_mode->hdisplay; + properties->physicalResolution.height = preferred_mode->vdisplay; + } else if (largest_mode) { + properties->physicalResolution.width = largest_mode->hdisplay; + properties->physicalResolution.height = largest_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->planeReorderPossible = VK_FALSE; + properties->persistentContent = VK_FALSE; +} + +/* + * 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]; + + if (wsi->fd < 0) + goto bail; + + drmModeResPtr mode_res = drmModeGetResources(wsi->fd); + + if (!mode_res) + goto bail; + + VK_OUTARRAY_MAKE(conn, properties, property_count); + + /* Get current information */ + + for (int c = 0; c < mode_res->count_connectors; c++) { + struct wsi_display_connector *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) { + vk_outarray_append(&conn, prop) { + wsi_display_fill_in_display_properties(wsi_device, + connector, + prop); + } + } + } + + drmModeFreeResources(mode_res); + + return vk_outarray_status(&conn); + +bail: + *property_count = 0; + 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]; + + VK_OUTARRAY_MAKE(conn, properties, property_count); + + wsi_for_each_connector(connector, wsi) { + vk_outarray_append(&conn, prop) { + if (connector && connector->active) { + prop->currentDisplay = wsi_display_connector_to_handle(connector); + prop->currentStackIndex = 0; + } else { + prop->currentDisplay = VK_NULL_HANDLE; + prop->currentStackIndex = 0; + } + } + } + return vk_outarray_status(&conn); +} + +/* + * 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]; + + VK_OUTARRAY_MAKE(conn, displays, display_count); + + int c = 0; + + wsi_for_each_connector(connector, wsi) { + if (c == plane_index && connector->connected) { + vk_outarray_append(&conn, display) { + *display = wsi_display_connector_to_handle(connector); + } + } + c++; + } + return vk_outarray_status(&conn); +} + +/* + * 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_connector *connector = + wsi_display_connector_from_handle(display); + + VK_OUTARRAY_MAKE(conn, properties, property_count); + + wsi_for_each_display_mode(display_mode, connector) { + if (display_mode->valid) { + vk_outarray_append(&conn, prop) { + prop->displayMode = wsi_display_mode_to_handle(display_mode); + prop->parameters.visibleRegion.width = display_mode->hdisplay; + prop->parameters.visibleRegion.height = display_mode->vdisplay; + prop->parameters.refreshRate = + (uint32_t) (wsi_display_mode_refresh(display_mode) * 1000 + 0.5); + } + } + } + return vk_outarray_status(&conn); +} + +static bool +wsi_display_mode_matches_vk(wsi_display_mode *wsi, + const VkDisplayModeParametersKHR *vk) +{ + return (vk->visibleRegion.width == wsi->hdisplay && + vk->visibleRegion.height == wsi->vdisplay && + fabs(wsi_display_mode_refresh(wsi) * 1000.0 - vk->refreshRate) < 10); +} + +/* + * Implement vkCreateDisplayModeKHR (VK_KHR_display) + */ +VkResult +wsi_display_create_display_mode(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + VkDisplayKHR display, + const VkDisplayModeCreateInfoKHR *create_info, + const VkAllocationCallbacks *allocator, + VkDisplayModeKHR *mode) +{ + struct wsi_display_connector *connector = + wsi_display_connector_from_handle(display); + + if (create_info->flags != 0) + return VK_ERROR_INITIALIZATION_FAILED; + + /* Check and see if the requested mode happens to match an existing one and + * return that. This makes the conformance suite happy. Doing more than + * this would involve embedding the CVT function into the driver, which seems + * excessive. + */ + wsi_for_each_display_mode(display_mode, connector) { + if (display_mode->valid) { + if (wsi_display_mode_matches_vk(display_mode, &create_info->parameters)) { + *mode = wsi_display_mode_to_handle(display_mode); + return VK_SUCCESS; + } + } + } + return VK_ERROR_INITIALIZATION_FAILED; +} + +/* + * 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; +} + +VkResult +wsi_create_display_surface(VkInstance instance, + const VkAllocationCallbacks *allocator, + const VkDisplaySurfaceCreateInfoKHR *create_info, + VkSurfaceKHR *surface_khr) +{ + VkIcdSurfaceDisplay *surface = vk_zalloc(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_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 struct { + VkFormat format; + uint32_t drm_format; +} available_surface_formats[] = { + { .format = VK_FORMAT_B8G8R8A8_SRGB, .drm_format = DRM_FORMAT_XRGB8888 }, + { .format = VK_FORMAT_B8G8R8A8_UNORM, .drm_format = DRM_FORMAT_XRGB8888 }, +}; + +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].format; + 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].format; + f->surfaceFormat.colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; + } + } + + return vk_outarray_status(&out); +} + +static VkResult +wsi_display_surface_get_present_modes(VkIcdSurfaceBase *surface, + uint32_t *present_mode_count, + VkPresentModeKHR *present_modes) +{ + VK_OUTARRAY_MAKE(conn, present_modes, present_mode_count); + + vk_outarray_append(&conn, present) { + *present = VK_PRESENT_MODE_FIFO_KHR; + } + + return vk_outarray_status(&conn); +} + +static void +wsi_display_destroy_buffer(struct wsi_display *wsi, + uint32_t buffer) +{ + (void) drmIoctl(wsi->fd, DRM_IOCTL_MODE_DESTROY_DUMB, + &((struct drm_mode_destroy_dumb) { .handle = buffer })); +} + +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; + uint32_t drm_format = 0; + + for (unsigned i = 0; i < ARRAY_SIZE(available_surface_formats); i++) { + if (create_info->imageFormat == available_surface_formats[i].format) { + drm_format = available_surface_formats[i].drm_format; + break; + } + } + + /* the application provided an invalid format, bail */ + if (drm_format == 0) + return VK_ERROR_DEVICE_LOST; + + VkResult result = wsi_create_native_image(&chain->base, create_info, + 0, NULL, NULL, + &image->base); + if (result != VK_SUCCESS) + return result; + + memset(image->buffer, 0, sizeof (image->buffer)); + + for (unsigned int i = 0; i < image->base.num_planes; i++) { + int ret = drmPrimeFDToHandle(wsi->fd, image->base.fds[i], + &image->buffer[i]); + + close(image->base.fds[i]); + image->base.fds[i] = -1; + if (ret < 0) + goto fail_handle; + } + + image->chain = chain; + image->state = WSI_IMAGE_IDLE; + image->fb_id = 0; + + int ret = drmModeAddFB2(wsi->fd, + create_info->imageExtent.width, + create_info->imageExtent.height, + drm_format, + image->buffer, + image->base.row_pitches, + image->base.offsets, + &image->fb_id, 0); + + if (ret) + goto fail_fb; + + return VK_SUCCESS; + +fail_fb: +fail_handle: + for (unsigned int i = 0; i < image->base.num_planes; i++) { + if (image->buffer[i]) + wsi_display_destroy_buffer(wsi, image->buffer[i]); + if (image->base.fds[i] != -1) { + close(image->base.fds[i]); + image->base.fds[i] = -1; + } + } + + 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; + struct wsi_display *wsi = chain->wsi; + + drmModeRmFB(wsi->fd, image->fb_id); + for (unsigned int i = 0; i < image->base.num_planes; i++) + wsi_display_destroy_buffer(wsi, image->buffer[i]); + 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 VkResult +_wsi_display_queue_next(struct wsi_swapchain *drv_chain); + +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; + struct wsi_display_swapchain *chain = image->chain; + + wsi_display_debug("image %ld displayed at %d\n", + image - &(image->chain->images[0]), frame); + image->state = WSI_IMAGE_DISPLAYING; + wsi_display_idle_old_displaying(image); + VkResult result = _wsi_display_queue_next(&(chain->base)); + if (result != VK_SUCCESS) + chain->status = result; +} + +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->fd, + .events = POLLIN + }; + + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + for (;;) { + int ret = poll(&pollfd, 1, -1); + if (ret > 0) { + pthread_mutex_lock(&wsi->wait_mutex); + (void) drmHandleEvent(wsi->fd, &event_context); + pthread_mutex_unlock(&wsi->wait_mutex); + pthread_cond_broadcast(&wsi->wait_cond); + } + } + return NULL; +} + +static int +wsi_display_start_wait_thread(struct wsi_display *wsi) +{ + if (!wsi->wait_thread) { + int ret = pthread_create(&wsi->wait_thread, NULL, + wsi_display_wait_thread, wsi); + if (ret) + return ret; + } + return 0; +} + +/* + * Wait for at least one event from the kernel to be processed. + * Call with wait_mutex held + */ +static int +wsi_display_wait_for_event(struct wsi_display *wsi, + uint64_t timeout_ns) +{ + int ret; + + ret = wsi_display_start_wait_thread(wsi); + + if (ret) + return ret; + + struct timespec abs_timeout = { + .tv_sec = timeout_ns / 1000000000ULL, + .tv_nsec = timeout_ns % 1000000000ULL, + }; + + 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; + VkResult result = VK_SUCCESS; + + /* Bail early if the swapchain is broken */ + if (chain->status != VK_SUCCESS) + return chain->status; + + if (timeout != 0 && timeout != UINT64_MAX) + timeout = wsi_rel_to_abs_time(timeout); + + pthread_mutex_lock(&wsi->wait_mutex); + 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; + result = VK_SUCCESS; + goto done; + } + wsi_display_debug("image %d state %d\n", i, chain->images[i].state); + } + + if (ret == ETIMEDOUT) { + result = VK_TIMEOUT; + goto done; + } + + ret = wsi_display_wait_for_event(wsi, timeout); + + if (ret && ret != ETIMEDOUT) { + result = VK_ERROR_OUT_OF_DATE_KHR; + goto done; + } + } +done: + pthread_mutex_unlock(&wsi->wait_mutex); + + if (result != VK_SUCCESS) + return result; + + return chain->status; +} + +/* + * 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) +{ + /* See if any other connectors share the same encoder */ + for (int c = 0; c < mode_res->count_connectors; c++) { + if (mode_res->connectors[c] == connector->connector_id) + continue; + + drmModeConnectorPtr other_connector = + drmModeGetConnector(wsi->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 (int e = 0; e < mode_res->count_encoders; e++) { + if (mode_res->encoders[e] == connector->encoder_id) + continue; + + drmModeEncoderPtr other_encoder = + drmModeGetEncoder(wsi->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; + + /* See what CRTC is currently driving this connector */ + if (drm_connector->encoder_id) { + drmModeEncoderPtr encoder = + drmModeGetEncoder(wsi->fd, drm_connector->encoder_id); + + if (encoder) { + uint32_t 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; + } + } + } + uint32_t crtc_id = 0; + for (int c = 0; crtc_id == 0 && c < mode_res->count_crtcs; c++) { + drmModeCrtcPtr crtc = drmModeGetCrtc(wsi->fd, mode_res->crtcs[c]); + if (crtc && crtc->buffer_id == 0) + crtc_id = crtc->crtc_id; + drmModeFreeCrtc(crtc); + } + return crtc_id; +} + +static VkResult +wsi_display_setup_connector(wsi_display_connector *connector, + wsi_display_mode *display_mode) +{ + struct wsi_display *wsi = connector->wsi; + + if (connector->current_mode == display_mode && connector->crtc_id) + return VK_SUCCESS; + + VkResult result = VK_SUCCESS; + + drmModeResPtr mode_res = drmModeGetResources(wsi->fd); + if (!mode_res) { + if (errno == ENOMEM) + result = VK_ERROR_OUT_OF_HOST_MEMORY; + else + result = VK_ERROR_OUT_OF_DATE_KHR; + goto bail; + } + + drmModeConnectorPtr drm_connector = + drmModeGetConnectorCurrent(wsi->fd, connector->id); + + if (!drm_connector) { + if (errno == ENOMEM) + result = VK_ERROR_OUT_OF_HOST_MEMORY; + else + result = VK_ERROR_OUT_OF_DATE_KHR; + 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 corresponding to the requested VkDisplayMode */ + drmModeModeInfoPtr drm_mode = NULL; + + for (int 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; + } + +bail_connector: + drmModeFreeConnector(drm_connector); +bail_mode_res: + drmModeFreeResources(mode_res); +bail: + return result; + +} + +/* + * Check to see if the kernel has no flip queued and if there's an image + * waiting to be displayed. + */ +static VkResult +_wsi_display_queue_next(struct wsi_swapchain *drv_chain) +{ + struct wsi_display_swapchain *chain = + (struct wsi_display_swapchain *) drv_chain; + struct wsi_display *wsi = chain->wsi; + VkIcdSurfaceDisplay *surface = chain->surface; + wsi_display_mode *display_mode = + wsi_display_mode_from_handle(surface->displayMode); + wsi_display_connector *connector = display_mode->connector; + + if (wsi->fd < 0) + return VK_ERROR_OUT_OF_DATE_KHR; + + if (display_mode != connector->current_mode) + connector->active = false; + + for (;;) { + + /* Check to see if there is an image to display, or if some image is + * already queued */ + + struct wsi_display_image *image = NULL; + + for (uint32_t i = 0; i < chain->base.image_count; i++) { + struct wsi_display_image *tmp_image = &chain->images[i]; + + switch (tmp_image->state) { + case WSI_IMAGE_FLIPPING: + /* already flipping, don't send another to the kernel yet */ + return VK_SUCCESS; + case WSI_IMAGE_QUEUED: + /* find the oldest queued */ + if (!image || tmp_image->flip_sequence < image->flip_sequence) + image = tmp_image; + break; + default: + break; + } + } + + if (!image) + return VK_SUCCESS; + + int ret; + if (connector->active) { + ret = drmModePageFlip(wsi->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 == -EINVAL) { + VkResult 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->fd, connector->crtc_id, + image->fb_id, 0, 0, + &connector->id, 1, + &connector->current_drm_mode); + if (ret == 0) { + /* Assume that the mode set is synchronous and that any + * previous image is now idle. + */ + image->state = WSI_IMAGE_DISPLAYING; + wsi_display_idle_old_displaying(image); + connector->active = true; + return VK_SUCCESS; + } + } + + if (ret != -EACCES) { + connector->active = false; + image->state = WSI_IMAGE_IDLE; + return VK_ERROR_OUT_OF_DATE_KHR; + } + + /* Some other VT is currently active. Sit here waiting for + * our VT to become active again by polling once a second + */ + usleep(1000 * 1000); + connector->active = false; + } +} + +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]; + VkResult result; + + /* Bail early if the swapchain is broken */ + if (chain->status != VK_SUCCESS) + return chain->status; + + assert(image->state == WSI_IMAGE_DRAWING); + wsi_display_debug("present %d\n", image_index); + + pthread_mutex_lock(&wsi->wait_mutex); + + image->flip_sequence = ++chain->flip_sequence; + image->state = WSI_IMAGE_QUEUED; + + result = _wsi_display_queue_next(drv_chain); + if (result != VK_SUCCESS) + chain->status = result; + + pthread_mutex_unlock(&wsi->wait_mutex); + + if (result != VK_SUCCESS) + return result; + + return chain->status; +} + +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]; + + assert(create_info->sType == VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR); + + const unsigned num_images = create_info->minImageCount; + struct wsi_display_swapchain *chain = + vk_zalloc(allocator, + sizeof(*chain) + num_images * sizeof(chain->images[0]), + 8, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); + + if (chain == NULL) + return VK_ERROR_OUT_OF_HOST_MEMORY; + + VkResult 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->status = VK_SUCCESS; + + 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) { + while (image > 0) { + --image; + wsi_display_image_finish(&chain->base, allocator, + &chain->images[image]); + } + vk_free(allocator, chain); + goto fail_init_images; + } + } + + *swapchain_out = &chain->base; + + return VK_SUCCESS; + +fail_init_images: + return result; +} + +static bool +wsi_init_pthread_cond_monotonic(pthread_cond_t *cond) +{ + pthread_condattr_t condattr; + bool ret = false; + + if (pthread_condattr_init(&condattr) != 0) + goto fail_attr_init; + + if (pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC) != 0) + goto fail_attr_set; + + if (pthread_cond_init(cond, &condattr) != 0) + goto fail_cond_init; + + ret = true; + +fail_cond_init: +fail_attr_set: + pthread_condattr_destroy(&condattr); +fail_attr_init: + return ret; +} + +VkResult +wsi_display_init_wsi(struct wsi_device *wsi_device, + const VkAllocationCallbacks *alloc, + int display_fd) +{ + struct wsi_display *wsi = vk_zalloc(alloc, sizeof(*wsi), 8, + VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + VkResult result; + + if (!wsi) { + result = VK_ERROR_OUT_OF_HOST_MEMORY; + goto fail; + } + + wsi->fd = display_fd; + wsi->alloc = alloc; + + list_inithead(&wsi->connectors); + + int ret = pthread_mutex_init(&wsi->wait_mutex, NULL); + if (ret) { + result = VK_ERROR_OUT_OF_HOST_MEMORY; + goto fail_mutex; + } + + if (!wsi_init_pthread_cond_monotonic(&wsi->wait_cond)) { + result = VK_ERROR_OUT_OF_HOST_MEMORY; + goto fail_cond; + } + + 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_cond: + pthread_mutex_destroy(&wsi->wait_mutex); +fail_mutex: + vk_free(alloc, wsi); +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) { + wsi_for_each_connector(connector, wsi) { + wsi_for_each_display_mode(mode, connector) { + vk_free(wsi->alloc, mode); + } + vk_free(wsi->alloc, connector); + } + + 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); + + 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..9ca1fa6be86 --- /dev/null +++ b/src/vulkan/wsi/wsi_common_display.h @@ -0,0 +1,85 @@ +/* + * 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_display_create_display_mode(VkPhysicalDevice physical_device, + struct wsi_device *wsi_device, + VkDisplayKHR display, + const VkDisplayModeCreateInfoKHR *create_info, + const VkAllocationCallbacks *allocator, + VkDisplayModeKHR *mode); + +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_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 90941c8201b..3d502b9fc9d 100644 --- a/src/vulkan/wsi/wsi_common_private.h +++ b/src/vulkan/wsi/wsi_common_private.h @@ -141,6 +141,15 @@ 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, + int display_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 * \ |