/* * 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 "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 "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; 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; pthread_mutex_t wait_mutex; pthread_cond_t wait_cond; pthread_t wait_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 wsi_fence base; bool event_received; bool destroyed; uint64_t sequence; }; 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 = wsi_common_get_current_time(); /* 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; /* 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(wsi->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; } /* * 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 (properties == NULL) { return wsi_display_get_physical_device_display_properties2( physical_device, wsi_device, property_count, 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) * *property_count, 8, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); if (props2 == NULL) return VK_ERROR_OUT_OF_HOST_MEMORY; for (uint32_t i = 0; i < *property_count; i++) props2[i].sType = VK_STRUCTURE_TYPE_DISPLAY_PROPERTIES_2_KHR; VkResult result = wsi_display_get_physical_device_display_properties2( physical_device, wsi_device, property_count, props2); if (result == VK_SUCCESS || result == VK_INCOMPLETE) { for (uint32_t i = 0; i < *property_count; i++) properties[i] = props2[i].displayProperties; } vk_free(wsi->alloc, props2); return result; } } VkResult wsi_display_get_physical_device_display_properties2( VkPhysicalDevice physical_device, struct wsi_device *wsi_device, uint32_t *property_count, VkDisplayProperties2KHR *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 */ 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; } } 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) { 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); } VkResult wsi_display_get_physical_device_display_plane_properties2( VkPhysicalDevice physical_device, struct wsi_device *wsi_device, uint32_t *property_count, VkDisplayPlaneProperties2KHR *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) { wsi_display_fill_in_display_plane_properties(wsi_device, connector, prop); } } 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) */ 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); } 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) 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); } VkResult wsi_display_get_display_mode_properties2(VkPhysicalDevice physical_device, struct wsi_device *wsi_device, VkDisplayKHR display, uint32_t *property_count, VkDisplayModeProperties2KHR *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) 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) */ 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_get_display_plane_capabilities2( VkPhysicalDevice physical_device, struct wsi_device *wsi_device, const VkDisplayPlaneInfo2KHR *pDisplayPlaneInfo, VkDisplayPlaneCapabilities2KHR *capabilities) { assert(capabilities->sType == VK_STRUCTURE_TYPE_DISPLAY_PLANE_CAPABILITIES_2_KHR); VkResult result = wsi_get_display_plane_capabilities(physical_device, wsi_device, pDisplayPlaneInfo->mode, pDisplayPlaneInfo->planeIndex, &capabilities->capabilities); vk_foreach_struct(ext, capabilities->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; } 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, uint32_t queueFamilyIndex, VkBool32* pSupported) { *pSupported = VK_TRUE; 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; 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 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 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_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]); 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_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, 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_fence *fence_wsi, uint64_t timeout) { const struct wsi_device *wsi_device = fence_wsi->wsi_device; struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; struct wsi_display_fence *fence = (struct wsi_display_fence *) fence_wsi; wsi_display_debug("%9lu wait fence %lu %ld\n", pthread_self(), fence->sequence, (int64_t) (timeout - wsi_common_get_current_time())); wsi_display_debug_code(uint64_t start_ns = wsi_common_get_current_time()); pthread_mutex_lock(&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; } ret = wsi_display_wait_for_event(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(&wsi->wait_mutex); wsi_display_debug("%9lu fence wait %f ms\n", pthread_self(), ((int64_t) (wsi_common_get_current_time() - 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->base.alloc, fence); } static void wsi_display_fence_event_handler(struct wsi_display_fence *fence) { fence->event_received = true; wsi_display_fence_check_free(fence); } static void wsi_display_fence_destroy(struct wsi_fence *fence_wsi) { struct wsi_display_fence *fence = (struct wsi_display_fence *) fence_wsi; assert(!fence->destroyed); fence->destroyed = true; wsi_display_fence_check_free(fence); } static struct wsi_display_fence * wsi_display_fence_alloc(VkDevice device, const struct wsi_device *wsi_device, VkDisplayKHR display, const VkAllocationCallbacks *allocator) { struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; struct wsi_display_fence *fence = vk_zalloc2(wsi->alloc, allocator, sizeof (*fence), 8, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); if (!fence) return NULL; fence->base.device = device; fence->base.display = display; fence->base.wsi_device = wsi_device; fence->base.alloc = allocator ? allocator : wsi->alloc; fence->base.wait = wsi_display_fence_wait; fence->base.destroy = wsi_display_fence_destroy; fence->event_received = false; fence->destroyed = false; fence->sequence = ++fence_sequence; return fence; } 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; 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) { /* 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); 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 = 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_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_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); } } /* * Implement vkReleaseDisplay */ VkResult wsi_release_display(VkPhysicalDevice physical_device, struct wsi_device *wsi_device, VkDisplayKHR display) { struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; if (wsi->fd >= 0) { 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; } VkResult wsi_acquire_xlib_display(VkPhysicalDevice physical_device, struct wsi_device *wsi_device, Display *dpy, VkDisplayKHR display) { struct wsi_display *wsi = (struct wsi_display *) wsi_device->wsi[VK_ICD_WSI_PLATFORM_DISPLAY]; xcb_connection_t *connection = XGetXCBConnection(dpy); struct wsi_display_connector *connector = wsi_display_connector_from_handle(display); xcb_window_t root; /* 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; } VkResult wsi_get_randr_output_display(VkPhysicalDevice physical_device, struct wsi_device *wsi_device, Display *dpy, RROutput output, VkDisplayKHR *display) { xcb_connection_t *connection = XGetXCBConnection(dpy); struct wsi_display_connector *connector = wsi_display_get_output(wsi_device, connection, (xcb_randr_output_t) output); if (connector) *display = wsi_display_connector_to_handle(connector); else *display = VK_NULL_HANDLE; return VK_SUCCESS; } #endif /* VK_EXT_display_control */ VkResult wsi_display_power_control(VkDevice device, struct wsi_device *wsi_device, VkDisplayKHR display, const VkDisplayPowerInfoEXT *display_power_info) { 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 (display_power_info->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 wsi_fence **fence_p) { return VK_ERROR_FEATURE_NOT_PRESENT; } VkResult wsi_register_display_event(VkDevice device, struct wsi_device *wsi_device, VkDisplayKHR display, const VkDisplayEventInfoEXT *display_event_info, const VkAllocationCallbacks *allocator, struct wsi_fence **fence_p) { 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(device, wsi_device, display, allocator); 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) *fence_p = &fence->base; else if (fence != NULL) vk_free2(wsi->alloc, allocator, fence); break; default: ret = VK_ERROR_FEATURE_NOT_PRESENT; break; } return ret; } VkResult wsi_get_swapchain_counter(VkDevice device, struct wsi_device *wsi_device, VkSwapchainKHR _swapchain, VkSurfaceCounterFlagBitsEXT flag_bits, uint64_t *value) { 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) { *value = 0; return VK_SUCCESS; } int ret = drmCrtcGetSequence(wsi->fd, connector->crtc_id, value, NULL); if (ret) *value = 0; return VK_SUCCESS; }