The open source OpenXR runtime
at prediction-2 506 lines 14 kB view raw
1// Copyright 2019-2020, Collabora, Ltd. 2// SPDX-License-Identifier: BSL-1.0 3/*! 4 * @file 5 * @brief XCB window code. 6 * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com> 7 * @author Jakob Bornecrantz <jakob@collabora.com> 8 * @ingroup comp_main 9 */ 10 11#include <xcb/xcb.h> 12#include <xcb/randr.h> 13 14#include "util/u_misc.h" 15#include "xrt/xrt_compiler.h" 16#include "main/comp_window.h" 17 18 19/* 20 * 21 * Private structs. 22 * 23 */ 24 25/*! 26 * Xcb display, xrandr output. 27 */ 28struct comp_window_xcb_display 29{ 30 char *name; 31 struct 32 { 33 int16_t x; 34 int16_t y; 35 } position; 36 37 struct 38 { 39 uint16_t width; 40 uint16_t height; 41 } size; 42}; 43 44/*! 45 * A xcb connection and window. 46 * 47 * @implements comp_target_swapchain 48 */ 49struct comp_window_xcb 50{ 51 struct comp_target_swapchain base; 52 53 xcb_connection_t *connection; 54 xcb_window_t window; 55 xcb_screen_t *screen; 56 57 xcb_atom_t atom_wm_protocols; 58 xcb_atom_t atom_wm_delete_window; 59 60 struct comp_window_xcb_display *displays; 61 uint16_t display_count; 62}; 63 64 65/* 66 * 67 * Pre declare functions. 68 * 69 */ 70 71static void 72comp_window_xcb_destroy(struct comp_target *ct); 73 74static void 75comp_window_xcb_flush(struct comp_target *ct); 76 77XRT_MAYBE_UNUSED static void 78comp_window_xcb_list_screens(struct comp_window_xcb *w, xcb_screen_t *screen); 79 80static bool 81comp_window_xcb_init(struct comp_target *ct); 82 83static struct comp_window_xcb_display * 84comp_window_xcb_current_display(struct comp_window_xcb *w); 85 86static bool 87comp_window_xcb_init_swapchain(struct comp_target *ct, uint32_t width, uint32_t height); 88 89static int 90comp_window_xcb_connect(struct comp_window_xcb *w); 91 92static void 93comp_window_xcb_create_window(struct comp_window_xcb *w, VkExtent2D extent); 94 95static void 96comp_window_xcb_get_randr_outputs(struct comp_window_xcb *w); 97 98static void 99comp_window_xcb_connect_delete_event(struct comp_window_xcb *w); 100 101static void 102comp_window_xcb_set_full_screen(struct comp_window_xcb *w); 103 104static xcb_atom_t 105comp_window_xcb_get_atom(struct comp_window_xcb *w, const char *name); 106 107static VkResult 108comp_window_xcb_create_surface(struct comp_window_xcb *w, VkSurfaceKHR *out_surface); 109 110static void 111comp_window_xcb_update_window_title(struct comp_target *ct, const char *title); 112 113 114/* 115 * 116 * Functions. 117 * 118 */ 119 120static inline struct vk_bundle * 121get_vk(struct comp_window_xcb *cwx) 122{ 123 return &cwx->base.base.c->base.vk; 124} 125 126struct comp_target * 127comp_window_xcb_create(struct comp_compositor *c) 128{ 129 struct comp_window_xcb *w = U_TYPED_CALLOC(struct comp_window_xcb); 130 131 /* 132 * The display timing code has been tested on XCB, 133 * and is know to be broken when using VK_PRESENT_MODE_IMMEDIATE_KHR. 134 */ 135 comp_target_swapchain_init_and_set_fnptrs(&w->base, COMP_TARGET_FORCE_FAKE_DISPLAY_TIMING); 136 137 w->base.base.name = "xcb"; 138 w->base.display = VK_NULL_HANDLE; 139 w->base.base.destroy = comp_window_xcb_destroy; 140 w->base.base.flush = comp_window_xcb_flush; 141 w->base.base.init_pre_vulkan = comp_window_xcb_init; 142 w->base.base.init_post_vulkan = comp_window_xcb_init_swapchain; 143 w->base.base.set_title = comp_window_xcb_update_window_title; 144 w->base.base.c = c; 145 146 return &w->base.base; 147} 148 149static void 150comp_window_xcb_destroy(struct comp_target *ct) 151{ 152 struct comp_window_xcb *w_xcb = (struct comp_window_xcb *)ct; 153 154 comp_target_swapchain_cleanup(&w_xcb->base); 155 156 xcb_destroy_window(w_xcb->connection, w_xcb->window); 157 xcb_disconnect(w_xcb->connection); 158 159 for (uint16_t i = 0; i > w_xcb->display_count; i++) 160 free(w_xcb->displays[i].name); 161 162 free(w_xcb->displays); 163 164 free(ct); 165} 166 167static void 168comp_window_xcb_list_screens(struct comp_window_xcb *w, xcb_screen_t *screen) 169{ 170 COMP_DEBUG(w->base.base.c, "Screen 0 %dx%d", screen->width_in_pixels, screen->height_in_pixels); 171 comp_window_xcb_get_randr_outputs(w); 172 173 for (uint16_t i = 0; i < w->display_count; i++) { 174 struct comp_window_xcb_display *d = &w->displays[i]; 175 COMP_DEBUG(w->base.base.c, "%d: %s %dx%d [%d, %d]", i, d->name, d->size.width, d->size.height, 176 d->position.x, d->position.y); 177 } 178} 179 180static bool 181select_new_current_display(struct comp_target *ct) 182{ 183 struct comp_window_xcb *w_xcb = (struct comp_window_xcb *)ct; 184 for (uint32_t i = 0; i < w_xcb->display_count; i++) { 185 if (w_xcb->displays[i].size.width != 0 && w_xcb->displays[i].size.height != 0) { 186 w_xcb->base.base.c->settings.display = i; 187 COMP_DEBUG(ct->c, "Select new current display %d: %s", i, w_xcb->displays[i].name); 188 return true; 189 } 190 } 191 192 return false; 193} 194 195static bool 196comp_window_xcb_init(struct comp_target *ct) 197{ 198 struct comp_window_xcb *w_xcb = (struct comp_window_xcb *)ct; 199 200 if (!comp_window_xcb_connect(w_xcb)) { 201 return false; 202 } 203 204 xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(w_xcb->connection)); 205 206 w_xcb->screen = iter.data; 207 208 if (ct->c->settings.fullscreen) { 209 comp_window_xcb_get_randr_outputs(w_xcb); 210 211 if (ct->c->settings.display > (int)w_xcb->display_count - 1) { 212 COMP_DEBUG(ct->c, 213 "Requested display %d, but only %d " 214 "displays are available.", 215 ct->c->settings.display, w_xcb->display_count); 216 217 ct->c->settings.display = 0; 218 struct comp_window_xcb_display *d = comp_window_xcb_current_display(w_xcb); 219 COMP_DEBUG(ct->c, "Selecting '%s' instead.", d->name); 220 } 221 222 if (ct->c->settings.display == -1) 223 ct->c->settings.display = 0; 224 225 struct comp_window_xcb_display *d = comp_window_xcb_current_display(w_xcb); 226 227 if (d->size.width == 0 || d->size.height == 0) { 228 COMP_WARN(ct->c, "Selected display %d has no size", w_xcb->base.base.c->settings.display); 229 if (select_new_current_display(ct)) { 230 d = comp_window_xcb_current_display(w_xcb); 231 COMP_WARN(ct->c, "Falling back to display %d: %s", w_xcb->base.base.c->settings.display, 232 d->name); 233 } else { 234 COMP_ERROR(ct->c, "No suitable display found, disabling fullscreen"); 235 w_xcb->base.base.c->settings.fullscreen = false; 236 } 237 } 238 239 if (d->size.width != 0 && d->size.height != 0) { 240 COMP_DEBUG(ct->c, "Setting window size %dx%d.", d->size.width, d->size.height); 241 242 VkExtent2D extent = {d->size.width, d->size.height}; 243 comp_target_swapchain_override_extents(&w_xcb->base, extent); 244 } 245 } 246 247 // The extent of the window we are about to create. 248 VkExtent2D extent = {ct->c->settings.preferred.width, ct->c->settings.preferred.height}; 249 250 // If we require a particular size, use that. 251 if (w_xcb->base.override.compositor_extent) { 252 extent = w_xcb->base.override.extent; 253 } 254 255 // We can now create the window. 256 comp_window_xcb_create_window(w_xcb, extent); 257 258 comp_window_xcb_connect_delete_event(w_xcb); 259 260 if (ct->c->settings.fullscreen) { 261 comp_window_xcb_set_full_screen(w_xcb); 262 } 263 264 xcb_map_window(w_xcb->connection, w_xcb->window); 265 266 return true; 267} 268 269static struct comp_window_xcb_display * 270comp_window_xcb_current_display(struct comp_window_xcb *w) 271{ 272 return &w->displays[w->base.base.c->settings.display]; 273} 274 275static void 276comp_window_xcb_flush(struct comp_target *ct) 277{ 278 (void)ct; 279} 280 281static bool 282comp_window_xcb_init_swapchain(struct comp_target *ct, uint32_t width, uint32_t height) 283{ 284 struct comp_window_xcb *w_xcb = (struct comp_window_xcb *)ct; 285 VkResult ret; 286 287 ret = comp_window_xcb_create_surface(w_xcb, &w_xcb->base.surface.handle); 288 if (ret != VK_SUCCESS) { 289 return false; 290 } 291 292 return true; 293} 294 295static int 296comp_window_xcb_connect(struct comp_window_xcb *w) 297{ 298 w->connection = xcb_connect(NULL, NULL); 299 return !xcb_connection_has_error(w->connection); 300} 301 302static void 303comp_window_xcb_create_window(struct comp_window_xcb *w, VkExtent2D extent) 304{ 305 w->window = xcb_generate_id(w->connection); 306 307 int x = 0; 308 int y = 0; 309 310 if (w->base.base.c->settings.fullscreen) { 311 x = comp_window_xcb_current_display(w)->position.x; 312 y = comp_window_xcb_current_display(w)->position.y; 313 } 314 315 uint32_t value_list = XCB_EVENT_MASK_STRUCTURE_NOTIFY; 316 317 xcb_create_window( // 318 w->connection, // conn 319 XCB_COPY_FROM_PARENT, // depth 320 w->window, // wid 321 w->screen->root, // parent 322 x, // x 323 y, // y 324 extent.width, // width 325 extent.height, // height 326 0, // border_width 327 XCB_WINDOW_CLASS_INPUT_OUTPUT, // _class 328 w->screen->root_visual, // visual 329 XCB_CW_EVENT_MASK, // value_mask 330 &value_list); // value_list 331} 332 333static void 334comp_window_xcb_get_randr_outputs(struct comp_window_xcb *w) 335{ 336 xcb_randr_get_screen_resources_cookie_t resources_cookie = 337 xcb_randr_get_screen_resources(w->connection, w->screen->root); 338 xcb_randr_get_screen_resources_reply_t *resources_reply = 339 xcb_randr_get_screen_resources_reply(w->connection, resources_cookie, NULL); 340 xcb_randr_output_t *xcb_outputs = xcb_randr_get_screen_resources_outputs(resources_reply); 341 342 w->display_count = xcb_randr_get_screen_resources_outputs_length(resources_reply); 343 if (w->display_count < 1) 344 COMP_ERROR(w->base.base.c, "Failed to retrieve randr outputs"); 345 346 w->displays = calloc(w->display_count, sizeof(struct comp_window_xcb_display)); 347 348 for (int i = 0; i < w->display_count; i++) { 349 xcb_randr_get_output_info_cookie_t output_cookie = 350 xcb_randr_get_output_info(w->connection, xcb_outputs[i], XCB_CURRENT_TIME); 351 xcb_randr_get_output_info_reply_t *output_reply = 352 xcb_randr_get_output_info_reply(w->connection, output_cookie, NULL); 353 354 if (output_reply->connection != XCB_RANDR_CONNECTION_CONNECTED || output_reply->crtc == XCB_NONE) { 355 free(output_reply); 356 continue; 357 } 358 359 xcb_randr_get_crtc_info_cookie_t crtc_cookie = 360 xcb_randr_get_crtc_info(w->connection, output_reply->crtc, XCB_CURRENT_TIME); 361 xcb_randr_get_crtc_info_reply_t *crtc_reply = 362 xcb_randr_get_crtc_info_reply(w->connection, crtc_cookie, NULL); 363 364 uint8_t *name = xcb_randr_get_output_info_name(output_reply); 365 int name_len = xcb_randr_get_output_info_name_length(output_reply); 366 367 w->displays[i] = (struct comp_window_xcb_display){ 368 .name = U_TYPED_ARRAY_CALLOC(char, name_len + 1), 369 .position = {crtc_reply->x, crtc_reply->y}, 370 .size = {crtc_reply->width, crtc_reply->height}, 371 }; 372 373 memcpy(w->displays[i].name, name, name_len); 374 w->displays[i].name[name_len] = '\0'; 375 376 COMP_DEBUG(w->base.base.c, "randr output %d: %s: %dx%d", i, w->displays[i].name, crtc_reply->width, 377 crtc_reply->height); 378 379 free(crtc_reply); 380 free(output_reply); 381 } 382 383 free(resources_reply); 384} 385 386static void 387comp_window_xcb_connect_delete_event(struct comp_window_xcb *w) 388{ 389 w->atom_wm_protocols = comp_window_xcb_get_atom(w, "WM_PROTOCOLS"); 390 w->atom_wm_delete_window = comp_window_xcb_get_atom(w, "WM_DELETE_WINDOW"); 391 xcb_change_property(w->connection, XCB_PROP_MODE_REPLACE, w->window, w->atom_wm_protocols, XCB_ATOM_ATOM, 32, 1, 392 &w->atom_wm_delete_window); 393} 394 395static void 396comp_window_xcb_set_full_screen(struct comp_window_xcb *w) 397{ 398 xcb_atom_t atom_wm_state = comp_window_xcb_get_atom(w, "_NET_WM_STATE"); 399 xcb_atom_t atom_wm_fullscreen = comp_window_xcb_get_atom(w, "_NET_WM_STATE_FULLSCREEN"); 400 xcb_change_property(w->connection, XCB_PROP_MODE_REPLACE, w->window, atom_wm_state, XCB_ATOM_ATOM, 32, 1, 401 &atom_wm_fullscreen); 402} 403 404static xcb_atom_t 405comp_window_xcb_get_atom(struct comp_window_xcb *w, const char *name) 406{ 407 xcb_intern_atom_cookie_t cookie; 408 xcb_intern_atom_reply_t *reply; 409 xcb_atom_t atom; 410 411 cookie = xcb_intern_atom(w->connection, 0, strlen(name), name); 412 reply = xcb_intern_atom_reply(w->connection, cookie, NULL); 413 if (reply) { 414 atom = reply->atom; 415 } else { 416 atom = XCB_NONE; 417 } 418 419 free(reply); 420 return atom; 421} 422 423static VkResult 424comp_window_xcb_create_surface(struct comp_window_xcb *w, VkSurfaceKHR *out_surface) 425{ 426 struct vk_bundle *vk = get_vk(w); 427 VkResult ret; 428 429 VkXcbSurfaceCreateInfoKHR surface_info = { 430 .sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR, 431 .connection = w->connection, 432 .window = w->window, 433 }; 434 435 VkSurfaceKHR surface = VK_NULL_HANDLE; 436 ret = vk->vkCreateXcbSurfaceKHR( // 437 vk->instance, // 438 &surface_info, // 439 NULL, // 440 &surface); // 441 if (ret != VK_SUCCESS) { 442 COMP_ERROR(w->base.base.c, "vkCreateXcbSurfaceKHR: %s", vk_result_string(ret)); 443 return ret; 444 } 445 446 VK_NAME_SURFACE(vk, surface, "comp_window_xcb surface"); 447 *out_surface = surface; 448 449 return VK_SUCCESS; 450} 451 452static void 453comp_window_xcb_update_window_title(struct comp_target *ct, const char *title) 454{ 455 struct comp_window_xcb *w_xcb = (struct comp_window_xcb *)ct; 456 457 xcb_change_property(w_xcb->connection, XCB_PROP_MODE_REPLACE, w_xcb->window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 458 8, strlen(title), title); 459} 460 461 462/* 463 * 464 * Factory 465 * 466 */ 467 468static const char *instance_extensions[] = { 469 VK_KHR_XCB_SURFACE_EXTENSION_NAME, 470}; 471 472static bool 473detect(const struct comp_target_factory *ctf, struct comp_compositor *c) 474{ 475 return false; 476} 477 478static bool 479create_target(const struct comp_target_factory *ctf, struct comp_compositor *c, struct comp_target **out_ct) 480{ 481 struct comp_target *ct = comp_window_xcb_create(c); 482 if (ct == NULL) { 483 return false; 484 } 485 486 COMP_DEBUG(c, "Using VK_PRESENT_MODE_IMMEDIATE_KHR for xcb window") 487 c->settings.present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR; 488 489 *out_ct = ct; 490 491 return true; 492} 493 494const struct comp_target_factory comp_target_factory_xcb = { 495 .name = "X11(XCB) Windowed", 496 .identifier = "x11", 497 .requires_vulkan_for_create = false, 498 .is_deferred = true, 499 .required_instance_version = 0, 500 .required_instance_extensions = instance_extensions, 501 .required_instance_extension_count = ARRAY_SIZE(instance_extensions), 502 .optional_device_extensions = NULL, 503 .optional_device_extension_count = 0, 504 .detect = detect, 505 .create_target = create_target, 506};