The open source OpenXR runtime
1// Copyright 2019-2024, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief Null compositor implementation.
6 *
7 * Originally based on src/xrt/compositor/main/comp_compositor.c
8 *
9 * @author Jakob Bornecrantz <jakob@collabora.com>
10 * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
11 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
12 * @ingroup comp_null
13 */
14
15#include "null_compositor.h"
16#include "null_interfaces.h"
17
18#include "os/os_time.h"
19
20#include "util/u_misc.h"
21#include "util/u_pacing.h"
22#include "util/u_time.h"
23#include "util/u_debug.h"
24#include "util/u_verify.h"
25#include "util/u_handles.h"
26#include "util/u_trace_marker.h"
27
28#include "util/comp_vulkan.h"
29
30#include "multi/comp_multi_interface.h"
31#include "xrt/xrt_compositor.h"
32#include "xrt/xrt_device.h"
33
34
35#include <stdint.h>
36#include <stdio.h>
37
38static const uint64_t RECOMMENDED_VIEW_WIDTH = 320;
39static const uint64_t RECOMMENDED_VIEW_HEIGHT = 240;
40
41static const uint64_t MAX_VIEW_WIDTH = 1920;
42static const uint64_t MAX_VIEW_HEIGHT = 1080;
43
44DEBUG_GET_ONCE_LOG_OPTION(log, "XRT_COMPOSITOR_LOG", U_LOGGING_INFO)
45
46
47/*
48 *
49 * Helper functions.
50 *
51 */
52
53static struct vk_bundle *
54get_vk(struct null_compositor *c)
55{
56 return &c->base.vk;
57}
58
59
60/*
61 *
62 * Vulkan extensions.
63 *
64 */
65
66static const char *instance_extensions_common[] = {
67 VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME, //
68 VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, //
69 VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME, //
70 VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, //
71};
72
73static const char *required_device_extensions[] = {
74 VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, //
75 VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME, //
76 VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, //
77 VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, //
78 VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, //
79
80// Platform version of "external_memory"
81#if defined(XRT_GRAPHICS_BUFFER_HANDLE_IS_FD)
82 VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
83
84#elif defined(XRT_GRAPHICS_BUFFER_HANDLE_IS_AHARDWAREBUFFER)
85 VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
86 VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
87 VK_KHR_MAINTENANCE_1_EXTENSION_NAME,
88 VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
89 VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
90
91#elif defined(XRT_GRAPHICS_BUFFER_HANDLE_IS_WIN32_HANDLE)
92 VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME,
93
94#else
95#error "Need port!"
96#endif
97
98// Platform version of "external_fence" and "external_semaphore"
99#if defined(XRT_GRAPHICS_SYNC_HANDLE_IS_FD) // Optional
100
101#elif defined(XRT_GRAPHICS_SYNC_HANDLE_IS_WIN32_HANDLE)
102 VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME, //
103 VK_KHR_EXTERNAL_FENCE_WIN32_EXTENSION_NAME, //
104
105#else
106#error "Need port!"
107#endif
108};
109
110static const char *optional_device_extensions[] = {
111 VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, //
112 VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, //
113
114// Platform version of "external_fence" and "external_semaphore"
115#if defined(XRT_GRAPHICS_SYNC_HANDLE_IS_FD) // Optional
116 VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, //
117 VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME, //
118
119#elif defined(XRT_GRAPHICS_SYNC_HANDLE_IS_WIN32_HANDLE) // Not optional
120
121#else
122#error "Need port!"
123#endif
124
125#ifdef VK_KHR_global_priority
126 VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME,
127#endif
128#ifdef VK_KHR_image_format_list
129 VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME,
130#endif
131#ifdef VK_KHR_maintenance1
132 VK_KHR_MAINTENANCE_1_EXTENSION_NAME,
133#endif
134#ifdef VK_KHR_maintenance2
135 VK_KHR_MAINTENANCE_2_EXTENSION_NAME,
136#endif
137#ifdef VK_KHR_timeline_semaphore
138 VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME,
139#endif
140#ifdef VK_EXT_calibrated_timestamps
141 VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME,
142#endif
143#ifdef VK_EXT_robustness2
144 VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
145#endif
146};
147
148static VkResult
149select_instances_extensions(struct null_compositor *c, struct u_string_list *required, struct u_string_list *optional)
150{
151#ifdef VK_EXT_display_surface_counter
152 u_string_list_append(optional, VK_EXT_DISPLAY_SURFACE_COUNTER_EXTENSION_NAME);
153#endif
154
155 return VK_SUCCESS;
156}
157
158static bool
159compositor_init_vulkan(struct null_compositor *c)
160{
161 struct vk_bundle *vk = get_vk(c);
162 VkResult ret;
163
164 // every backend needs at least the common extensions
165 struct u_string_list *required_instance_ext_list =
166 u_string_list_create_from_array(instance_extensions_common, ARRAY_SIZE(instance_extensions_common));
167
168 struct u_string_list *optional_instance_ext_list = u_string_list_create();
169
170 ret = select_instances_extensions(c, required_instance_ext_list, optional_instance_ext_list);
171 if (ret != VK_SUCCESS) {
172 VK_ERROR(vk, "select_instances_extensions: %s\n\tFailed to select instance extensions.",
173 vk_result_string(ret));
174 u_string_list_destroy(&required_instance_ext_list);
175 u_string_list_destroy(&optional_instance_ext_list);
176 return ret;
177 }
178
179 struct u_string_list *required_device_extension_list =
180 u_string_list_create_from_array(required_device_extensions, ARRAY_SIZE(required_device_extensions));
181
182 struct u_string_list *optional_device_extension_list =
183 u_string_list_create_from_array(optional_device_extensions, ARRAY_SIZE(optional_device_extensions));
184
185 struct comp_vulkan_arguments vk_args = {
186 .get_instance_proc_address = vkGetInstanceProcAddr,
187 .required_instance_version = VK_MAKE_VERSION(1, 0, 0),
188 .required_instance_extensions = required_instance_ext_list,
189 .optional_instance_extensions = optional_instance_ext_list,
190 .required_device_extensions = required_device_extension_list,
191 .optional_device_extensions = optional_device_extension_list,
192 .log_level = c->settings.log_level,
193 .only_compute_queue = false, // Regular GFX
194 .selected_gpu_index = -1, // Auto
195 .client_gpu_index = -1, // Auto
196 .timeline_semaphore = true, // Flag is optional, not a hard requirement.
197 };
198
199 struct comp_vulkan_results vk_res = {0};
200 bool bundle_ret = comp_vulkan_init_bundle(vk, &vk_args, &vk_res);
201
202 u_string_list_destroy(&required_instance_ext_list);
203 u_string_list_destroy(&optional_instance_ext_list);
204 u_string_list_destroy(&required_device_extension_list);
205 u_string_list_destroy(&optional_device_extension_list);
206
207 if (!bundle_ret) {
208 return false;
209 }
210
211 // clang-format off
212 static_assert(ARRAY_SIZE(vk_res.client_gpu_deviceUUID.data) == XRT_UUID_SIZE, "array size mismatch");
213 static_assert(ARRAY_SIZE(vk_res.selected_gpu_deviceUUID.data) == XRT_UUID_SIZE, "array size mismatch");
214 static_assert(ARRAY_SIZE(vk_res.client_gpu_deviceUUID.data) == ARRAY_SIZE(c->sys_info.client_vk_deviceUUID.data), "array size mismatch");
215 static_assert(ARRAY_SIZE(vk_res.selected_gpu_deviceUUID.data) == ARRAY_SIZE(c->sys_info.compositor_vk_deviceUUID.data), "array size mismatch");
216 static_assert(ARRAY_SIZE(vk_res.client_gpu_deviceLUID.data) == XRT_LUID_SIZE, "array size mismatch");
217 static_assert(ARRAY_SIZE(vk_res.client_gpu_deviceLUID.data) == ARRAY_SIZE(c->sys_info.client_d3d_deviceLUID.data), "array size mismatch");
218 // clang-format on
219
220 c->sys_info.client_vk_deviceUUID = vk_res.client_gpu_deviceUUID;
221 c->sys_info.compositor_vk_deviceUUID = vk_res.selected_gpu_deviceUUID;
222 c->sys_info.client_d3d_deviceLUID = vk_res.client_gpu_deviceLUID;
223 c->sys_info.client_d3d_deviceLUID_valid = vk_res.client_gpu_deviceLUID_valid;
224
225 // Tie the lifetimes of swapchains to Vulkan.
226 xrt_result_t xret = comp_swapchain_shared_init(&c->base.cscs, vk);
227 if (xret != XRT_SUCCESS) {
228 return false;
229 }
230
231 return true;
232}
233
234
235/*
236 *
237 * Other init functions.
238 *
239 */
240
241static bool
242compositor_init_pacing(struct null_compositor *c)
243{
244 xrt_result_t xret = u_pc_fake_create(c->settings.frame_interval_ns, os_monotonic_get_ns(), &c->upc);
245 if (xret != XRT_SUCCESS) {
246 NULL_ERROR(c, "Failed to create fake pacing helper!");
247 return false;
248 }
249
250 return true;
251}
252
253static bool
254compositor_init_info(struct null_compositor *c)
255{
256 struct xrt_compositor_info *info = &c->base.base.base.info;
257
258 struct comp_vulkan_formats formats = {0};
259 comp_vulkan_formats_check(get_vk(c), &formats);
260 comp_vulkan_formats_copy_to_info(&formats, info);
261 comp_vulkan_formats_log(c->settings.log_level, &formats);
262
263 return true;
264}
265
266static bool
267compositor_init_sys_info(struct null_compositor *c, struct xrt_device *xdev)
268{
269 struct xrt_system_compositor_info *sys_info = &c->sys_info;
270
271 // Required by OpenXR spec.
272 sys_info->max_layers = XRT_MAX_LAYERS;
273
274 // UUIDs and LUID already set in vk init.
275 (void)sys_info->compositor_vk_deviceUUID;
276 (void)sys_info->client_vk_deviceUUID;
277 (void)sys_info->client_d3d_deviceLUID;
278 (void)sys_info->client_d3d_deviceLUID_valid;
279 uint32_t view_count = xdev->hmd->view_count;
280 // clang-format off
281 for (uint32_t i = 0; i < view_count; ++i) {
282 sys_info->views[i].recommended.width_pixels = RECOMMENDED_VIEW_WIDTH;
283 sys_info->views[i].recommended.height_pixels = RECOMMENDED_VIEW_HEIGHT;
284 sys_info->views[i].recommended.sample_count = 1;
285 sys_info->views[i].max.width_pixels = MAX_VIEW_WIDTH;
286 sys_info->views[i].max.height_pixels = MAX_VIEW_HEIGHT;
287 sys_info->views[i].max.sample_count = 1;
288 }
289 // clang-format on
290
291 // Copy the list directly.
292 assert(xdev->hmd->blend_mode_count <= XRT_MAX_DEVICE_BLEND_MODES);
293 assert(xdev->hmd->blend_mode_count != 0);
294 assert(xdev->hmd->blend_mode_count <= ARRAY_SIZE(sys_info->supported_blend_modes));
295 for (size_t i = 0; i < xdev->hmd->blend_mode_count; ++i) {
296 assert(u_verify_blend_mode_valid(xdev->hmd->blend_modes[i]));
297 sys_info->supported_blend_modes[i] = xdev->hmd->blend_modes[i];
298 }
299 sys_info->supported_blend_mode_count = (uint8_t)xdev->hmd->blend_mode_count;
300
301 // Refresh rates.
302 sys_info->refresh_rate_count = 1;
303 sys_info->refresh_rates_hz[0] = (float)(1. / time_ns_to_s(c->settings.frame_interval_ns));
304
305 return true;
306}
307
308
309/*
310 *
311 * Member functions.
312 *
313 */
314
315static xrt_result_t
316null_compositor_begin_session(struct xrt_compositor *xc, const struct xrt_begin_session_info *type)
317{
318 struct null_compositor *c = null_compositor(xc);
319 NULL_DEBUG(c, "BEGIN_SESSION");
320
321 /*
322 * No logic needed here for the null compositor, if using the null
323 * compositor as a base for a new compositor put desired logic here.
324 */
325
326 return XRT_SUCCESS;
327}
328
329static xrt_result_t
330null_compositor_end_session(struct xrt_compositor *xc)
331{
332 struct null_compositor *c = null_compositor(xc);
333 NULL_DEBUG(c, "END_SESSION");
334
335 /*
336 * No logic needed here for the null compositor, if using the null
337 * compositor as a base for a new compositor put desired logic here.
338 */
339
340 return XRT_SUCCESS;
341}
342
343static xrt_result_t
344null_compositor_predict_frame(struct xrt_compositor *xc,
345 int64_t *out_frame_id,
346 int64_t *out_wake_time_ns,
347 int64_t *out_predicted_gpu_time_ns,
348 int64_t *out_predicted_display_time_ns,
349 int64_t *out_predicted_display_period_ns)
350{
351 COMP_TRACE_MARKER();
352
353 struct null_compositor *c = null_compositor(xc);
354 NULL_TRACE(c, "PREDICT_FRAME");
355
356 int64_t now_ns = os_monotonic_get_ns();
357 int64_t null_desired_present_time_ns = 0;
358 int64_t null_present_slop_ns = 0;
359 int64_t null_min_display_period_ns = 0;
360
361 u_pc_predict( //
362 c->upc, // upc
363 now_ns, // now_ns
364 out_frame_id, // out_frame_id
365 out_wake_time_ns, // out_wake_up_time_ns
366 &null_desired_present_time_ns, // out_desired_present_time_ns
367 &null_present_slop_ns, // out_present_slop_ns
368 out_predicted_display_time_ns, // out_predicted_display_time_ns
369 out_predicted_display_period_ns, // out_predicted_display_period_ns
370 &null_min_display_period_ns); // out_min_display_period_ns
371
372 return XRT_SUCCESS;
373}
374
375static xrt_result_t
376null_compositor_mark_frame(struct xrt_compositor *xc,
377 int64_t frame_id,
378 enum xrt_compositor_frame_point point,
379 int64_t when_ns)
380{
381 COMP_TRACE_MARKER();
382
383 struct null_compositor *c = null_compositor(xc);
384 NULL_TRACE(c, "MARK_FRAME %i", point);
385
386 switch (point) {
387 case XRT_COMPOSITOR_FRAME_POINT_WOKE:
388 u_pc_mark_point(c->upc, U_TIMING_POINT_WAKE_UP, frame_id, when_ns);
389 return XRT_SUCCESS;
390 default: assert(false);
391 }
392
393 return XRT_SUCCESS;
394}
395
396static xrt_result_t
397null_compositor_begin_frame(struct xrt_compositor *xc, int64_t frame_id)
398{
399 struct null_compositor *c = null_compositor(xc);
400 NULL_TRACE(c, "BEGIN_FRAME");
401
402 /*
403 * No logic needed here for the null compositor, if using the null
404 * compositor as a base for a new compositor put desired logic here.
405 */
406
407 return XRT_SUCCESS;
408}
409
410static xrt_result_t
411null_compositor_discard_frame(struct xrt_compositor *xc, int64_t frame_id)
412{
413 struct null_compositor *c = null_compositor(xc);
414 NULL_TRACE(c, "DISCARD_FRAME");
415
416 // Shouldn't be called.
417 assert(false);
418
419 return XRT_SUCCESS;
420}
421
422static xrt_result_t
423null_compositor_layer_commit(struct xrt_compositor *xc, xrt_graphics_sync_handle_t sync_handle)
424{
425 COMP_TRACE_MARKER();
426
427 struct null_compositor *c = null_compositor(xc);
428 NULL_TRACE(c, "LAYER_COMMIT");
429
430 int64_t frame_id = c->base.layer_accum.data.frame_id;
431 int64_t display_time_ns = c->base.layer_accum.layers[0].data.timestamp;
432
433 // Default value from monado, overridden by HMD device where possible.
434 struct xrt_vec3 default_eye_relation = {0.063f, 0.f, 0.f};
435 struct xrt_space_relation head_relation = {0};
436
437 struct xrt_fov fovs[2] = {0};
438 struct xrt_pose poses[2] = {0};
439 xrt_result_t xret =
440 xrt_device_get_view_poses(c->xdev, &default_eye_relation, display_time_ns, 2, &head_relation, fovs, poses);
441 if (xret != XRT_SUCCESS) {
442 return xret;
443 }
444
445
446 /*
447 * The null compositor doesn't render any frames, but needs to do
448 * minimal bookkeeping and handling of arguments. If using the null
449 * compositor as a base for a new compositor this is where you render
450 * frames to be displayed to devices or remote clients.
451 */
452
453 // If you are using the system/multi-compositor (multiple client module), your native compositor
454 // can just unref the sync handle. Otherwise please use it.
455 u_graphics_sync_unref(&sync_handle);
456
457 /*
458 * Time keeping needed to keep the pacer happy.
459 */
460
461 // When we begin rendering.
462 {
463 int64_t now_ns = os_monotonic_get_ns();
464 u_pc_mark_point(c->upc, U_TIMING_POINT_BEGIN, frame_id, now_ns);
465 }
466
467 // When we are submitting to the GPU.
468 {
469 int64_t now_ns = os_monotonic_get_ns();
470 u_pc_mark_point(c->upc, U_TIMING_POINT_SUBMIT_BEGIN, frame_id, now_ns);
471
472 now_ns = os_monotonic_get_ns();
473 u_pc_mark_point(c->upc, U_TIMING_POINT_SUBMIT_END, frame_id, now_ns);
474 }
475
476 // Now is a good point to garbage collect.
477 comp_swapchain_shared_garbage_collect(&c->base.cscs);
478
479 return XRT_SUCCESS;
480}
481
482static void
483null_compositor_destroy(struct xrt_compositor *xc)
484{
485 struct null_compositor *c = null_compositor(xc);
486 struct vk_bundle *vk = get_vk(c);
487
488 NULL_DEBUG(c, "NULL_COMP_DESTROY");
489
490 // Make sure we don't have anything to destroy.
491 comp_swapchain_shared_garbage_collect(&c->base.cscs);
492
493 // Must be destroyed before Vulkan.
494 comp_swapchain_shared_destroy(&c->base.cscs, vk);
495
496 if (vk->device != VK_NULL_HANDLE) {
497 vk->vkDestroyDevice(vk->device, NULL);
498 vk->device = VK_NULL_HANDLE;
499 }
500
501 vk_deinit_mutex(vk);
502
503 if (vk->instance != VK_NULL_HANDLE) {
504 vk->vkDestroyInstance(vk->instance, NULL);
505 vk->instance = VK_NULL_HANDLE;
506 }
507
508 comp_base_fini(&c->base);
509
510 u_pc_destroy(&c->upc);
511
512 free(c);
513}
514
515static xrt_result_t
516null_compositor_get_display_refresh_rate(struct xrt_compositor *xc, float *out_display_refresh_rate_hz)
517{
518 struct null_compositor *c = null_compositor(xc);
519
520 *out_display_refresh_rate_hz = c->sys_info.refresh_rates_hz[0];
521 return XRT_SUCCESS;
522}
523
524static xrt_result_t
525null_compositor_request_display_refresh_rate(struct xrt_compositor *xc, float display_refresh_rate_hz)
526{
527 return XRT_SUCCESS;
528}
529
530/*
531 *
532 * 'Exported' functions.
533 *
534 */
535
536xrt_result_t
537null_compositor_create_system(struct xrt_device *xdev, struct xrt_system_compositor **out_xsysc)
538{
539 struct null_compositor *c = U_TYPED_CALLOC(struct null_compositor);
540
541 struct xrt_compositor *iface = &c->base.base.base;
542 iface->begin_session = null_compositor_begin_session;
543 iface->end_session = null_compositor_end_session;
544 iface->predict_frame = null_compositor_predict_frame;
545 iface->mark_frame = null_compositor_mark_frame;
546 iface->begin_frame = null_compositor_begin_frame;
547 iface->discard_frame = null_compositor_discard_frame;
548 iface->layer_commit = null_compositor_layer_commit;
549 iface->destroy = null_compositor_destroy;
550 c->base.base.base.get_display_refresh_rate = null_compositor_get_display_refresh_rate;
551 c->base.base.base.request_display_refresh_rate = null_compositor_request_display_refresh_rate;
552 c->settings.log_level = debug_get_log_option_log();
553 c->frame.waited.id = -1;
554 c->frame.rendering.id = -1;
555 c->settings.frame_interval_ns = U_TIME_1S_IN_NS / 20; // 20 FPS
556 c->xdev = xdev;
557
558 NULL_DEBUG(c, "Doing init %p", (void *)c);
559
560 NULL_INFO(c,
561 "\n"
562 "################################################################################\n"
563 "# Null compositor starting, if you intended to use the null compositor (for CI #\n"
564 "# integration) then everything is mostly likely setup correctly. But if you #\n"
565 "# intended to use Monado with real hardware it you probably built Monado #\n"
566 "# without the main compositor, please check your build config and make sure #\n"
567 "# that the main compositor is being built. Also make sure that the environment #\n"
568 "# variable XRT_COMPOSITOR_NULL is not set. #\n"
569 "################################################################################");
570
571 // Do this as early as possible
572 comp_base_init(&c->base);
573
574
575 /*
576 * Main init sequence.
577 */
578
579 if (!compositor_init_pacing(c) || //
580 !compositor_init_vulkan(c) || //
581 !compositor_init_sys_info(c, xdev) || //
582 !compositor_init_info(c)) { //
583 NULL_DEBUG(c, "Failed to init compositor %p", (void *)c);
584 c->base.base.base.destroy(&c->base.base.base);
585
586 return XRT_ERROR_VULKAN;
587 }
588
589
590 NULL_DEBUG(c, "Done %p", (void *)c);
591
592 // Standard app pacer.
593 struct u_pacing_app_factory *upaf = NULL;
594 XRT_MAYBE_UNUSED xrt_result_t xret = u_pa_factory_create(&upaf);
595 assert(xret == XRT_SUCCESS && upaf != NULL);
596
597 return comp_multi_create_system_compositor(&c->base.base, upaf, &c->sys_info, false, out_xsysc);
598}