From 724e2b0f71b87ca55353394af2b422092306224d Mon Sep 17 00:00:00 2001 From: Jakob Bornecrantz Date: Mon, 3 Jun 2024 20:44:31 +0100 Subject: [PATCH] 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: --- src/xrt/compositor/CMakeLists.txt | 1 + src/xrt/compositor/main/comp_compositor.c | 1 + src/xrt/compositor/main/comp_window.h | 8 + .../compositor/main/comp_window_debug_image.c | 411 ++++++++++++++++++ 4 files changed, 421 insertions(+) create mode 100644 src/xrt/compositor/main/comp_window_debug_image.c diff --git a/src/xrt/compositor/CMakeLists.txt b/src/xrt/compositor/CMakeLists.txt index f1c54ed4d..e0128e5b6 100644 --- a/src/xrt/compositor/CMakeLists.txt +++ b/src/xrt/compositor/CMakeLists.txt @@ -190,6 +190,7 @@ if(XRT_MODULE_COMPOSITOR_MAIN) main/comp_target_swapchain.c main/comp_target_swapchain.h main/comp_window.h + main/comp_window_debug_image.c main/comp_mirror_to_debug_gui.c main/comp_mirror_to_debug_gui.h ) diff --git a/src/xrt/compositor/main/comp_compositor.c b/src/xrt/compositor/main/comp_compositor.c index 26c341385..eecb14c17 100644 --- a/src/xrt/compositor/main/comp_compositor.c +++ b/src/xrt/compositor/main/comp_compositor.c @@ -756,6 +756,7 @@ const struct comp_target_factory *ctfs[] = { #ifdef VK_USE_PLATFORM_DISPLAY_KHR &comp_target_factory_vk_display, #endif + &comp_target_factory_debug_image, }; static void diff --git a/src/xrt/compositor/main/comp_window.h b/src/xrt/compositor/main/comp_window.h index bea06ec87..eb41c3c36 100644 --- a/src/xrt/compositor/main/comp_window.h +++ b/src/xrt/compositor/main/comp_window.h @@ -1,4 +1,5 @@ // Copyright 2019, Collabora, Ltd. +// Copyright 2024-2025, NVIDIA CORPORATION. // SPDX-License-Identifier: BSL-1.0 /*! * @file @@ -26,6 +27,13 @@ extern "C" { * */ +/*! + * Debug info target factor, always available. + * + * @ingroup comp_main + */ +extern const struct comp_target_factory comp_target_factory_debug_image; + #ifdef VK_USE_PLATFORM_XCB_KHR /*! diff --git a/src/xrt/compositor/main/comp_window_debug_image.c b/src/xrt/compositor/main/comp_window_debug_image.c new file mode 100644 index 000000000..517924e2d --- /dev/null +++ b/src/xrt/compositor/main/comp_window_debug_image.c @@ -0,0 +1,411 @@ +// Copyright 2019-2023, Collabora, Ltd. +// Copyright 2024-2025, NVIDIA CORPORATION. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Simple debug image based target. + * @author Jakob Bornecrantz + * @ingroup comp_main + */ + +#include "util/u_misc.h" +#include "util/u_pacing.h" +#include "util/u_pretty_print.h" + +#include "main/comp_window.h" + + +/* + * + * Structs and defines. + * + */ + +struct debug_image_target +{ + //! Base "class", so that we are a target the compositor can use. + struct comp_target base; + + //! For error checking. + int64_t index; + + //! Used to create the Vulkan resources, also manages index. + struct comp_scratch_single_images target; + + /*! + * Storage for 'exported' images, these are pointed at by + * comt_target::images pointer in the @p base struct. + */ + struct comp_target_image images[COMP_SCRATCH_NUM_IMAGES]; + + //! Compositor frame pacing helper. + struct u_pacing_compositor *upc; + + // So we know we can free Vulkan resources safely. + bool has_init_vulkan; +}; + + +/* + * + * Target members. + * + */ + +static bool +target_init_pre_vulkan(struct comp_target *ct) +{ + return true; // No-op +} + +static bool +target_init_post_vulkan(struct comp_target *ct, uint32_t preferred_width, uint32_t preferred_height) +{ + struct debug_image_target *dit = (struct debug_image_target *)ct; + + // We now know Vulkan is running and we can use it. + dit->has_init_vulkan = true; + + return true; +} + +static bool +target_check_ready(struct comp_target *ct) +{ + return true; // Always ready. +} + +static void +target_create_images(struct comp_target *ct, const struct comp_target_create_images_info *create_info) +{ + struct debug_image_target *dit = (struct debug_image_target *)ct; + struct vk_bundle *vk = &dit->base.c->base.vk; + bool use_unorm = false, use_srgb = false, maybe_convert = false; + + // Paranoia. + assert(dit->has_init_vulkan); + + /* + * Find the format we should use, since we are using the scratch images + * to allocate the images we only support the two formats it uses + * (listed below). We search for those breaking as soon as we find those + * and setting if the compositor wanted SRGB or UNORM. But we also look + * for two other commonly used formats, but continue searching for the + * other true formats. + * + * The format used by the scratch image is: + * - VK_FORMAT_R8G8B8A8_SRGB + * - VK_FORMAT_R8G8B8A8_UNORM + * + * The other formats used to determine SRGB vs UNORM: + * - VK_FORMAT_B8G8R8A8_SRGB + * - VK_FORMAT_B8G8R8A8_UNORM + */ + for (uint32_t i = 0; i < create_info->format_count; i++) { + VkFormat format = create_info->formats[i]; + + // Used to figure out if we want SRGB or UNORM only. + if (!maybe_convert && format == VK_FORMAT_B8G8R8A8_UNORM) { + use_unorm = true; + maybe_convert = true; + continue; // Keep going, we might get better formats. + } + if (!maybe_convert && format == VK_FORMAT_B8G8R8A8_SRGB) { + use_srgb = true; + maybe_convert = true; + continue; // Keep going, we might get better formats. + } + + // These two are what the scratch image allocates. + if (format == VK_FORMAT_R8G8B8A8_UNORM) { + use_unorm = true; + maybe_convert = false; + break; // Best match, stop searching. + } + if (format == VK_FORMAT_R8G8B8A8_SRGB) { + use_srgb = true; + maybe_convert = false; + break; // Best match, stop searching. + } + } + + // Check + assert(use_unorm || use_srgb); + if (maybe_convert) { + COMP_WARN(ct->c, "Ignoring the format and picking something we use."); + } + + // Do the allocation. + comp_scratch_single_images_ensure(&dit->target, vk, create_info->extent); + + // Share the Vulkan handles of images and image views. + for (uint32_t i = 0; i < COMP_SCRATCH_NUM_IMAGES; i++) { + dit->images[i].handle = dit->target.images[i].image; + if (use_unorm) { + dit->images[i].view = dit->target.images[i].unorm_view; + } + if (use_srgb) { + dit->images[i].view = dit->target.images[i].srgb_view; + } + } + + // Fill in exported data. + dit->base.image_count = COMP_SCRATCH_NUM_IMAGES; + dit->base.images = &dit->images[0]; + dit->base.width = create_info->extent.width; + dit->base.height = create_info->extent.height; + if (use_unorm) { + dit->base.format = VK_FORMAT_R8G8B8A8_UNORM; + } + if (use_srgb) { + dit->base.format = VK_FORMAT_R8G8B8A8_SRGB; + } +} + +static bool +target_has_images(struct comp_target *ct) +{ + struct debug_image_target *dit = (struct debug_image_target *)ct; + + // Simple check. + return dit->base.images != NULL; +} + +static VkResult +target_acquire(struct comp_target *ct, uint32_t *out_index) +{ + struct debug_image_target *dit = (struct debug_image_target *)ct; + + // Error checking. + assert(dit->index == -1); + + uint32_t index = 0; + comp_scratch_single_images_get(&dit->target, &index); + + // For error checking. + dit->index = index; + + // Return the variable. + *out_index = index; + + return VK_SUCCESS; +} + +static VkResult +target_present(struct comp_target *ct, + VkQueue queue, + uint32_t index, + uint64_t timeline_semaphore_value, + int64_t desired_present_time_ns, + int64_t present_slop_ns) +{ + struct debug_image_target *dit = (struct debug_image_target *)ct; + + assert(index == dit->index); + + comp_scratch_single_images_done(&dit->target); + + // For error checking. + dit->index = -1; + + return VK_SUCCESS; +} + +static void +target_flush(struct comp_target *ct) +{ + // No-op +} + +static void +target_calc_frame_pacing(struct comp_target *ct, + int64_t *out_frame_id, + int64_t *out_wake_up_time_ns, + int64_t *out_desired_present_time_ns, + int64_t *out_present_slop_ns, + int64_t *out_predicted_display_time_ns) +{ + struct debug_image_target *dit = (struct debug_image_target *)ct; + + int64_t frame_id = -1; + int64_t wake_up_time_ns = 0; + int64_t desired_present_time_ns = 0; + int64_t present_slop_ns = 0; + int64_t predicted_display_time_ns = 0; + int64_t predicted_display_period_ns = 0; + int64_t min_display_period_ns = 0; + int64_t now_ns = os_monotonic_get_ns(); + + u_pc_predict(dit->upc, // + now_ns, // + &frame_id, // + &wake_up_time_ns, // + &desired_present_time_ns, // + &present_slop_ns, // + &predicted_display_time_ns, // + &predicted_display_period_ns, // + &min_display_period_ns); // + + *out_frame_id = frame_id; + *out_wake_up_time_ns = wake_up_time_ns; + *out_desired_present_time_ns = desired_present_time_ns; + *out_predicted_display_time_ns = predicted_display_time_ns; + *out_present_slop_ns = present_slop_ns; +} + +static void +target_mark_timing_point(struct comp_target *ct, enum comp_target_timing_point point, int64_t frame_id, int64_t when_ns) +{ + struct debug_image_target *dit = (struct debug_image_target *)ct; + + switch (point) { + case COMP_TARGET_TIMING_POINT_WAKE_UP: + u_pc_mark_point(dit->upc, U_TIMING_POINT_WAKE_UP, frame_id, when_ns); + break; + case COMP_TARGET_TIMING_POINT_BEGIN: // + u_pc_mark_point(dit->upc, U_TIMING_POINT_BEGIN, frame_id, when_ns); + break; + case COMP_TARGET_TIMING_POINT_SUBMIT_BEGIN: + u_pc_mark_point(dit->upc, U_TIMING_POINT_SUBMIT_BEGIN, frame_id, when_ns); + break; + case COMP_TARGET_TIMING_POINT_SUBMIT_END: + u_pc_mark_point(dit->upc, U_TIMING_POINT_SUBMIT_END, frame_id, when_ns); + break; + default: assert(false); + } +} + +static VkResult +target_update_timings(struct comp_target *ct) +{ + return VK_SUCCESS; // No-op +} + +static void +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) +{ + struct debug_image_target *dit = (struct debug_image_target *)ct; + + u_pc_info_gpu(dit->upc, frame_id, gpu_start_ns, gpu_end_ns, when_ns); +} + +static void +target_set_title(struct comp_target *ct, const char *title) +{ + // No-op +} + +static void +target_destroy(struct comp_target *ct) +{ + struct debug_image_target *dit = (struct debug_image_target *)ct; + struct vk_bundle *vk = &dit->base.c->base.vk; + + // Do this first. + u_var_remove_root(dit); + + // Can only allocate if we have Vulkan. + if (dit->has_init_vulkan) { + comp_scratch_single_images_free(&dit->target, vk); + dit->has_init_vulkan = false; + dit->base.image_count = 0; + dit->base.images = NULL; + dit->base.width = 0; + dit->base.height = 0; + dit->base.format = VK_FORMAT_UNDEFINED; + } + + // Always free non-Vulkan resources. + comp_scratch_single_images_destroy(&dit->target); + + // Pacing is always created. + u_pc_destroy(&dit->upc); + + // Free memory. + free(dit); +} + +struct comp_target * +target_create(struct comp_compositor *c) +{ + struct debug_image_target *dit = U_TYPED_CALLOC(struct debug_image_target); + + dit->base.name = "debug_image"; + dit->base.init_pre_vulkan = target_init_pre_vulkan; + dit->base.init_post_vulkan = target_init_post_vulkan; + dit->base.check_ready = target_check_ready; + dit->base.create_images = target_create_images; + dit->base.has_images = target_has_images; + dit->base.acquire = target_acquire; + dit->base.present = target_present; + dit->base.flush = target_flush; + dit->base.calc_frame_pacing = target_calc_frame_pacing; + dit->base.mark_timing_point = target_mark_timing_point; + dit->base.update_timings = target_update_timings; + dit->base.info_gpu = target_info_gpu; + dit->base.set_title = target_set_title; + dit->base.destroy = target_destroy; + dit->base.c = c; + + // Create the pacer. + uint64_t now_ns = os_monotonic_get_ns(); + u_pc_fake_create(c->settings.nominal_frame_interval_ns, now_ns, &dit->upc); + + // Only inits locking, Vulkan resources inited later. + comp_scratch_single_images_init(&dit->target); + + // For error checking. + dit->index = -1; + + // Variable tracking. + u_var_add_root(dit, "Compositor output", true); + u_var_add_native_images_debug(dit, &dit->target.unid, "Image"); + + return &dit->base; +} + + +/* + * + * Factory + * + */ + +static bool +factory_detect(const struct comp_target_factory *ctf, struct comp_compositor *c) +{ + return false; +} + +static bool +factory_create_target(const struct comp_target_factory *ctf, struct comp_compositor *c, struct comp_target **out_ct) +{ + struct comp_target *ct = target_create(c); + if (ct == NULL) { + return false; + } + + COMP_INFO(c, + "\n################################################################################\n" + "# Debug image target used, if you wanted to see something in your headset #\n" + "# something is probably wrong with your setup, sorry. #\n" + "################################################################################"); + + *out_ct = ct; + + return true; +} + +const struct comp_target_factory comp_target_factory_debug_image = { + .name = "Debug Image", + .identifier = "debug_image", + .requires_vulkan_for_create = false, + .is_deferred = false, + .required_instance_version = 0, + .required_instance_extensions = NULL, + .required_instance_extension_count = 0, + .optional_device_extensions = NULL, + .optional_device_extension_count = 0, + .detect = factory_detect, + .create_target = factory_create_target, +}; -- 2.51.2