The open source OpenXR runtime
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

c/main: Add comp_target_debug_image

This change lets you run the main compositor without any output,
since this uses the debug image these can be inspected with the debug
gui or other code using the u_var system.

Part-of: <https://gitlab.freedesktop.org/monado/monado/-/merge_requests/2490>

+421
+1
src/xrt/compositor/CMakeLists.txt
··· 190 190 main/comp_target_swapchain.c 191 191 main/comp_target_swapchain.h 192 192 main/comp_window.h 193 + main/comp_window_debug_image.c 193 194 main/comp_mirror_to_debug_gui.c 194 195 main/comp_mirror_to_debug_gui.h 195 196 )
+1
src/xrt/compositor/main/comp_compositor.c
··· 756 756 #ifdef VK_USE_PLATFORM_DISPLAY_KHR 757 757 &comp_target_factory_vk_display, 758 758 #endif 759 + &comp_target_factory_debug_image, 759 760 }; 760 761 761 762 static void
+8
src/xrt/compositor/main/comp_window.h
··· 1 1 // Copyright 2019, Collabora, Ltd. 2 + // Copyright 2024-2025, NVIDIA CORPORATION. 2 3 // SPDX-License-Identifier: BSL-1.0 3 4 /*! 4 5 * @file ··· 25 26 * Functions. 26 27 * 27 28 */ 29 + 30 + /*! 31 + * Debug info target factor, always available. 32 + * 33 + * @ingroup comp_main 34 + */ 35 + extern const struct comp_target_factory comp_target_factory_debug_image; 28 36 29 37 #ifdef VK_USE_PLATFORM_XCB_KHR 30 38
+411
src/xrt/compositor/main/comp_window_debug_image.c
··· 1 + // Copyright 2019-2023, Collabora, Ltd. 2 + // Copyright 2024-2025, NVIDIA CORPORATION. 3 + // SPDX-License-Identifier: BSL-1.0 4 + /*! 5 + * @file 6 + * @brief Simple debug image based target. 7 + * @author Jakob Bornecrantz <tbornecrantz@nvidia.com> 8 + * @ingroup comp_main 9 + */ 10 + 11 + #include "util/u_misc.h" 12 + #include "util/u_pacing.h" 13 + #include "util/u_pretty_print.h" 14 + 15 + #include "main/comp_window.h" 16 + 17 + 18 + /* 19 + * 20 + * Structs and defines. 21 + * 22 + */ 23 + 24 + struct debug_image_target 25 + { 26 + //! Base "class", so that we are a target the compositor can use. 27 + struct comp_target base; 28 + 29 + //! For error checking. 30 + int64_t index; 31 + 32 + //! Used to create the Vulkan resources, also manages index. 33 + struct comp_scratch_single_images target; 34 + 35 + /*! 36 + * Storage for 'exported' images, these are pointed at by 37 + * comt_target::images pointer in the @p base struct. 38 + */ 39 + struct comp_target_image images[COMP_SCRATCH_NUM_IMAGES]; 40 + 41 + //! Compositor frame pacing helper. 42 + struct u_pacing_compositor *upc; 43 + 44 + // So we know we can free Vulkan resources safely. 45 + bool has_init_vulkan; 46 + }; 47 + 48 + 49 + /* 50 + * 51 + * Target members. 52 + * 53 + */ 54 + 55 + static bool 56 + target_init_pre_vulkan(struct comp_target *ct) 57 + { 58 + return true; // No-op 59 + } 60 + 61 + static bool 62 + target_init_post_vulkan(struct comp_target *ct, uint32_t preferred_width, uint32_t preferred_height) 63 + { 64 + struct debug_image_target *dit = (struct debug_image_target *)ct; 65 + 66 + // We now know Vulkan is running and we can use it. 67 + dit->has_init_vulkan = true; 68 + 69 + return true; 70 + } 71 + 72 + static bool 73 + target_check_ready(struct comp_target *ct) 74 + { 75 + return true; // Always ready. 76 + } 77 + 78 + static void 79 + target_create_images(struct comp_target *ct, const struct comp_target_create_images_info *create_info) 80 + { 81 + struct debug_image_target *dit = (struct debug_image_target *)ct; 82 + struct vk_bundle *vk = &dit->base.c->base.vk; 83 + bool use_unorm = false, use_srgb = false, maybe_convert = false; 84 + 85 + // Paranoia. 86 + assert(dit->has_init_vulkan); 87 + 88 + /* 89 + * Find the format we should use, since we are using the scratch images 90 + * to allocate the images we only support the two formats it uses 91 + * (listed below). We search for those breaking as soon as we find those 92 + * and setting if the compositor wanted SRGB or UNORM. But we also look 93 + * for two other commonly used formats, but continue searching for the 94 + * other true formats. 95 + * 96 + * The format used by the scratch image is: 97 + * - VK_FORMAT_R8G8B8A8_SRGB 98 + * - VK_FORMAT_R8G8B8A8_UNORM 99 + * 100 + * The other formats used to determine SRGB vs UNORM: 101 + * - VK_FORMAT_B8G8R8A8_SRGB 102 + * - VK_FORMAT_B8G8R8A8_UNORM 103 + */ 104 + for (uint32_t i = 0; i < create_info->format_count; i++) { 105 + VkFormat format = create_info->formats[i]; 106 + 107 + // Used to figure out if we want SRGB or UNORM only. 108 + if (!maybe_convert && format == VK_FORMAT_B8G8R8A8_UNORM) { 109 + use_unorm = true; 110 + maybe_convert = true; 111 + continue; // Keep going, we might get better formats. 112 + } 113 + if (!maybe_convert && format == VK_FORMAT_B8G8R8A8_SRGB) { 114 + use_srgb = true; 115 + maybe_convert = true; 116 + continue; // Keep going, we might get better formats. 117 + } 118 + 119 + // These two are what the scratch image allocates. 120 + if (format == VK_FORMAT_R8G8B8A8_UNORM) { 121 + use_unorm = true; 122 + maybe_convert = false; 123 + break; // Best match, stop searching. 124 + } 125 + if (format == VK_FORMAT_R8G8B8A8_SRGB) { 126 + use_srgb = true; 127 + maybe_convert = false; 128 + break; // Best match, stop searching. 129 + } 130 + } 131 + 132 + // Check 133 + assert(use_unorm || use_srgb); 134 + if (maybe_convert) { 135 + COMP_WARN(ct->c, "Ignoring the format and picking something we use."); 136 + } 137 + 138 + // Do the allocation. 139 + comp_scratch_single_images_ensure(&dit->target, vk, create_info->extent); 140 + 141 + // Share the Vulkan handles of images and image views. 142 + for (uint32_t i = 0; i < COMP_SCRATCH_NUM_IMAGES; i++) { 143 + dit->images[i].handle = dit->target.images[i].image; 144 + if (use_unorm) { 145 + dit->images[i].view = dit->target.images[i].unorm_view; 146 + } 147 + if (use_srgb) { 148 + dit->images[i].view = dit->target.images[i].srgb_view; 149 + } 150 + } 151 + 152 + // Fill in exported data. 153 + dit->base.image_count = COMP_SCRATCH_NUM_IMAGES; 154 + dit->base.images = &dit->images[0]; 155 + dit->base.width = create_info->extent.width; 156 + dit->base.height = create_info->extent.height; 157 + if (use_unorm) { 158 + dit->base.format = VK_FORMAT_R8G8B8A8_UNORM; 159 + } 160 + if (use_srgb) { 161 + dit->base.format = VK_FORMAT_R8G8B8A8_SRGB; 162 + } 163 + } 164 + 165 + static bool 166 + target_has_images(struct comp_target *ct) 167 + { 168 + struct debug_image_target *dit = (struct debug_image_target *)ct; 169 + 170 + // Simple check. 171 + return dit->base.images != NULL; 172 + } 173 + 174 + static VkResult 175 + target_acquire(struct comp_target *ct, uint32_t *out_index) 176 + { 177 + struct debug_image_target *dit = (struct debug_image_target *)ct; 178 + 179 + // Error checking. 180 + assert(dit->index == -1); 181 + 182 + uint32_t index = 0; 183 + comp_scratch_single_images_get(&dit->target, &index); 184 + 185 + // For error checking. 186 + dit->index = index; 187 + 188 + // Return the variable. 189 + *out_index = index; 190 + 191 + return VK_SUCCESS; 192 + } 193 + 194 + static VkResult 195 + target_present(struct comp_target *ct, 196 + VkQueue queue, 197 + uint32_t index, 198 + uint64_t timeline_semaphore_value, 199 + int64_t desired_present_time_ns, 200 + int64_t present_slop_ns) 201 + { 202 + struct debug_image_target *dit = (struct debug_image_target *)ct; 203 + 204 + assert(index == dit->index); 205 + 206 + comp_scratch_single_images_done(&dit->target); 207 + 208 + // For error checking. 209 + dit->index = -1; 210 + 211 + return VK_SUCCESS; 212 + } 213 + 214 + static void 215 + target_flush(struct comp_target *ct) 216 + { 217 + // No-op 218 + } 219 + 220 + static void 221 + target_calc_frame_pacing(struct comp_target *ct, 222 + int64_t *out_frame_id, 223 + int64_t *out_wake_up_time_ns, 224 + int64_t *out_desired_present_time_ns, 225 + int64_t *out_present_slop_ns, 226 + int64_t *out_predicted_display_time_ns) 227 + { 228 + struct debug_image_target *dit = (struct debug_image_target *)ct; 229 + 230 + int64_t frame_id = -1; 231 + int64_t wake_up_time_ns = 0; 232 + int64_t desired_present_time_ns = 0; 233 + int64_t present_slop_ns = 0; 234 + int64_t predicted_display_time_ns = 0; 235 + int64_t predicted_display_period_ns = 0; 236 + int64_t min_display_period_ns = 0; 237 + int64_t now_ns = os_monotonic_get_ns(); 238 + 239 + u_pc_predict(dit->upc, // 240 + now_ns, // 241 + &frame_id, // 242 + &wake_up_time_ns, // 243 + &desired_present_time_ns, // 244 + &present_slop_ns, // 245 + &predicted_display_time_ns, // 246 + &predicted_display_period_ns, // 247 + &min_display_period_ns); // 248 + 249 + *out_frame_id = frame_id; 250 + *out_wake_up_time_ns = wake_up_time_ns; 251 + *out_desired_present_time_ns = desired_present_time_ns; 252 + *out_predicted_display_time_ns = predicted_display_time_ns; 253 + *out_present_slop_ns = present_slop_ns; 254 + } 255 + 256 + static void 257 + target_mark_timing_point(struct comp_target *ct, enum comp_target_timing_point point, int64_t frame_id, int64_t when_ns) 258 + { 259 + struct debug_image_target *dit = (struct debug_image_target *)ct; 260 + 261 + switch (point) { 262 + case COMP_TARGET_TIMING_POINT_WAKE_UP: 263 + u_pc_mark_point(dit->upc, U_TIMING_POINT_WAKE_UP, frame_id, when_ns); 264 + break; 265 + case COMP_TARGET_TIMING_POINT_BEGIN: // 266 + u_pc_mark_point(dit->upc, U_TIMING_POINT_BEGIN, frame_id, when_ns); 267 + break; 268 + case COMP_TARGET_TIMING_POINT_SUBMIT_BEGIN: 269 + u_pc_mark_point(dit->upc, U_TIMING_POINT_SUBMIT_BEGIN, frame_id, when_ns); 270 + break; 271 + case COMP_TARGET_TIMING_POINT_SUBMIT_END: 272 + u_pc_mark_point(dit->upc, U_TIMING_POINT_SUBMIT_END, frame_id, when_ns); 273 + break; 274 + default: assert(false); 275 + } 276 + } 277 + 278 + static VkResult 279 + target_update_timings(struct comp_target *ct) 280 + { 281 + return VK_SUCCESS; // No-op 282 + } 283 + 284 + static void 285 + target_info_gpu(struct comp_target *ct, int64_t frame_id, int64_t gpu_start_ns, int64_t gpu_end_ns, int64_t when_ns) 286 + { 287 + struct debug_image_target *dit = (struct debug_image_target *)ct; 288 + 289 + u_pc_info_gpu(dit->upc, frame_id, gpu_start_ns, gpu_end_ns, when_ns); 290 + } 291 + 292 + static void 293 + target_set_title(struct comp_target *ct, const char *title) 294 + { 295 + // No-op 296 + } 297 + 298 + static void 299 + target_destroy(struct comp_target *ct) 300 + { 301 + struct debug_image_target *dit = (struct debug_image_target *)ct; 302 + struct vk_bundle *vk = &dit->base.c->base.vk; 303 + 304 + // Do this first. 305 + u_var_remove_root(dit); 306 + 307 + // Can only allocate if we have Vulkan. 308 + if (dit->has_init_vulkan) { 309 + comp_scratch_single_images_free(&dit->target, vk); 310 + dit->has_init_vulkan = false; 311 + dit->base.image_count = 0; 312 + dit->base.images = NULL; 313 + dit->base.width = 0; 314 + dit->base.height = 0; 315 + dit->base.format = VK_FORMAT_UNDEFINED; 316 + } 317 + 318 + // Always free non-Vulkan resources. 319 + comp_scratch_single_images_destroy(&dit->target); 320 + 321 + // Pacing is always created. 322 + u_pc_destroy(&dit->upc); 323 + 324 + // Free memory. 325 + free(dit); 326 + } 327 + 328 + struct comp_target * 329 + target_create(struct comp_compositor *c) 330 + { 331 + struct debug_image_target *dit = U_TYPED_CALLOC(struct debug_image_target); 332 + 333 + dit->base.name = "debug_image"; 334 + dit->base.init_pre_vulkan = target_init_pre_vulkan; 335 + dit->base.init_post_vulkan = target_init_post_vulkan; 336 + dit->base.check_ready = target_check_ready; 337 + dit->base.create_images = target_create_images; 338 + dit->base.has_images = target_has_images; 339 + dit->base.acquire = target_acquire; 340 + dit->base.present = target_present; 341 + dit->base.flush = target_flush; 342 + dit->base.calc_frame_pacing = target_calc_frame_pacing; 343 + dit->base.mark_timing_point = target_mark_timing_point; 344 + dit->base.update_timings = target_update_timings; 345 + dit->base.info_gpu = target_info_gpu; 346 + dit->base.set_title = target_set_title; 347 + dit->base.destroy = target_destroy; 348 + dit->base.c = c; 349 + 350 + // Create the pacer. 351 + uint64_t now_ns = os_monotonic_get_ns(); 352 + u_pc_fake_create(c->settings.nominal_frame_interval_ns, now_ns, &dit->upc); 353 + 354 + // Only inits locking, Vulkan resources inited later. 355 + comp_scratch_single_images_init(&dit->target); 356 + 357 + // For error checking. 358 + dit->index = -1; 359 + 360 + // Variable tracking. 361 + u_var_add_root(dit, "Compositor output", true); 362 + u_var_add_native_images_debug(dit, &dit->target.unid, "Image"); 363 + 364 + return &dit->base; 365 + } 366 + 367 + 368 + /* 369 + * 370 + * Factory 371 + * 372 + */ 373 + 374 + static bool 375 + factory_detect(const struct comp_target_factory *ctf, struct comp_compositor *c) 376 + { 377 + return false; 378 + } 379 + 380 + static bool 381 + factory_create_target(const struct comp_target_factory *ctf, struct comp_compositor *c, struct comp_target **out_ct) 382 + { 383 + struct comp_target *ct = target_create(c); 384 + if (ct == NULL) { 385 + return false; 386 + } 387 + 388 + COMP_INFO(c, 389 + "\n################################################################################\n" 390 + "# Debug image target used, if you wanted to see something in your headset #\n" 391 + "# something is probably wrong with your setup, sorry. #\n" 392 + "################################################################################"); 393 + 394 + *out_ct = ct; 395 + 396 + return true; 397 + } 398 + 399 + const struct comp_target_factory comp_target_factory_debug_image = { 400 + .name = "Debug Image", 401 + .identifier = "debug_image", 402 + .requires_vulkan_for_create = false, 403 + .is_deferred = false, 404 + .required_instance_version = 0, 405 + .required_instance_extensions = NULL, 406 + .required_instance_extension_count = 0, 407 + .optional_device_extensions = NULL, 408 + .optional_device_extension_count = 0, 409 + .detect = factory_detect, 410 + .create_target = factory_create_target, 411 + };