The open source OpenXR runtime
0
fork

Configure Feed

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

at main 519 lines 15 kB view raw
1// Copyright 2020-2024, Collabora, Ltd. 2// SPDX-License-Identifier: BSL-1.0 3/*! 4 * @file 5 * @brief For generating a fake timing. 6 * @author Jakob Bornecrantz <jakob@collabora.com> 7 * @ingroup aux_util 8 */ 9 10#include "os/os_time.h" 11 12#include "util/u_var.h" 13#include "util/u_time.h" 14#include "util/u_misc.h" 15#include "util/u_debug.h" 16#include "util/u_pacing.h" 17#include "util/u_metrics.h" 18#include "util/u_logging.h" 19#include "util/u_live_stats.h" 20#include "util/u_trace_marker.h" 21 22#include <stdio.h> 23#include <assert.h> 24#include <math.h> 25 26DEBUG_GET_ONCE_LOG_OPTION(log_level_fake, "U_PACING_COMPOSITOR_FAKE_LOG", U_LOGGING_INFO) 27 28#define UPC_LOG_T(...) U_LOG_IFL_T(debug_get_log_option_log_level_fake(), __VA_ARGS__) 29#define UPC_LOG_D(...) U_LOG_IFL_D(debug_get_log_option_log_level_fake(), __VA_ARGS__) 30#define UPC_LOG_I(...) U_LOG_IFL_I(debug_get_log_option_log_level_fake(), __VA_ARGS__) 31#define UPC_LOG_W(...) U_LOG_IFL_W(debug_get_log_option_log_level_fake(), __VA_ARGS__) 32#define UPC_LOG_E(...) U_LOG_IFL_E(debug_get_log_option_log_level_fake(), __VA_ARGS__) 33 34 35/* 36 * 37 * Structs and defines. 38 * 39 */ 40 41DEBUG_GET_ONCE_FLOAT_OPTION(present_to_display_offset_ms, "U_PACING_COMP_PRESENT_TO_DISPLAY_OFFSET_MS", 4.0f) 42DEBUG_GET_ONCE_FLOAT_OPTION(min_comp_time_ms, "U_PACING_COMP_MIN_TIME_MS", 3.0f) 43DEBUG_GET_ONCE_FLOAT_OPTION(comp_time_fraction_percent, "U_PACING_COMP_TIME_FRACTION_PERCENT", 20.0f) 44DEBUG_GET_ONCE_BOOL_OPTION(live_stats, "U_PACING_LIVE_STATS", false) 45 46// We keep track of this number of frames. 47#define FRAME_COUNT 8 48 49/* 50 * Internal helper for keeping track of frame data. 51 */ 52struct frame 53{ 54 //! An arbitrary id that identifies this frame. Set in `pc_predict`. 55 int64_t frame_id; 56 57 //! When should the compositor wake up. Set in `pc_predict`. 58 int64_t predicted_wake_up_time_ns; 59 60 //! When should the compositor present the frame. 61 int64_t predicted_present_time_ns; 62 63 //! When should the frame be displayed. 64 int64_t predicted_display_time_ns; 65 66 //! The period that the pacer used for this frame. 67 int64_t predicted_display_period_ns; 68 69 //! When this frame was last used for a prediction. Set in `pc_predict`. 70 int64_t when_predict_ns; 71 72 /*! 73 * When the compositor woke up after its equivalent of wait_frame. 74 * Set in `pc_mark_point` with `U_TIMING_POINT_WAKE_UP`. 75 */ 76 int64_t when_woke_ns; 77 78 /*! 79 * When the compositor began rendering a frame. Set in `pc_mark_point` 80 * with `U_TIMING_POINT_BEGIN`. 81 */ 82 int64_t when_began_ns; 83 84 /*! 85 * When the compositor began submitting the work to the GPU, after 86 * it completed building the command buffers. Set in `pc_mark_point` 87 * with `U_TIMING_POINT_SUBMIT_BEGIN`. 88 */ 89 int64_t when_submit_began_ns; 90 91 /*! 92 * When the compositor completed submitting the work to the GPU. Set in 93 * `pc_mark_point` with `U_TIMING_POINT_SUBMIT_END`. 94 */ 95 int64_t when_submit_end_ns; 96}; 97 98/*! 99 * A very simple pacer that tries it best to pace a compositor. Used when the 100 * compositor can't get any good or limited feedback from the presentation 101 * engine about timing. 102 */ 103struct fake_timing 104{ 105 struct u_pacing_compositor base; 106 107 /*! 108 * The periodicity of the display. 109 */ 110 int64_t frame_period_ns; 111 112 /*! 113 * When the last frame was presented, not displayed. 114 */ 115 int64_t last_present_time_ns; 116 117 /*! 118 * Very often the present time that we get from the system is only when 119 * the display engine starts scanning out from the buffers we provided, 120 * and not when the pixels turned into photons that the user sees. 121 */ 122 struct u_var_draggable_f32 present_to_display_offset_ms; 123 124 //! The amount of time that the application needs to render frame. 125 int64_t comp_time_ns; 126 127 //! This won't run out, trust me. 128 int64_t frame_id_generator; 129 130 //! Frames we keep track off. 131 struct frame frames[FRAME_COUNT]; 132 133 //! Live stats we keep track off. 134 struct u_live_stats_ns cpu, draw, submit, gpu, gpu_delay, total_frame; 135}; 136 137 138/* 139 * 140 * Helper functions. 141 * 142 */ 143 144static inline struct fake_timing * 145fake_timing(struct u_pacing_compositor *upc) 146{ 147 return (struct fake_timing *)upc; 148} 149 150static struct frame * 151get_frame_or_null(struct fake_timing *ft, int64_t frame_id) 152{ 153 uint64_t index = (uint64_t)frame_id % FRAME_COUNT; 154 struct frame *f = &ft->frames[index]; 155 156 if (f->frame_id == frame_id) { 157 return f; 158 } 159 // Just drop it, doesn't happen during normal operation. 160 return NULL; 161} 162 163static struct frame * 164get_new_frame(struct fake_timing *ft) 165{ 166 int64_t frame_id = ++ft->frame_id_generator; 167 168 uint64_t index = (uint64_t)frame_id % FRAME_COUNT; 169 struct frame *f = &ft->frames[index]; 170 171 // We don't care if it has been fully finished. 172 U_ZERO(f); 173 f->frame_id = frame_id; 174 175 return f; 176} 177 178static int64_t 179predict_next_frame_present_time(struct fake_timing *ft, int64_t now_ns) 180{ 181 int64_t time_needed_ns = ft->comp_time_ns; 182 int64_t predicted_present_time_ns = ft->last_present_time_ns + ft->frame_period_ns; 183 184 while (now_ns + time_needed_ns > predicted_present_time_ns) { 185 predicted_present_time_ns += ft->frame_period_ns; 186 } 187 188 return predicted_present_time_ns; 189} 190 191static int64_t 192calc_display_time(struct fake_timing *ft, int64_t present_time_ns) 193{ 194 double offset_ms = ft->present_to_display_offset_ms.val; 195 int64_t offset_ns = time_ms_f_to_ns(offset_ms); 196 return present_time_ns + offset_ns; 197} 198 199static int64_t 200get_percent_of_time(int64_t time_ns, double fraction_percent) 201{ 202 double fraction = fraction_percent / 100.0; 203 return time_s_to_ns(time_ns_to_s(time_ns) * fraction); 204} 205 206static void 207print_and_reset(struct fake_timing *ft) 208{ 209 struct u_pp_sink_stack_only sink; 210 u_pp_delegate_t dg = u_pp_sink_stack_only_init(&sink); 211 212 u_pp(dg, "Compositor frame timing:\n"); 213 u_ls_ns_print_header(dg); 214 u_pp(dg, "\n"); 215 u_ls_ns_print_and_reset(&ft->cpu, dg); 216 u_pp(dg, "\n"); 217 u_ls_ns_print_and_reset(&ft->draw, dg); 218 u_pp(dg, "\n"); 219 u_ls_ns_print_and_reset(&ft->submit, dg); 220 u_pp(dg, "\n"); 221 u_ls_ns_print_and_reset(&ft->gpu, dg); 222 u_pp(dg, "\n"); 223 u_ls_ns_print_and_reset(&ft->gpu_delay, dg); 224 u_pp(dg, "\n"); 225 u_ls_ns_print_and_reset(&ft->total_frame, dg); 226 227 UPC_LOG_I("%s", sink.buffer); 228} 229 230static void 231calc_frame_stats(struct fake_timing *ft, struct frame *f) 232{ 233 if (!debug_get_bool_option_live_stats()) { 234 return; 235 } 236 237 int64_t cpu_ns = f->when_began_ns - f->when_woke_ns; 238 int64_t draw_ns = f->when_submit_began_ns - f->when_began_ns; 239 int64_t submit_ns = f->when_submit_end_ns - f->when_submit_began_ns; 240 241 bool full = false; 242 full |= u_ls_ns_add(&ft->cpu, cpu_ns); 243 full |= u_ls_ns_add(&ft->draw, draw_ns); 244 full |= u_ls_ns_add(&ft->submit, submit_ns); 245 246 if (full) { 247 print_and_reset(ft); 248 } 249} 250 251static void 252calc_gpu_stats(struct fake_timing *ft, struct frame *f, int64_t gpu_start_ns, int64_t gpu_end_ns) 253{ 254 if (!debug_get_bool_option_live_stats()) { 255 return; 256 } 257 258 int64_t then_ns = f->when_submit_began_ns; 259 int64_t delay_ns = gpu_start_ns > then_ns ? gpu_start_ns - then_ns : 0; 260 int64_t gpu_ns = gpu_end_ns - gpu_start_ns; 261 int64_t frame_ns = gpu_end_ns - f->when_woke_ns; 262 263 bool full = false; 264 full |= u_ls_ns_add(&ft->gpu, gpu_ns); 265 full |= u_ls_ns_add(&ft->gpu_delay, delay_ns); 266 full |= u_ls_ns_add(&ft->total_frame, frame_ns); 267 268 if (full) { 269 print_and_reset(ft); 270 } 271} 272 273 274/* 275 * 276 * Member functions. 277 * 278 */ 279 280static void 281pc_predict(struct u_pacing_compositor *upc, 282 int64_t now_ns, 283 int64_t *out_frame_id, 284 int64_t *out_wake_up_time_ns, 285 int64_t *out_desired_present_time_ns, 286 int64_t *out_present_slop_ns, 287 int64_t *out_predicted_display_time_ns, 288 int64_t *out_predicted_display_period_ns, 289 int64_t *out_min_display_period_ns) 290{ 291 struct fake_timing *ft = fake_timing(upc); 292 293 struct frame *f = get_new_frame(ft); 294 295 int64_t frame_id = f->frame_id; 296 int64_t desired_present_time_ns = predict_next_frame_present_time(ft, now_ns); 297 int64_t predicted_display_time_ns = calc_display_time(ft, desired_present_time_ns); 298 299 int64_t wake_up_time_ns = desired_present_time_ns - ft->comp_time_ns; 300 int64_t present_slop_ns = U_TIME_HALF_MS_IN_NS; 301 int64_t predicted_display_period_ns = ft->frame_period_ns; 302 int64_t min_display_period_ns = ft->frame_period_ns; 303 304 // Set the frame info. 305 f->predicted_wake_up_time_ns = wake_up_time_ns; 306 f->predicted_present_time_ns = desired_present_time_ns; 307 f->predicted_display_time_ns = predicted_display_time_ns; 308 f->predicted_display_period_ns = predicted_display_period_ns; 309 310 *out_frame_id = frame_id; 311 *out_wake_up_time_ns = wake_up_time_ns; 312 *out_desired_present_time_ns = desired_present_time_ns; 313 *out_present_slop_ns = present_slop_ns; 314 *out_predicted_display_time_ns = predicted_display_time_ns; 315 *out_predicted_display_period_ns = predicted_display_period_ns; 316 *out_min_display_period_ns = min_display_period_ns; 317 318 if (!u_metrics_is_active()) { 319 return; 320 } 321 322 struct u_metrics_system_frame umsf = { 323 .frame_id = frame_id, 324 .predicted_display_time_ns = predicted_display_time_ns, 325 .predicted_display_period_ns = predicted_display_period_ns, 326 .desired_present_time_ns = desired_present_time_ns, 327 .wake_up_time_ns = wake_up_time_ns, 328 .present_slop_ns = present_slop_ns, 329 }; 330 331 u_metrics_write_system_frame(&umsf); 332} 333 334static void 335pc_mark_point(struct u_pacing_compositor *upc, enum u_timing_point point, int64_t frame_id, int64_t when_ns) 336{ 337 struct fake_timing *ft = fake_timing(upc); 338 struct frame *f = get_frame_or_null(ft, frame_id); 339 340 // Just drop info if no frame found. 341 if (f == NULL) { 342 return; 343 } 344 345 // To help validate calling code. 346 switch (point) { 347 case U_TIMING_POINT_WAKE_UP: f->when_woke_ns = when_ns; break; 348 case U_TIMING_POINT_BEGIN: f->when_began_ns = when_ns; break; 349 case U_TIMING_POINT_SUBMIT_BEGIN: f->when_submit_began_ns = when_ns; break; 350 case U_TIMING_POINT_SUBMIT_END: 351 f->when_submit_end_ns = when_ns; 352 calc_frame_stats(ft, f); 353 break; 354 default: assert(false); 355 } 356} 357 358static void 359pc_info(struct u_pacing_compositor *upc, 360 int64_t frame_id, 361 int64_t desired_present_time_ns, 362 int64_t actual_present_time_ns, 363 int64_t earliest_present_time_ns, 364 int64_t present_margin_ns, 365 int64_t when_ns) 366{ 367 /* 368 * The compositor might call this function because it selected the 369 * fake timing code even tho displaying timing is available. 370 */ 371} 372 373static void 374pc_info_gpu( 375 struct u_pacing_compositor *upc, int64_t frame_id, int64_t gpu_start_ns, int64_t gpu_end_ns, int64_t when_ns) 376{ 377 struct fake_timing *ft = fake_timing(upc); 378 379 struct frame *f = get_frame_or_null(ft, frame_id); 380 if (f != NULL) { 381 calc_gpu_stats(ft, f, gpu_start_ns, gpu_end_ns); 382 } 383 384 if (u_metrics_is_active()) { 385 struct u_metrics_system_gpu_info umgi = { 386 .frame_id = frame_id, 387 .gpu_start_ns = gpu_start_ns, 388 .gpu_end_ns = gpu_end_ns, 389 .when_ns = when_ns, 390 }; 391 392 u_metrics_write_system_gpu_info(&umgi); 393 } 394 395#ifdef U_TRACE_PERCETTO // Uses Percetto specific things. 396 if (U_TRACE_CATEGORY_IS_ENABLED(timing)) { 397#define TE_BEG(TRACK, TIME, NAME) U_TRACE_EVENT_BEGIN_ON_TRACK_DATA(timing, TRACK, TIME, NAME, PERCETTO_I(frame_id)) 398#define TE_END(TRACK, TIME) U_TRACE_EVENT_END_ON_TRACK(timing, TRACK, TIME) 399 400 TE_BEG(pc_gpu, gpu_start_ns, "gpu"); 401 TE_END(pc_gpu, gpu_end_ns); 402 403#undef TE_BEG 404#undef TE_END 405 } 406#endif 407 408#ifdef U_TRACE_TRACY 409 int64_t diff_ns = gpu_end_ns - gpu_start_ns; 410 TracyCPlot("Compositor GPU(ms)", time_ns_to_ms_f(diff_ns)); 411#endif 412} 413 414static void 415pc_update_vblank_from_display_control(struct u_pacing_compositor *upc, int64_t last_vblank_ns) 416{ 417 struct fake_timing *ft = fake_timing(upc); 418 419 // Use the last vblank time to sync to the output. 420 ft->last_present_time_ns = last_vblank_ns; 421} 422 423static void 424pc_update_present_offset(struct u_pacing_compositor *upc, int64_t frame_id, int64_t present_to_display_offset_ns) 425{ 426 struct fake_timing *ft = fake_timing(upc); 427 428 // not associating with frame IDs right now. 429 (void)frame_id; 430 431 double offset_ms = time_ns_to_ms_f(present_to_display_offset_ns); 432 433 ft->present_to_display_offset_ms.val = offset_ms; 434} 435 436static void 437pc_destroy(struct u_pacing_compositor *upc) 438{ 439 struct fake_timing *ft = fake_timing(upc); 440 441 u_var_remove_root(ft); 442 443 free(ft); 444} 445 446 447/* 448 * 449 * 'Exported' functions. 450 * 451 */ 452 453xrt_result_t 454u_pc_fake_create(int64_t estimated_frame_period_ns, int64_t now_ns, struct u_pacing_compositor **out_upc) 455{ 456 struct fake_timing *ft = U_TYPED_CALLOC(struct fake_timing); 457 ft->base.predict = pc_predict; 458 ft->base.mark_point = pc_mark_point; 459 ft->base.info = pc_info; 460 ft->base.info_gpu = pc_info_gpu; 461 ft->base.update_vblank_from_display_control = pc_update_vblank_from_display_control; 462 ft->base.update_present_offset = pc_update_present_offset; 463 ft->base.destroy = pc_destroy; 464 ft->frame_period_ns = estimated_frame_period_ns; 465 466 snprintf(ft->cpu.name, ARRAY_SIZE(ft->cpu.name), "cpu"); 467 snprintf(ft->draw.name, ARRAY_SIZE(ft->draw.name), "draw"); 468 snprintf(ft->submit.name, ARRAY_SIZE(ft->submit.name), "submit"); 469 snprintf(ft->gpu.name, ARRAY_SIZE(ft->gpu.name), "gpu"); 470 snprintf(ft->gpu_delay.name, ARRAY_SIZE(ft->gpu_delay.name), "gpu_delay"); 471 snprintf(ft->total_frame.name, ARRAY_SIZE(ft->total_frame.name), "total_frame"); 472 473 // An arbitrary guess, that happens to be based on Index. 474 float present_to_display_offset_ms = debug_get_float_option_present_to_display_offset_ms(); 475 476 // Present to display offset, aka vblank to pixel turning into photons. 477 ft->present_to_display_offset_ms = (struct u_var_draggable_f32){ 478 .val = present_to_display_offset_ms, 479 .min = 1.0, // A lot of things assumes this is not negative. 480 .step = 0.1, 481 .max = +40.0, 482 }; 483 484 // Set comp_time_ms to a percentage of the frame time. 485 float comp_time_fraction_percent = fabs(debug_get_float_option_comp_time_fraction_percent()); 486 ft->comp_time_ns = get_percent_of_time(estimated_frame_period_ns, comp_time_fraction_percent); 487 488 // Or at least a certain amount of time. 489 double min_comp_time_ms_f = debug_get_float_option_min_comp_time_ms(); 490 int64_t min_comp_time_ns = time_ms_f_to_ns(min_comp_time_ms_f); 491 492 if (ft->comp_time_ns < min_comp_time_ns) { 493 ft->comp_time_ns = min_comp_time_ns; 494 } 495 496 // Make the next present time be in the future. 497 ft->last_present_time_ns = now_ns + U_TIME_1MS_IN_NS * 50; 498 499 // U variable tracking. 500 u_var_add_root(ft, "Compositor timing info", true); 501 u_var_add_draggable_f32(ft, &ft->present_to_display_offset_ms, "Present to display offset(ms)"); 502 u_var_add_ro_i64(ft, &ft->frame_period_ns, "Frame period(ns)"); 503 u_var_add_i64(ft, &ft->comp_time_ns, "Compositor time(ns)"); 504 u_var_add_ro_i64(ft, &ft->last_present_time_ns, "Last present time(ns)"); 505 506 // Return value. 507 *out_upc = &ft->base; 508 509 UPC_LOG_I( 510 "Created pacer (non-feedback version aka \"fake\")" 511 "\n\testimated_frame_period: %fms" 512 "\n\tpercentage: %f%%" 513 "\n\tmin_comp_time: %fms" 514 "\n\tcomp_time: %fms", 515 time_ns_to_ms_f(estimated_frame_period_ns), comp_time_fraction_percent, min_comp_time_ms_f, 516 time_ns_to_ms_f(ft->comp_time_ns)); 517 518 return XRT_SUCCESS; 519}