/* * 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 #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBUDEV #include #endif #include "drm-uapi/drm_fourcc.h" #ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT #include #include #endif #include "util/hash_table.h" #include "util/list.h" #include "util/os_time.h" #include "vk_device.h" #include "vk_fence.h" #include "vk_instance.h" #include "vk_physical_device.h" #include "vk_sync.h" #include "vk_util.h" #include "wsi_common_entrypoints.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; uint32_t dpms_property; #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; int fd; /* Used with syncobj imported from driver side. */ int syncobj_fd; pthread_mutex_t wait_mutex; pthread_cond_t wait_cond; pthread_t wait_thread; pthread_cond_t hotplug_cond; pthread_t hotplug_thread; struct list_head connectors; /* list of all discovered 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]; }; struct wsi_display_fence { struct list_head link; struct wsi_display *wsi; bool event_received; bool destroyed; uint32_t syncobj; /* syncobj to signal on event */ uint64_t sequence; bool device_event; /* fence is used for device events */ }; struct wsi_display_sync { struct vk_sync sync; struct wsi_display_fence *fence; }; static uint64_t fence_sequence; 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_rel_to_abs_time(uint64_t rel_time) { uint64_t current_time = os_time_get_nano(); /* 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, int drm_fd, uint32_t connector_id) { struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; if (drm_fd < 0) return NULL; drmModeConnectorPtr drm_connector = drmModeGetConnector(drm_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; /* Look for a DPMS property if we haven't already found one */ for (int p = 0; connector->dpms_property == 0 && p < drm_connector->count_props; p++) { drmModePropertyPtr prop = drmModeGetProperty(drm_fd, drm_connector->props[p]); if (!prop) continue; if (prop->flags & DRM_MODE_PROP_ENUM) { if (!strcmp(prop->name, "DPMS")) connector->dpms_property = drm_connector->props[p]; } drmModeFreeProperty(prop); } /* 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, VkDisplayProperties2KHR *properties2) { assert(properties2->sType == VK_STRUCTURE_TYPE_DISPLAY_PROPERTIES_2_KHR); VkDisplayPropertiesKHR *properties = &properties2->displayProperties; 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; } VKAPI_ATTR VkResult VKAPI_CALL wsi_GetPhysicalDeviceDisplayPropertiesKHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, VkDisplayPropertiesKHR *pProperties) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; if (pProperties == NULL) { return wsi_GetPhysicalDeviceDisplayProperties2KHR(physicalDevice, pPropertyCount, NULL); } else { /* If we're actually returning properties, allocate a temporary array of * VkDisplayProperties2KHR structs, call properties2 to fill them out, * and then copy them to the client. This seems a bit expensive but * wsi_display_get_physical_device_display_properties2() calls * drmModeGetResources() which does an ioctl and then a bunch of * allocations so this should get lost in the noise. */ VkDisplayProperties2KHR *props2 = vk_zalloc(wsi->alloc, sizeof(*props2) * *pPropertyCount, 8, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); if (props2 == NULL) return VK_ERROR_OUT_OF_HOST_MEMORY; for (uint32_t i = 0; i < *pPropertyCount; i++) props2[i].sType = VK_STRUCTURE_TYPE_DISPLAY_PROPERTIES_2_KHR; VkResult result = wsi_GetPhysicalDeviceDisplayProperties2KHR(physicalDevice, pPropertyCount, props2); if (result == VK_SUCCESS || result == VK_INCOMPLETE) { for (uint32_t i = 0; i < *pPropertyCount; i++) pProperties[i] = props2[i].displayProperties; } vk_free(wsi->alloc, props2); return result; } } static VkResult wsi_get_connectors(VkPhysicalDevice physicalDevice) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; if (wsi->fd < 0) return VK_SUCCESS; drmModeResPtr mode_res = drmModeGetResources(wsi->fd); if (!mode_res) return VK_ERROR_OUT_OF_HOST_MEMORY; /* Get current information */ for (int c = 0; c < mode_res->count_connectors; c++) { struct wsi_display_connector *connector = wsi_display_get_connector(wsi_device, wsi->fd, mode_res->connectors[c]); if (!connector) { drmModeFreeResources(mode_res); return VK_ERROR_OUT_OF_HOST_MEMORY; } } drmModeFreeResources(mode_res); return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL wsi_GetPhysicalDeviceDisplayProperties2KHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, VkDisplayProperties2KHR *pProperties) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; /* Get current information */ VkResult result = wsi_get_connectors(physicalDevice); if (result != VK_SUCCESS) goto bail; VK_OUTARRAY_MAKE(conn, pProperties, pPropertyCount); wsi_for_each_connector(connector, wsi) { if (connector->connected) { vk_outarray_append(&conn, prop) { wsi_display_fill_in_display_properties(wsi_device, connector, prop); } } } return vk_outarray_status(&conn); bail: *pPropertyCount = 0; return result; } /* * Implement vkGetPhysicalDeviceDisplayPlanePropertiesKHR (VK_KHR_display */ static void wsi_display_fill_in_display_plane_properties( struct wsi_device *wsi_device, struct wsi_display_connector *connector, VkDisplayPlaneProperties2KHR *properties) { assert(properties->sType == VK_STRUCTURE_TYPE_DISPLAY_PLANE_PROPERTIES_2_KHR); VkDisplayPlanePropertiesKHR *prop = &properties->displayPlaneProperties; if (connector && connector->active) { prop->currentDisplay = wsi_display_connector_to_handle(connector); prop->currentStackIndex = 0; } else { prop->currentDisplay = VK_NULL_HANDLE; prop->currentStackIndex = 0; } } VKAPI_ATTR VkResult VKAPI_CALL wsi_GetPhysicalDeviceDisplayPlanePropertiesKHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, VkDisplayPlanePropertiesKHR *pProperties) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; VkResult result = wsi_get_connectors(physicalDevice); if (result != VK_SUCCESS) goto bail; VK_OUTARRAY_MAKE(conn, pProperties, pPropertyCount); wsi_for_each_connector(connector, wsi) { vk_outarray_append(&conn, prop) { VkDisplayPlaneProperties2KHR prop2 = { .sType = VK_STRUCTURE_TYPE_DISPLAY_PLANE_PROPERTIES_2_KHR, }; wsi_display_fill_in_display_plane_properties(wsi_device, connector, &prop2); *prop = prop2.displayPlaneProperties; } } return vk_outarray_status(&conn); bail: *pPropertyCount = 0; return result; } VKAPI_ATTR VkResult VKAPI_CALL wsi_GetPhysicalDeviceDisplayPlaneProperties2KHR(VkPhysicalDevice physicalDevice, uint32_t *pPropertyCount, VkDisplayPlaneProperties2KHR *pProperties) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; /* Get current information */ VkResult result = wsi_get_connectors(physicalDevice); if (result != VK_SUCCESS) goto bail; VK_OUTARRAY_MAKE(conn, pProperties, pPropertyCount); wsi_for_each_connector(connector, wsi) { vk_outarray_append(&conn, prop) { wsi_display_fill_in_display_plane_properties(wsi_device, connector, prop); } } return vk_outarray_status(&conn); bail: *pPropertyCount = 0; return result; } /* * Implement vkGetDisplayPlaneSupportedDisplaysKHR (VK_KHR_display) */ VKAPI_ATTR VkResult VKAPI_CALL wsi_GetDisplayPlaneSupportedDisplaysKHR(VkPhysicalDevice physicalDevice, uint32_t planeIndex, uint32_t *pDisplayCount, VkDisplayKHR *pDisplays) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; VK_OUTARRAY_MAKE(conn, pDisplays, pDisplayCount); int c = 0; wsi_for_each_connector(connector, wsi) { if (c == planeIndex && 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) */ static void wsi_display_fill_in_display_mode_properties( struct wsi_device *wsi_device, struct wsi_display_mode *display_mode, VkDisplayModeProperties2KHR *properties) { assert(properties->sType == VK_STRUCTURE_TYPE_DISPLAY_MODE_PROPERTIES_2_KHR); VkDisplayModePropertiesKHR *prop = &properties->displayModeProperties; 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); } VKAPI_ATTR VkResult VKAPI_CALL wsi_GetDisplayModePropertiesKHR(VkPhysicalDevice physicalDevice, VkDisplayKHR display, uint32_t *pPropertyCount, VkDisplayModePropertiesKHR *pProperties) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; struct wsi_display_connector *connector = wsi_display_connector_from_handle(display); VK_OUTARRAY_MAKE(conn, pProperties, pPropertyCount); wsi_for_each_display_mode(display_mode, connector) { if (!display_mode->valid) continue; vk_outarray_append(&conn, prop) { VkDisplayModeProperties2KHR prop2 = { .sType = VK_STRUCTURE_TYPE_DISPLAY_MODE_PROPERTIES_2_KHR, }; wsi_display_fill_in_display_mode_properties(wsi_device, display_mode, &prop2); *prop = prop2.displayModeProperties; } } return vk_outarray_status(&conn); } VKAPI_ATTR VkResult VKAPI_CALL wsi_GetDisplayModeProperties2KHR(VkPhysicalDevice physicalDevice, VkDisplayKHR display, uint32_t *pPropertyCount, VkDisplayModeProperties2KHR *pProperties) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; struct wsi_display_connector *connector = wsi_display_connector_from_handle(display); VK_OUTARRAY_MAKE(conn, pProperties, pPropertyCount); wsi_for_each_display_mode(display_mode, connector) { if (!display_mode->valid) continue; vk_outarray_append(&conn, prop) { wsi_display_fill_in_display_mode_properties(wsi_device, display_mode, prop); } } 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) */ VKAPI_ATTR VkResult VKAPI_CALL wsi_CreateDisplayModeKHR(VkPhysicalDevice physicalDevice, VkDisplayKHR display, const VkDisplayModeCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDisplayModeKHR *pMode) { struct wsi_display_connector *connector = wsi_display_connector_from_handle(display); if (pCreateInfo->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, &pCreateInfo->parameters)) { *pMode = wsi_display_mode_to_handle(display_mode); return VK_SUCCESS; } } } return VK_ERROR_INITIALIZATION_FAILED; } /* * Implement vkGetDisplayPlaneCapabilities */ VKAPI_ATTR VkResult VKAPI_CALL wsi_GetDisplayPlaneCapabilitiesKHR(VkPhysicalDevice physicalDevice, VkDisplayModeKHR _mode, uint32_t planeIndex, VkDisplayPlaneCapabilitiesKHR *pCapabilities) { struct wsi_display_mode *mode = wsi_display_mode_from_handle(_mode); /* XXX use actual values */ pCapabilities->supportedAlpha = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR; pCapabilities->minSrcPosition.x = 0; pCapabilities->minSrcPosition.y = 0; pCapabilities->maxSrcPosition.x = 0; pCapabilities->maxSrcPosition.y = 0; pCapabilities->minSrcExtent.width = mode->hdisplay; pCapabilities->minSrcExtent.height = mode->vdisplay; pCapabilities->maxSrcExtent.width = mode->hdisplay; pCapabilities->maxSrcExtent.height = mode->vdisplay; pCapabilities->minDstPosition.x = 0; pCapabilities->minDstPosition.y = 0; pCapabilities->maxDstPosition.x = 0; pCapabilities->maxDstPosition.y = 0; pCapabilities->minDstExtent.width = mode->hdisplay; pCapabilities->minDstExtent.height = mode->vdisplay; pCapabilities->maxDstExtent.width = mode->hdisplay; pCapabilities->maxDstExtent.height = mode->vdisplay; return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL wsi_GetDisplayPlaneCapabilities2KHR(VkPhysicalDevice physicalDevice, const VkDisplayPlaneInfo2KHR *pDisplayPlaneInfo, VkDisplayPlaneCapabilities2KHR *pCapabilities) { assert(pCapabilities->sType == VK_STRUCTURE_TYPE_DISPLAY_PLANE_CAPABILITIES_2_KHR); VkResult result = wsi_GetDisplayPlaneCapabilitiesKHR(physicalDevice, pDisplayPlaneInfo->mode, pDisplayPlaneInfo->planeIndex, &pCapabilities->capabilities); vk_foreach_struct(ext, pCapabilities->pNext) { switch (ext->sType) { case VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR: { VkSurfaceProtectedCapabilitiesKHR *protected = (void *)ext; protected->supportsProtected = VK_FALSE; break; } default: /* Ignored */ break; } } return result; } VKAPI_ATTR VkResult VKAPI_CALL wsi_CreateDisplayPlaneSurfaceKHR(VkInstance _instance, const VkDisplaySurfaceCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSurfaceKHR *pSurface) { VK_FROM_HANDLE(vk_instance, instance, _instance); VkIcdSurfaceDisplay *surface; assert(pCreateInfo->sType == VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR); surface = vk_zalloc2(&instance->alloc, pAllocator, 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 = pCreateInfo->displayMode; surface->planeIndex = pCreateInfo->planeIndex; surface->planeStackIndex = pCreateInfo->planeStackIndex; surface->transform = pCreateInfo->transform; surface->globalAlpha = pCreateInfo->globalAlpha; surface->alphaMode = pCreateInfo->alphaMode; surface->imageExtent = pCreateInfo->imageExtent; *pSurface = VkIcdSurfaceBase_to_handle(&surface->base); return VK_SUCCESS; } static VkResult wsi_display_surface_get_support(VkIcdSurfaceBase *surface, struct wsi_device *wsi_device, uint32_t queueFamilyIndex, VkBool32* pSupported) { struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; *pSupported = wsi->fd != -1; return VK_SUCCESS; } static VkResult wsi_display_surface_get_capabilities(VkIcdSurfaceBase *surface_base, struct wsi_device *wsi_device, 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; caps->minImageExtent = (VkExtent2D) { 1, 1 }; caps->maxImageExtent = (VkExtent2D) { wsi_device->maxImageDimension2D, wsi_device->maxImageDimension2D, }; 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_STORAGE_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; return VK_SUCCESS; } static VkResult wsi_display_surface_get_surface_counters( VkIcdSurfaceBase *surface_base, VkSurfaceCounterFlagsEXT *counters) { *counters = VK_SURFACE_COUNTER_VBLANK_EXT; return VK_SUCCESS; } static VkResult wsi_display_surface_get_capabilities2(VkIcdSurfaceBase *icd_surface, struct wsi_device *wsi_device, const void *info_next, VkSurfaceCapabilities2KHR *caps) { assert(caps->sType == VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR); VkResult result; result = wsi_display_surface_get_capabilities(icd_surface, wsi_device, &caps->surfaceCapabilities); if (result != VK_SUCCESS) return result; struct wsi_surface_supported_counters *counters = vk_find_struct( caps->pNext, WSI_SURFACE_SUPPORTED_COUNTERS_MESA); if (counters) { result = wsi_display_surface_get_surface_counters( icd_surface, &counters->supported_surface_counters); } return result; } 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 void get_sorted_vk_formats(struct wsi_device *wsi_device, VkFormat *sorted_formats) { for (unsigned i = 0; i < ARRAY_SIZE(available_surface_formats); i++) sorted_formats[i] = available_surface_formats[i].format; if (wsi_device->force_bgra8_unorm_first) { for (unsigned i = 0; i < ARRAY_SIZE(available_surface_formats); i++) { if (sorted_formats[i] == VK_FORMAT_B8G8R8A8_UNORM) { sorted_formats[i] = sorted_formats[0]; sorted_formats[0] = VK_FORMAT_B8G8R8A8_UNORM; break; } } } } 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); VkFormat sorted_formats[ARRAY_SIZE(available_surface_formats)]; get_sorted_vk_formats(wsi_device, sorted_formats); for (unsigned i = 0; i < ARRAY_SIZE(sorted_formats); i++) { vk_outarray_append(&out, f) { f->format = sorted_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); VkFormat sorted_formats[ARRAY_SIZE(available_surface_formats)]; get_sorted_vk_formats(wsi_device, sorted_formats); for (unsigned i = 0; i < ARRAY_SIZE(sorted_formats); i++) { vk_outarray_append(&out, f) { assert(f->sType == VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR); f->surfaceFormat.format = sorted_formats[i]; 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 VkResult wsi_display_surface_get_present_rectangles(VkIcdSurfaceBase *surface_base, struct wsi_device *wsi_device, uint32_t* pRectCount, VkRect2D* pRects) { VkIcdSurfaceDisplay *surface = (VkIcdSurfaceDisplay *) surface_base; wsi_display_mode *mode = wsi_display_mode_from_handle(surface->displayMode); VK_OUTARRAY_MAKE(out, pRects, pRectCount); if (wsi_device_matches_drm_fd(wsi_device, mode->connector->wsi->fd)) { vk_outarray_append(&out, rect) { *rect = (VkRect2D) { .offset = { 0, 0 }, .extent = { mode->hdisplay, mode->vdisplay }, }; } } return vk_outarray_status(&out); } static void wsi_display_destroy_buffer(struct wsi_display *wsi, uint32_t buffer) { (void) drmIoctl(wsi->fd, DRM_IOCTL_GEM_CLOSE, &((struct drm_gem_close) { .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_image(&chain->base, &chain->base.image_info, &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]); wsi_destroy_image_info(&chain->base, &chain->base.image_info); wsi_swapchain_finish(&chain->base); 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_fence_event_handler(struct wsi_display_fence *fence); 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 void wsi_display_vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { struct wsi_display_fence *fence = data; wsi_display_fence_event_handler(fence); } static void wsi_display_sequence_handler(int fd, uint64_t frame, uint64_t nsec, uint64_t user_data) { struct wsi_display_fence *fence = (struct wsi_display_fence *) (uintptr_t) user_data; wsi_display_fence_event_handler(fence); } 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 .vblank_handler = wsi_display_vblank_handler, .sequence_handler = wsi_display_sequence_handler, }; 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_cond_broadcast(&wsi->wait_cond); pthread_mutex_unlock(&wsi->wait_mutex); } } 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; } static void wsi_display_stop_wait_thread(struct wsi_display *wsi) { pthread_mutex_lock(&wsi->wait_mutex); if (wsi->wait_thread) { pthread_cancel(wsi->wait_thread); pthread_join(wsi->wait_thread, NULL); wsi->wait_thread = 0; } pthread_mutex_unlock(&wsi->wait_mutex); } static int cond_timedwait_ns(pthread_cond_t *cond, pthread_mutex_t *mutex, uint64_t timeout_ns) { struct timespec abs_timeout = { .tv_sec = timeout_ns / 1000000000ULL, .tv_nsec = timeout_ns % 1000000000ULL, }; int ret = pthread_cond_timedwait(cond, mutex, &abs_timeout); wsi_display_debug("%9ld done waiting for event %d\n", pthread_self(), ret); return ret; } /* * 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 = wsi_display_start_wait_thread(wsi); if (ret) return ret; return cond_timedwait_ns(&wsi->wait_cond, &wsi->wait_mutex, timeout_ns); } /* Wait for device event to be processed. * Call with wait_mutex held */ static int wsi_device_wait_for_event(struct wsi_display *wsi, uint64_t timeout_ns) { return cond_timedwait_ns(&wsi->hotplug_cond, &wsi->wait_mutex, timeout_ns); } static VkResult wsi_display_acquire_next_image(struct wsi_swapchain *drv_chain, const VkAcquireNextImageInfoKHR *info, 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; uint64_t timeout = info->timeout; 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_SURFACE_LOST_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(const 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_SURFACE_LOST_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_SURFACE_LOST_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_SURFACE_LOST_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_SURFACE_LOST_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; } static VkResult wsi_display_fence_wait(struct wsi_display_fence *fence, uint64_t timeout) { wsi_display_debug("%9lu wait fence %lu %ld\n", pthread_self(), fence->sequence, (int64_t) (timeout - os_time_get_nano())); wsi_display_debug_code(uint64_t start_ns = os_time_get_nano()); pthread_mutex_lock(&fence->wsi->wait_mutex); VkResult result; int ret = 0; for (;;) { if (fence->event_received) { wsi_display_debug("%9lu fence %lu passed\n", pthread_self(), fence->sequence); result = VK_SUCCESS; break; } if (ret == ETIMEDOUT) { wsi_display_debug("%9lu fence %lu timeout\n", pthread_self(), fence->sequence); result = VK_TIMEOUT; break; } if (fence->device_event) ret = wsi_device_wait_for_event(fence->wsi, timeout); else ret = wsi_display_wait_for_event(fence->wsi, timeout); if (ret && ret != ETIMEDOUT) { wsi_display_debug("%9lu fence %lu error\n", pthread_self(), fence->sequence); result = VK_ERROR_DEVICE_LOST; break; } } pthread_mutex_unlock(&fence->wsi->wait_mutex); wsi_display_debug("%9lu fence wait %f ms\n", pthread_self(), ((int64_t) (os_time_get_nano() - start_ns)) / 1.0e6); return result; } static void wsi_display_fence_check_free(struct wsi_display_fence *fence) { if (fence->event_received && fence->destroyed) vk_free(fence->wsi->alloc, fence); } static void wsi_display_fence_event_handler(struct wsi_display_fence *fence) { if (fence->syncobj) { (void) drmSyncobjSignal(fence->wsi->syncobj_fd, &fence->syncobj, 1); (void) drmSyncobjDestroy(fence->wsi->syncobj_fd, fence->syncobj); } fence->event_received = true; wsi_display_fence_check_free(fence); } static void wsi_display_fence_destroy(struct wsi_display_fence *fence) { /* Destroy hotplug fence list. */ if (fence->device_event) { mtx_lock(&fence->wsi->wait_mutex); list_del(&fence->link); mtx_unlock(&fence->wsi->wait_mutex); fence->event_received = true; } assert(!fence->destroyed); fence->destroyed = true; wsi_display_fence_check_free(fence); } static struct wsi_display_fence * wsi_display_fence_alloc(struct wsi_display *wsi, int sync_fd) { struct wsi_display_fence *fence = vk_zalloc(wsi->alloc, sizeof (*fence), 8, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); if (!fence) return NULL; if (sync_fd >= 0) { int ret = drmSyncobjFDToHandle(wsi->syncobj_fd, sync_fd, &fence->syncobj); if (ret) { vk_free(wsi->alloc, fence); return NULL; } } fence->wsi = wsi; fence->event_received = false; fence->destroyed = false; fence->sequence = ++fence_sequence; return fence; } static VkResult wsi_display_sync_init(struct vk_device *device, struct vk_sync *sync, uint64_t initial_value) { assert(initial_value == 0); return VK_SUCCESS; } static void wsi_display_sync_finish(struct vk_device *device, struct vk_sync *sync) { struct wsi_display_sync *wsi_sync = container_of(sync, struct wsi_display_sync, sync); if (wsi_sync->fence) wsi_display_fence_destroy(wsi_sync->fence); } static VkResult wsi_display_sync_wait(struct vk_device *device, struct vk_sync *sync, uint64_t wait_value, enum vk_sync_wait_flags wait_flags, uint64_t abs_timeout_ns) { struct wsi_display_sync *wsi_sync = container_of(sync, struct wsi_display_sync, sync); assert(wait_value == 0); assert(wait_flags == VK_SYNC_WAIT_COMPLETE); return wsi_display_fence_wait(wsi_sync->fence, abs_timeout_ns); } static const struct vk_sync_type wsi_display_sync_type = { .size = sizeof(struct wsi_display_sync), .features = VK_SYNC_FEATURE_BINARY | VK_SYNC_FEATURE_CPU_WAIT, .init = wsi_display_sync_init, .finish = wsi_display_sync_finish, .wait = wsi_display_sync_wait, }; static VkResult wsi_display_sync_create(struct vk_device *device, struct wsi_display_fence *fence, struct vk_sync **sync_out) { VkResult result = vk_sync_create(device, &wsi_display_sync_type, 0 /* flags */, 0 /* initial_value */, sync_out); if (result != VK_SUCCESS) return result; struct wsi_display_sync *sync = container_of(*sync_out, struct wsi_display_sync, sync); sync->fence = fence; return VK_SUCCESS; } static VkResult wsi_register_vblank_event(struct wsi_display_fence *fence, const struct wsi_device *wsi_device, VkDisplayKHR display, uint32_t flags, uint64_t frame_requested, uint64_t *frame_queued) { 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); if (wsi->fd < 0) return VK_ERROR_INITIALIZATION_FAILED; /* A display event may be registered before the first page flip at which * point crtc_id will be 0. If this is the case we setup the connector * here to allow drmCrtcQueueSequence to succeed. */ if (!connector->crtc_id) { VkResult ret = wsi_display_setup_connector(connector, connector->current_mode); if (ret != VK_SUCCESS) return VK_ERROR_INITIALIZATION_FAILED; } for (;;) { int ret = drmCrtcQueueSequence(wsi->fd, connector->crtc_id, flags, frame_requested, frame_queued, (uintptr_t) fence); if (!ret) return VK_SUCCESS; if (errno != ENOMEM) { /* Something unexpected happened. Pause for a moment so the * application doesn't just spin and then return a failure indication */ wsi_display_debug("queue vblank event %lu failed\n", fence->sequence); struct timespec delay = { .tv_sec = 0, .tv_nsec = 100000000ull, }; nanosleep(&delay, NULL); return VK_ERROR_OUT_OF_HOST_MEMORY; } /* The kernel event queue is full. Wait for some events to be * processed and try again */ pthread_mutex_lock(&wsi->wait_mutex); ret = wsi_display_wait_for_event(wsi, wsi_rel_to_abs_time(100000000ull)); pthread_mutex_unlock(&wsi->wait_mutex); if (ret) { wsi_display_debug("vblank queue full, event wait failed\n"); return VK_ERROR_OUT_OF_HOST_MEMORY; } } } /* * 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_SURFACE_LOST_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) { /* Disable the HW cursor as the app doesn't have a mechanism * to control it. * Refer to question 12 of the VK_KHR_display spec. */ ret = drmModeSetCursor(wsi->fd, connector->crtc_id, 0, 0, 0 ); if (ret != 0) { wsi_display_debug("failed to hide cursor err %d %s\n", ret, strerror(-ret)); } /* 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_SURFACE_LOST_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, 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, false); if (result != VK_SUCCESS) { vk_free(allocator, chain); return result; } 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 = wsi_swapchain_get_present_mode(wsi_device, create_info); chain->base.image_count = num_images; chain->wsi = wsi; chain->status = VK_SUCCESS; chain->surface = (VkIcdSurfaceDisplay *) icd_surface; result = wsi_configure_native_image(&chain->base, create_info, 0, NULL, NULL, NULL /* alloc_shm */, &chain->base.image_info); if (result != VK_SUCCESS) { vk_free(allocator, chain); goto fail_init_images; } 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]); } wsi_destroy_image_info(&chain->base, &chain->base.image_info); 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; } /* * Local version fo the libdrm helper. Added to avoid depending on bleeding * edge version of the library. */ static int local_drmIsMaster(int fd) { /* Detect master by attempting something that requires master. * * Authenticating magic tokens requires master and 0 is an * internal kernel detail which we could use. Attempting this on * a master fd would fail therefore fail with EINVAL because 0 * is invalid. * * A non-master fd will fail with EACCES, as the kernel checks * for master before attempting to do anything else. * * Since we don't want to leak implementation details, use * EACCES. */ return drmAuthMagic(fd, 0) != -EACCES; } #ifdef HAVE_LIBUDEV static void * udev_event_listener_thread(void *data) { struct wsi_device *wsi_device = data; struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; struct udev *u = udev_new(); if (!u) goto fail; struct udev_monitor *mon = udev_monitor_new_from_netlink(u, "udev"); if (!mon) goto fail_udev; int ret = udev_monitor_filter_add_match_subsystem_devtype(mon, "drm", "drm_minor"); if (ret < 0) goto fail_udev_monitor; ret = udev_monitor_enable_receiving(mon); if (ret < 0) goto fail_udev_monitor; int udev_fd = udev_monitor_get_fd(mon); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); for (;;) { nfds_t nfds = 1; struct pollfd fds[1] = { { .fd = udev_fd, .events = POLLIN, }, }; int ret = poll(fds, nfds, -1); if (ret > 0) { if (fds[0].revents & POLLIN) { struct udev_device *dev = udev_monitor_receive_device(mon); /* Ignore event if it is not a hotplug event */ if (!atoi(udev_device_get_property_value(dev, "HOTPLUG"))) continue; /* Note, this supports both drmSyncobjWait for fence->syncobj * and wsi_display_wait_for_event. */ mtx_lock(&wsi->wait_mutex); pthread_cond_broadcast(&wsi->hotplug_cond); list_for_each_entry(struct wsi_display_fence, fence, &wsi_device->hotplug_fences, link) { if (fence->syncobj) drmSyncobjSignal(wsi->syncobj_fd, &fence->syncobj, 1); fence->event_received = true; } mtx_unlock(&wsi->wait_mutex); udev_device_unref(dev); } } else if (ret < 0) { goto fail; } } udev_monitor_unref(mon); udev_unref(u); return 0; fail_udev_monitor: udev_monitor_unref(mon); fail_udev: udev_unref(u); fail: wsi_display_debug("critical hotplug thread error\n"); return 0; } #endif 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; if (wsi->fd != -1 && !local_drmIsMaster(wsi->fd)) wsi->fd = -1; wsi->syncobj_fd = wsi->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; } if (!wsi_init_pthread_cond_monotonic(&wsi->hotplug_cond)) { result = VK_ERROR_OUT_OF_HOST_MEMORY; goto fail_hotplug_cond; } wsi->base.get_support = wsi_display_surface_get_support; 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.get_present_rectangles = wsi_display_surface_get_present_rectangles; wsi->base.create_swapchain = wsi_display_surface_create_swapchain; wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY] = &wsi->base; return VK_SUCCESS; fail_hotplug_cond: pthread_cond_destroy(&wsi->wait_cond); 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); } wsi_display_stop_wait_thread(wsi); if (wsi->hotplug_thread) { pthread_cancel(wsi->hotplug_thread); pthread_join(wsi->hotplug_thread, NULL); } pthread_mutex_destroy(&wsi->wait_mutex); pthread_cond_destroy(&wsi->wait_cond); pthread_cond_destroy(&wsi->hotplug_cond); vk_free(alloc, wsi); } } /* * Implement vkReleaseDisplay */ VKAPI_ATTR VkResult VKAPI_CALL wsi_ReleaseDisplayEXT(VkPhysicalDevice physicalDevice, VkDisplayKHR display) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; if (wsi->fd >= 0) { wsi_display_stop_wait_thread(wsi); close(wsi->fd); wsi->fd = -1; } #ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT wsi_display_connector_from_handle(display)->output = None; #endif return VK_SUCCESS; } #ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT static struct wsi_display_connector * wsi_display_find_output(struct wsi_device *wsi_device, xcb_randr_output_t output) { struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; wsi_for_each_connector(connector, wsi) { 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, xcb_randr_output_t 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 */ for (xcb_screen_iterator_t 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); for (int o = 0; o < gsr_r->num_outputs; o++) { if (ro[o] == output) { root = iter.data->root; break; } } free(gsr_r); } return root; } static bool wsi_display_mode_matches_x(struct wsi_display_mode *wsi, xcb_randr_mode_info_t *xcb) { return wsi->clock == (xcb->dot_clock + 500) / 1000 && 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->vscan <= 1 && wsi->flags == xcb->mode_flags; } 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) { wsi_for_each_display_mode(display_mode, connector) { if (wsi_display_mode_matches_x(display_mode, mode)) return display_mode; } return NULL; } 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 = wsi_display_find_x_mode(wsi_device, connector, x_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 = preferred; display_mode->clock = (x_mode->dot_clock + 500) / 1000; /* kHz */ 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; display_mode->flags = x_mode->mode_flags; list_addtail(&display_mode->list, &connector->display_modes); return VK_SUCCESS; } static struct wsi_display_connector * wsi_display_get_output(struct wsi_device *wsi_device, xcb_connection_t *connection, xcb_randr_output_t 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 = wsi_display_output_to_root(connection, output); if (!root) return 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) { return NULL; } /* See if we already have a connector for this id */ connector = wsi_display_find_connector(wsi_device, connector_id); if (connector == NULL) { connector = wsi_display_alloc_connector(wsi, connector_id); if (!connector) { return NULL; } list_addtail(&connector->list, &wsi->connectors); } connector->output = output; } xcb_randr_get_screen_resources_cookie_t src = xcb_randr_get_screen_resources(connection, root); xcb_randr_get_output_info_cookie_t oic = xcb_randr_get_output_info(connection, output, XCB_CURRENT_TIME); xcb_randr_get_screen_resources_reply_t *srr = xcb_randr_get_screen_resources_reply(connection, src, NULL); xcb_randr_get_output_info_reply_t *oir = xcb_randr_get_output_info_reply(connection, oic, NULL); 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); xcb_randr_mode_t *x_modes = xcb_randr_get_output_info_modes(oir); for (int 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 */ for (int 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); if (num_outputs == 1 && outputs[0] == output) active_crtc = rc[c]; } 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; } VKAPI_ATTR VkResult VKAPI_CALL wsi_AcquireXlibDisplayEXT(VkPhysicalDevice physicalDevice, Display *dpy, VkDisplayKHR display) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; 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; /* XXX no support for multiple leases yet */ if (wsi->fd >= 0) return VK_ERROR_INITIALIZATION_FAILED; 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_INITIALIZATION_FAILED; } root = wsi_display_output_to_root(connection, connector->output); if (!root) return VK_ERROR_INITIALIZATION_FAILED; xcb_randr_crtc_t crtc = wsi_display_find_crtc_for_output(connection, root, connector->output); if (!crtc) return VK_ERROR_INITIALIZATION_FAILED; #ifdef HAVE_DRI3_MODIFIERS 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_INITIALIZATION_FAILED; 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_INITIALIZATION_FAILED; wsi->fd = fd; #endif return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL wsi_GetRandROutputDisplayEXT(VkPhysicalDevice physicalDevice, Display *dpy, RROutput rrOutput, VkDisplayKHR *pDisplay) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; xcb_connection_t *connection = XGetXCBConnection(dpy); struct wsi_display_connector *connector = wsi_display_get_output(wsi_device, connection, (xcb_randr_output_t) rrOutput); if (connector) *pDisplay = wsi_display_connector_to_handle(connector); else *pDisplay = VK_NULL_HANDLE; return VK_SUCCESS; } #endif /* VK_EXT_display_control */ VKAPI_ATTR VkResult VKAPI_CALL wsi_DisplayPowerControlEXT(VkDevice _device, VkDisplayKHR display, const VkDisplayPowerInfoEXT *pDisplayPowerInfo) { VK_FROM_HANDLE(vk_device, device, _device); struct wsi_device *wsi_device = device->physical->wsi_device; 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 mode; if (wsi->fd < 0) return VK_ERROR_INITIALIZATION_FAILED; switch (pDisplayPowerInfo->powerState) { case VK_DISPLAY_POWER_STATE_OFF_EXT: mode = DRM_MODE_DPMS_OFF; break; case VK_DISPLAY_POWER_STATE_SUSPEND_EXT: mode = DRM_MODE_DPMS_SUSPEND; break; default: mode = DRM_MODE_DPMS_ON; break; } drmModeConnectorSetProperty(wsi->fd, connector->id, connector->dpms_property, mode); return VK_SUCCESS; } VkResult wsi_register_device_event(VkDevice _device, struct wsi_device *wsi_device, const VkDeviceEventInfoEXT *device_event_info, const VkAllocationCallbacks *allocator, struct vk_sync **sync_out, int sync_fd) { VK_FROM_HANDLE(vk_device, device, _device); struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; VkResult ret = VK_SUCCESS; #ifdef HAVE_LIBUDEV /* Start listening for output change notifications. */ mtx_lock(&wsi->wait_mutex); if (!wsi->hotplug_thread) { if (pthread_create(&wsi->hotplug_thread, NULL, udev_event_listener_thread, wsi_device)) { mtx_unlock(&wsi->wait_mutex); return VK_ERROR_OUT_OF_HOST_MEMORY; } } mtx_unlock(&wsi->wait_mutex); #endif struct wsi_display_fence *fence; assert(device_event_info->deviceEvent == VK_DEVICE_EVENT_TYPE_DISPLAY_HOTPLUG_EXT); fence = wsi_display_fence_alloc(wsi, sync_fd); if (!fence) return VK_ERROR_OUT_OF_HOST_MEMORY; fence->device_event = true; mtx_lock(&wsi->wait_mutex); list_addtail(&fence->link, &wsi_device->hotplug_fences); mtx_unlock(&wsi->wait_mutex); if (sync_out) { ret = wsi_display_sync_create(device, fence, sync_out); if (ret != VK_SUCCESS) wsi_display_fence_destroy(fence); } else { wsi_display_fence_destroy(fence); } return ret; } VKAPI_ATTR VkResult VKAPI_CALL wsi_RegisterDeviceEventEXT(VkDevice _device, const VkDeviceEventInfoEXT *device_event_info, const VkAllocationCallbacks *allocator, VkFence *_fence) { VK_FROM_HANDLE(vk_device, device, _device); struct vk_fence *fence; VkResult ret; const VkFenceCreateInfo info = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = 0, }; ret = vk_fence_create(device, &info, allocator, &fence); if (ret != VK_SUCCESS) return ret; ret = wsi_register_device_event(_device, device->physical->wsi_device, device_event_info, allocator, &fence->temporary, -1); if (ret == VK_SUCCESS) *_fence = vk_fence_to_handle(fence); else vk_fence_destroy(device, fence, allocator); return ret; } VkResult wsi_register_display_event(VkDevice _device, struct wsi_device *wsi_device, VkDisplayKHR display, const VkDisplayEventInfoEXT *display_event_info, const VkAllocationCallbacks *allocator, struct vk_sync **sync_out, int sync_fd) { VK_FROM_HANDLE(vk_device, device, _device); struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; struct wsi_display_fence *fence; VkResult ret; switch (display_event_info->displayEvent) { case VK_DISPLAY_EVENT_TYPE_FIRST_PIXEL_OUT_EXT: fence = wsi_display_fence_alloc(wsi, sync_fd); if (!fence) return VK_ERROR_OUT_OF_HOST_MEMORY; ret = wsi_register_vblank_event(fence, wsi_device, display, DRM_CRTC_SEQUENCE_RELATIVE, 1, NULL); if (ret == VK_SUCCESS) { if (sync_out) { ret = wsi_display_sync_create(device, fence, sync_out); if (ret != VK_SUCCESS) wsi_display_fence_destroy(fence); } else { wsi_display_fence_destroy(fence); } } else if (fence != NULL) { if (fence->syncobj) drmSyncobjDestroy(wsi->syncobj_fd, fence->syncobj); vk_free2(wsi->alloc, allocator, fence); } break; default: ret = VK_ERROR_FEATURE_NOT_PRESENT; break; } return ret; } VKAPI_ATTR VkResult VKAPI_CALL wsi_RegisterDisplayEventEXT(VkDevice _device, VkDisplayKHR display, const VkDisplayEventInfoEXT *display_event_info, const VkAllocationCallbacks *allocator, VkFence *_fence) { VK_FROM_HANDLE(vk_device, device, _device); struct vk_fence *fence; VkResult ret; const VkFenceCreateInfo info = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = 0, }; ret = vk_fence_create(device, &info, allocator, &fence); if (ret != VK_SUCCESS) return ret; ret = wsi_register_display_event( _device, device->physical->wsi_device, display, display_event_info, allocator, &fence->temporary, -1); if (ret == VK_SUCCESS) *_fence = vk_fence_to_handle(fence); else vk_fence_destroy(device, fence, allocator); return ret; } void wsi_display_setup_syncobj_fd(struct wsi_device *wsi_device, int fd) { struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; wsi->syncobj_fd = fd; } VKAPI_ATTR VkResult VKAPI_CALL wsi_GetSwapchainCounterEXT(VkDevice _device, VkSwapchainKHR _swapchain, VkSurfaceCounterFlagBitsEXT counter, uint64_t *pCounterValue) { VK_FROM_HANDLE(vk_device, device, _device); struct wsi_device *wsi_device = device->physical->wsi_device; struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; struct wsi_display_swapchain *swapchain = (struct wsi_display_swapchain *) wsi_swapchain_from_handle(_swapchain); struct wsi_display_connector *connector = wsi_display_mode_from_handle(swapchain->surface->displayMode)->connector; if (wsi->fd < 0) return VK_ERROR_INITIALIZATION_FAILED; if (!connector->active) { *pCounterValue = 0; return VK_SUCCESS; } int ret = drmCrtcGetSequence(wsi->fd, connector->crtc_id, pCounterValue, NULL); if (ret) *pCounterValue = 0; return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL wsi_AcquireDrmDisplayEXT(VkPhysicalDevice physicalDevice, int32_t drmFd, VkDisplayKHR display) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; if (!wsi_device_matches_drm_fd(wsi_device, drmFd)) return VK_ERROR_UNKNOWN; struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; /* XXX no support for mulitple leases yet */ if (wsi->fd >= 0 || !local_drmIsMaster(drmFd)) return VK_ERROR_INITIALIZATION_FAILED; struct wsi_display_connector *connector = wsi_display_connector_from_handle(display); drmModeConnectorPtr drm_connector = drmModeGetConnectorCurrent(drmFd, connector->id); if (!drm_connector) return VK_ERROR_INITIALIZATION_FAILED; drmModeFreeConnector(drm_connector); wsi->fd = drmFd; return VK_SUCCESS; } VKAPI_ATTR VkResult VKAPI_CALL wsi_GetDrmDisplayEXT(VkPhysicalDevice physicalDevice, int32_t drmFd, uint32_t connectorId, VkDisplayKHR *pDisplay) { VK_FROM_HANDLE(vk_physical_device, pdevice, physicalDevice); struct wsi_device *wsi_device = pdevice->wsi_device; if (!wsi_device_matches_drm_fd(wsi_device, drmFd)) return VK_ERROR_UNKNOWN; struct wsi_display_connector *connector = wsi_display_get_connector(wsi_device, drmFd, connectorId); if (!connector) { *pDisplay = VK_NULL_HANDLE; return VK_ERROR_UNKNOWN; } *pDisplay = wsi_display_connector_to_handle(connector); return VK_SUCCESS; }