monado/src/xrt/compositor/main/comp_compositor.c
Christoph Haag 6e396ed07e comp: Add hack waiting on xrEndFrame() until frame should be displayed.
xrWaitFrame() doesn't block at all, will only predict the time for the next frame.
Once the compositor is multithreaded, this wait should be moved to the compositor thread
and xrWaitFrame should be the function to block until the compositor finished rendering the frame.
2019-08-15 12:29:58 +00:00

687 lines
18 KiB
C

// Copyright 2019, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Main compositor written using Vulkan implementation.
* @author Jakob Bornecrantz <jakob@collabora.com>
* @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
* @ingroup comp
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "util/u_debug.h"
#include "util/u_misc.h"
#include "util/u_time.h"
#include "main/comp_compositor.h"
#include "main/comp_client_interface.h"
#include <unistd.h>
static void
compositor_destroy(struct xrt_compositor *xc)
{
struct comp_compositor *c = comp_compositor(xc);
struct vk_bundle *vk = &c->vk;
COMP_DEBUG(c, "DESTROY");
if (c->r) {
comp_renderer_destroy(c->r);
c->r = NULL;
}
if (c->window != NULL) {
vk_swapchain_cleanup(&c->window->swapchain);
c->window->destroy(c->window);
c->window = NULL;
}
if (vk->cmd_pool != VK_NULL_HANDLE) {
vk->vkDestroyCommandPool(vk->device, vk->cmd_pool, NULL);
vk->cmd_pool = VK_NULL_HANDLE;
}
if (vk->device != VK_NULL_HANDLE) {
vk->vkDestroyDevice(vk->device, NULL);
vk->device = VK_NULL_HANDLE;
}
vk_destroy_validation_callback(vk);
if (vk->instance != VK_NULL_HANDLE) {
vk->vkDestroyInstance(vk->instance, NULL);
vk->instance = VK_NULL_HANDLE;
}
free(c);
}
static void
compositor_begin_session(struct xrt_compositor *xc, enum xrt_view_type type)
{
struct comp_compositor *c = comp_compositor(xc);
COMP_DEBUG(c, "BEGIN_SESSION");
}
static void
compositor_end_session(struct xrt_compositor *xc)
{
struct comp_compositor *c = comp_compositor(xc);
COMP_DEBUG(c, "END_SESSION");
}
static void
compositor_wait_frame(struct xrt_compositor *xc,
int64_t *predicted_display_time,
int64_t *predicted_display_period)
{
struct comp_compositor *c = comp_compositor(xc);
COMP_SPEW(c, "WAIT_FRAME");
*predicted_display_period = c->settings.nominal_frame_interval_ns;
*predicted_display_time =
c->last_frame_time_ns + c->settings.nominal_frame_interval_ns;
//! @todo set *predicted_display_time
// *predicted_display_time = 0;
// *predicted_display_period = 0;
}
static void
compositor_begin_frame(struct xrt_compositor *xc)
{
struct comp_compositor *c = comp_compositor(xc);
COMP_SPEW(c, "BEGIN_FRAME");
}
static void
compositor_discard_frame(struct xrt_compositor *xc)
{
struct comp_compositor *c = comp_compositor(xc);
COMP_SPEW(c, "DISCARD_FRAME");
}
static void
compositor_end_frame(struct xrt_compositor *xc,
enum xrt_blend_mode blend_mode,
struct xrt_swapchain **xscs,
uint32_t *image_index,
uint32_t *layers,
uint32_t num_swapchains)
{
struct comp_compositor *c = comp_compositor(xc);
COMP_SPEW(c, "END_FRAME");
struct comp_swapchain_image *right;
struct comp_swapchain_image *left;
//! HACK: Wait until the frame is predicted to be displayed.
// This needs improvement, but blocks for plausible timings.
uint64_t now = time_state_get_now(c->timekeeping);
uint64_t already_used = now - c->last_frame_time_ns;
if (already_used < c->settings.nominal_frame_interval_ns) {
uint64_t remaining_usec =
(c->settings.nominal_frame_interval_ns - already_used) /
1000;
remaining_usec -= 500; // HACK: leave a bit of time for overhead
// printf("Remaining usec before this frame is displayed :
// %lu\n", remaining_usec);
usleep(remaining_usec);
}
// Stereo!
if (num_swapchains == 2) {
left = &comp_swapchain(xscs[0])->images[image_index[0]];
right = &comp_swapchain(xscs[1])->images[image_index[1]];
comp_renderer_frame(c->r, left, layers[0], right, layers[1]);
} else {
COMP_ERROR(c, "non-stereo rendering not supported");
}
// Record the time of this frame.
c->last_frame_time_ns = time_state_get_now(c->timekeeping);
}
/*
*
* Vulkan functions.
*
*/
#define GET_DEV_PROC(c, name) \
(PFN_##name) c->vk.vkGetDeviceProcAddr(c->vk.device, #name);
#define GET_INS_PROC(c, name) \
(PFN_##name) c->vk.vkGetInstanceProcAddr(c->vk.instance, #name);
#define GET_DEV_PROC(c, name) \
(PFN_##name) c->vk.vkGetDeviceProcAddr(c->vk.device, #name);
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
vkGetInstanceProcAddr(VkInstance instance, const char *pName);
static VkResult
find_get_instance_proc_addr(struct comp_compositor *c)
{
//! @todo Do any library loading here.
return vk_get_loader_functions(&c->vk, vkGetInstanceProcAddr);
}
#ifdef XRT_ENABLE_VK_VALIDATION
#define COMPOSITOR_DEBUG_VULKAN_EXTENSIONS VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
#else
#define COMPOSITOR_DEBUG_VULKAN_EXTENSIONS
#endif
#define COMPOSITOR_COMMON_VULKAN_EXTENSIONS \
COMPOSITOR_DEBUG_VULKAN_EXTENSIONS \
VK_KHR_SURFACE_EXTENSION_NAME, \
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, \
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, \
VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, \
VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME
static const char *instance_extensions_none[] = {
COMPOSITOR_COMMON_VULKAN_EXTENSIONS};
#ifdef VK_USE_PLATFORM_XCB_KHR
static const char *instance_extensions_xcb[] = {
COMPOSITOR_COMMON_VULKAN_EXTENSIONS,
VK_KHR_XCB_SURFACE_EXTENSION_NAME,
};
#endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
static const char *instance_extensions_wayland[] = {
COMPOSITOR_COMMON_VULKAN_EXTENSIONS,
VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
};
#endif
#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT
static const char *instance_extensions_direct_mode[] = {
COMPOSITOR_COMMON_VULKAN_EXTENSIONS,
VK_KHR_DISPLAY_EXTENSION_NAME,
VK_EXT_DIRECT_MODE_DISPLAY_EXTENSION_NAME,
VK_EXT_ACQUIRE_XLIB_DISPLAY_EXTENSION_NAME,
};
#endif
static VkResult
select_instances_extensions(struct comp_compositor *c,
const char ***out_exts,
uint32_t *out_num)
{
switch (c->settings.window_type) {
case WINDOW_NONE:
*out_exts = instance_extensions_none;
*out_num = ARRAY_SIZE(instance_extensions_none);
break;
#ifdef VK_USE_PLATFORM_XCB_KHR
case WINDOW_XCB:
*out_exts = instance_extensions_xcb;
*out_num = ARRAY_SIZE(instance_extensions_xcb);
break;
#endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
case WINDOW_WAYLAND:
*out_exts = instance_extensions_wayland;
*out_num = ARRAY_SIZE(instance_extensions_wayland);
break;
#endif
#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT
case WINDOW_DIRECT_RANDR:
case WINDOW_DIRECT_NVIDIA:
*out_exts = instance_extensions_direct_mode;
*out_num = ARRAY_SIZE(instance_extensions_direct_mode);
break;
#endif
default: return VK_ERROR_INITIALIZATION_FAILED;
}
return VK_SUCCESS;
}
static VkResult
create_instance(struct comp_compositor *c)
{
const char **instance_extensions;
uint32_t num_extensions;
VkResult ret;
VkApplicationInfo app_info = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "Collabora Compositor",
.pEngineName = "Monado",
.apiVersion = VK_MAKE_VERSION(1, 0, 2),
};
ret = select_instances_extensions(c, &instance_extensions,
&num_extensions);
if (ret != VK_SUCCESS) {
COMP_ERROR(c, "Failed to select instance extensions: %s",
vk_result_string(ret));
return ret;
}
VkInstanceCreateInfo instance_info = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &app_info,
.enabledExtensionCount = num_extensions,
.ppEnabledExtensionNames = instance_extensions,
};
#ifdef XRT_ENABLE_VK_VALIDATION
const char *instance_layers[] = {
"VK_LAYER_LUNARG_standard_validation",
};
if (c->settings.validate_vulkan) {
instance_info.enabledLayerCount = ARRAY_SIZE(instance_layers);
instance_info.ppEnabledLayerNames = instance_layers;
}
#endif
ret = c->vk.vkCreateInstance(&instance_info, NULL, &c->vk.instance);
if (ret != VK_SUCCESS) {
COMP_ERROR(c, "vkCreateInstance: %s\n", vk_result_string(ret));
COMP_ERROR(c, "Failed to create Vulkan instance");
return ret;
}
ret = vk_get_instance_functions(&c->vk);
if (ret != VK_SUCCESS) {
COMP_ERROR(c, "Failed to get Vulkan instance functions: %s",
vk_result_string(ret));
return ret;
}
#ifdef XRT_ENABLE_VK_VALIDATION
if (c->settings.validate_vulkan)
vk_init_validation_callback(&c->vk);
#endif
return ret;
}
static bool
compositor_init_vulkan(struct comp_compositor *c)
{
VkResult ret;
ret = find_get_instance_proc_addr(c);
if (ret != VK_SUCCESS) {
return false;
}
ret = create_instance(c);
if (ret != VK_SUCCESS) {
return false;
}
ret = vk_create_device(&c->vk);
if (ret != VK_SUCCESS) {
return false;
}
ret = vk_init_cmd_pool(&c->vk);
if (ret != VK_SUCCESS) {
return false;
}
return true;
}
/*
*
* Other functions.
*
*/
void
comp_compositor_print(struct comp_compositor *c,
const char *func,
const char *fmt,
...)
{
fprintf(stderr, "%s - ", func);
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}
static bool
compositor_check_vulkan_caps(struct comp_compositor *c)
{
VkResult ret;
// this is duplicative, but seems to be the easiest way to 'pre-check'
// capabilities when window creation precedes vulkan instance creation.
// we also need to load the VK_KHR_DISPLAY extension.
if (c->settings.window_type != WINDOW_AUTO) {
COMP_DEBUG(c, "Skipping NVIDIA detection, window type forced.");
return true;
} else {
COMP_DEBUG(c, "Checking for NVIDIA vulkan driver.");
}
struct vk_bundle temp_vk = {0};
ret = vk_get_loader_functions(&temp_vk, vkGetInstanceProcAddr);
if (ret != VK_SUCCESS) {
return false;
}
const char *extension_names[] = {
VK_KHR_SURFACE_EXTENSION_NAME,
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME,
VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME,
VK_KHR_DISPLAY_EXTENSION_NAME,
};
VkInstanceCreateInfo instance_create_info = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pNext = NULL,
.enabledExtensionCount = ARRAY_SIZE(extension_names),
.ppEnabledExtensionNames = extension_names,
};
ret = temp_vk.vkCreateInstance(&instance_create_info, NULL,
&(temp_vk.instance));
if (ret != VK_SUCCESS) {
COMP_ERROR(c, "Failed to create VkInstance: %s",
vk_result_string(ret));
return false;
}
ret = vk_get_instance_functions(&temp_vk);
if (ret != VK_SUCCESS) {
COMP_ERROR(c, "Failed to get Vulkan instance functions: %s",
vk_result_string(ret));
return false;
}
// follow same device selection logic as subsequent calls
ret = vk_create_device(&temp_vk);
if (ret != VK_SUCCESS) {
COMP_ERROR(c, "Failed to create VkDevice: %s",
vk_result_string(ret));
return false;
}
bool nvidia_tests_passed = false;
VkPhysicalDeviceProperties physical_device_properties;
temp_vk.vkGetPhysicalDeviceProperties(temp_vk.physical_device,
&physical_device_properties);
if (physical_device_properties.vendorID == 0x10DE) {
// our physical device is an nvidia card, we can potentially
// select nvidia-specific direct mode.
// we need to also check if we are confident that we can create
// a direct mode display, if not we need to abandon the attempt
// here, and allow desktop-window fallback to occur.
// get a list of attached displays
uint32_t display_count;
if (temp_vk.vkGetPhysicalDeviceDisplayPropertiesKHR(
temp_vk.physical_device, &display_count, NULL) !=
VK_SUCCESS) {
COMP_ERROR(c, "Failed to get vulkan display count");
nvidia_tests_passed = false;
}
VkDisplayPropertiesKHR *display_props =
U_TYPED_ARRAY_CALLOC(VkDisplayPropertiesKHR, display_count);
if (display_props &&
temp_vk.vkGetPhysicalDeviceDisplayPropertiesKHR(
temp_vk.physical_device, &display_count,
display_props) != VK_SUCCESS) {
COMP_ERROR(c, "Failed to get display properties");
nvidia_tests_passed = false;
}
for (uint32_t i = 0; i < display_count; i++) {
VkDisplayPropertiesKHR disp = *(display_props + i);
// check this display against our whitelist
uint32_t wl_elements = sizeof(NV_DIRECT_WHITELIST) /
sizeof(NV_DIRECT_WHITELIST[0]);
for (uint32_t j = 0; j < wl_elements; j++) {
unsigned long wl_entry_length =
strlen(NV_DIRECT_WHITELIST[j]);
unsigned long disp_entry_length =
strlen(disp.displayName);
if (disp_entry_length >= wl_entry_length) {
if (strncmp(NV_DIRECT_WHITELIST[j],
disp.displayName,
wl_entry_length) == 0) {
// we have a match with this
// whitelist entry.
nvidia_tests_passed = true;
}
}
}
}
free(display_props);
}
if (nvidia_tests_passed) {
c->settings.window_type = WINDOW_DIRECT_NVIDIA;
COMP_DEBUG(c, "Selecting direct NVIDIA window type!");
} else {
COMP_DEBUG(c, "Keeping auto window type!");
}
temp_vk.vkDestroyDevice(temp_vk.device, NULL);
temp_vk.vkDestroyInstance(temp_vk.instance, NULL);
return true;
}
static bool
compositor_try_window(struct comp_compositor *c, struct comp_window *window)
{
if (window == NULL) {
return false;
}
if (!window->init(window)) {
window->destroy(window);
return false;
} else {
COMP_DEBUG(c, "Window backend %s initialized!", window->name);
c->window = window;
return true;
}
}
static bool
compositor_init_window_pre_vulkan(struct comp_compositor *c)
{
// Setup the initial width from the settings.
c->current.width = c->settings.width;
c->current.height = c->settings.height;
// Nothing to do for nvidia.
if (c->settings.window_type == WINDOW_DIRECT_NVIDIA) {
return true;
}
switch (c->settings.window_type) {
case WINDOW_AUTO:
#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT
if (compositor_try_window(c, comp_window_direct_create(c))) {
c->settings.window_type = WINDOW_DIRECT_RANDR;
return true;
}
#endif
#ifdef VK_USE_PLATFORM_XCB_KHR
if (compositor_try_window(c, comp_window_xcb_create(c))) {
c->settings.window_type = WINDOW_XCB;
return true;
}
#endif
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
if (compositor_try_window(c, comp_window_wayland_create(c))) {
c->settings.window_type = WINDOW_WAYLAND;
return true;
}
#endif
COMP_ERROR(c, "Failed to auto detect window support!");
break;
case WINDOW_XCB:
#ifdef VK_USE_PLATFORM_XCB_KHR
compositor_try_window(c, comp_window_xcb_create(c));
#else
COMP_ERROR(c, "XCB support not compiled in!");
#endif
break;
case WINDOW_WAYLAND:
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
compositor_try_window(c, comp_window_wayland_create(c));
#else
COMP_ERROR(c, "Wayland support not compiled in!");
#endif
break;
case WINDOW_DIRECT_RANDR:
#ifdef VK_USE_PLATFORM_XLIB_XRANDR_EXT
compositor_try_window(c, comp_window_direct_create(c));
#else
COMP_ERROR(c, "Direct mode support not compiled in!");
#endif
break;
default: COMP_ERROR(c, "Unknown window type!"); break;
}
// Failed to create?
if (c->window == NULL) {
return false;
}
return true;
}
static bool
compositor_init_window_post_vulkan(struct comp_compositor *c)
{
if (c->settings.window_type != WINDOW_DIRECT_NVIDIA) {
return true;
}
return compositor_try_window(c, comp_window_direct_create(c));
}
static void
_sc_dimension_cb(uint32_t width, uint32_t height, void *ptr)
{
struct comp_compositor *c = (struct comp_compositor *)ptr;
COMP_DEBUG(c, "_sc_dimension_cb %dx%d", width, height);
c->current.width = width;
c->current.height = height;
}
static bool
compositor_init_swapchain(struct comp_compositor *c)
{
//! @todo Make c->window->init_swachain call vk_swapchain_init
//! and give
//! _sc_dimension_cb to window or just have it call a
//! function?
vk_swapchain_init(&c->window->swapchain, &c->vk, _sc_dimension_cb,
(void *)c);
if (!c->window->init_swapchain(c->window, c->current.width,
c->current.height)) {
COMP_ERROR(c, "Window init_swapchain failed!");
goto err_destroy;
}
return true;
// Error path.
err_destroy:
c->window->destroy(c->window);
c->window = NULL;
return false;
}
static bool
compositor_init_renderer(struct comp_compositor *c)
{
c->r = comp_renderer_create(c);
if (c->r == NULL) {
return false;
}
return true;
}
struct xrt_compositor_fd *
comp_compositor_create(struct xrt_device *xdev,
struct time_state *timekeeping,
bool flip_y)
{
struct comp_compositor *c = U_TYPED_CALLOC(struct comp_compositor);
c->base.base.create_swapchain = comp_swapchain_create;
c->base.base.begin_session = compositor_begin_session;
c->base.base.end_session = compositor_end_session;
c->base.base.wait_frame = compositor_wait_frame;
c->base.base.begin_frame = compositor_begin_frame;
c->base.base.discard_frame = compositor_discard_frame;
c->base.base.end_frame = compositor_end_frame;
c->base.base.destroy = compositor_destroy;
c->xdev = xdev;
c->timekeeping = timekeeping;
COMP_DEBUG(c, "Doing init %p", (void *)c);
// Init the settings to default.
comp_settings_init(&c->settings, xdev);
c->settings.flip_y = flip_y;
c->last_frame_time_ns = time_state_get_now(c->timekeeping);
// Need to select window backend before creating Vulkan, then
// swapchain will initialize the window fully and the swapchain,
// and finally the renderer is created which renderers to
// window/swapchain.
// clang-format off
if (!compositor_check_vulkan_caps(c) ||
!compositor_init_window_pre_vulkan(c) ||
!compositor_init_vulkan(c) ||
!compositor_init_window_post_vulkan(c) ||
!compositor_init_swapchain(c) ||
!compositor_init_renderer(c)) {
COMP_DEBUG(c, "Failed to init compositor %p", (void *)c);
c->base.base.destroy(&c->base.base);
return NULL;
}
// clang-format on
COMP_DEBUG(c, "Done %p", (void *)c);
return &c->base;
}