The open source OpenXR runtime
at main 788 lines 23 kB view raw
1// Copyright 2020-2022, Collabora, Ltd. 2// SPDX-License-Identifier: BSL-1.0 3/*! 4 * @file 5 * @brief Shared frame timing code. 6 * @author Jakob Bornecrantz <jakob@collabora.com> 7 * @ingroup aux_util 8 */ 9 10#include "os/os_time.h" 11 12#include "util/u_time.h" 13#include "util/u_misc.h" 14#include "util/u_debug.h" 15#include "util/u_pacing.h" 16#include "util/u_metrics.h" 17#include "util/u_logging.h" 18#include "util/u_trace_marker.h" 19 20#include <stdio.h> 21#include <assert.h> 22 23DEBUG_GET_ONCE_LOG_OPTION(log_level, "U_PACING_COMPOSITOR_LOG", U_LOGGING_WARN) 24 25#define UPC_LOG_T(...) U_LOG_IFL_T(debug_get_log_option_log_level(), __VA_ARGS__) 26#define UPC_LOG_D(...) U_LOG_IFL_D(debug_get_log_option_log_level(), __VA_ARGS__) 27#define UPC_LOG_I(...) U_LOG_IFL_I(debug_get_log_option_log_level(), __VA_ARGS__) 28#define UPC_LOG_W(...) U_LOG_IFL_W(debug_get_log_option_log_level(), __VA_ARGS__) 29#define UPC_LOG_E(...) U_LOG_IFL_E(debug_get_log_option_log_level(), __VA_ARGS__) 30 31#define NUM_FRAMES 16 32 33#define PRESENT_SLOP_NS (U_TIME_HALF_MS_IN_NS) 34 35 36/* 37 * 38 * Compositor pacing code, which depends directly on the display's timing. 39 * 40 */ 41 42enum frame_state 43{ 44 STATE_SKIPPED = -1, 45 STATE_CLEARED = 0, 46 STATE_PREDICTED = 1, 47 STATE_WOKE = 2, 48 STATE_BEGAN = 3, 49 STATE_SUBMITTED = 4, 50 STATE_INFO = 5, 51}; 52 53struct frame 54{ 55 //! An arbitrary id that identifies this frame. Set in `create_frame`. 56 int64_t frame_id; 57 58 //! When this frame was last used for a prediction. Set in `predict_next_frame`. 59 int64_t when_predict_ns; 60 61 //! When should the compositor wake up. Set in `predict_next_frame`. 62 int64_t wake_up_time_ns; 63 64 //! When we last woke up the compositor after its equivalent of wait_frame. Set in `pc_mark_point` with 65 //! `U_TIMING_POINT_WAKE_UP`. 66 int64_t when_woke_ns; 67 68 //! When the compositor started rendering a frame 69 int64_t when_began_ns; 70 71 //! When the compositor finished rendering a frame 72 int64_t when_submitted_ns; 73 74 //! When new frame timing info was last added. 75 int64_t when_infoed_ns; 76 77 //! How much time we currently expect the compositor to take rendering a frame. Updated in `predict_next_frame` 78 int64_t current_comp_time_ns; 79 80 int64_t expected_done_time_ns; //!< When we expect the compositor to be done with its frame. 81 int64_t desired_present_time_ns; //!< The GPU should start scanning out at this time. 82 int64_t predicted_display_time_ns; //!< At what time have we predicted that pixels turns to photons. 83 int64_t present_margin_ns; 84 int64_t actual_present_time_ns; 85 int64_t earliest_present_time_ns; 86 87 enum frame_state state; 88}; 89 90struct pacing_compositor 91{ 92 struct u_pacing_compositor base; 93 94 /*! 95 * Very often the present time that we get from the system is only when 96 * the display engine starts scanning out from the buffers we provided, 97 * and not when the pixels turned into photons that the user sees. 98 */ 99 int64_t present_to_display_offset_ns; 100 101 /*! 102 * Frame period of the device. 103 */ 104 int64_t frame_period_ns; 105 106 /*! 107 * The amount of time that the compositor needs to render frame. 108 */ 109 int64_t comp_time_ns; 110 111 /*! 112 * Used to generate frame IDs. 113 */ 114 int64_t next_frame_id; 115 116 /*! 117 * The maximum amount we give to the compositor. 118 */ 119 int64_t comp_time_max_ns; 120 121 /*! 122 * If we missed a frame, back off this much. 123 */ 124 int64_t adjust_missed_ns; 125 126 /*! 127 * Adjustment of time if we didn't miss the frame, 128 * also used as range to stay around timing target. 129 */ 130 int64_t adjust_non_miss_ns; 131 132 /*! 133 * Extra time between end of draw time and when the present happens. 134 */ 135 int64_t margin_ns; 136 137 /*! 138 * Frame store. 139 */ 140 struct frame frames[NUM_FRAMES]; 141}; 142 143 144/* 145 * 146 * Helper functions. 147 * 148 */ 149 150static inline struct pacing_compositor * 151pacing_compositor(struct u_pacing_compositor *upc) 152{ 153 return (struct pacing_compositor *)upc; 154} 155 156static double 157ns_to_ms(int64_t t) 158{ 159 return (double)(t / 1000) / 1000.0; 160} 161 162static int64_t 163get_percent_of_time(int64_t time_ns, uint32_t fraction_percent) 164{ 165 double fraction = (double)fraction_percent / 100.0; 166 return time_s_to_ns(time_ns_to_s(time_ns) * fraction); 167} 168 169static int64_t 170calc_total_comp_time(struct pacing_compositor *pc) 171{ 172 return pc->comp_time_ns + pc->margin_ns; 173} 174 175static int64_t 176calc_display_time_from_present_time(struct pacing_compositor *pc, int64_t desired_present_time_ns) 177{ 178 return desired_present_time_ns + pc->present_to_display_offset_ns; 179} 180 181static inline bool 182is_within_of_each_other(int64_t l, int64_t r, int64_t range) 183{ 184 int64_t t = (int64_t)l - (int64_t)r; 185 return (-(int64_t)range < t) && (t < (int64_t)range); 186} 187 188static inline bool 189is_within_half_ms(int64_t l, int64_t r) 190{ 191 return is_within_of_each_other(l, r, U_TIME_HALF_MS_IN_NS); 192} 193 194/*! 195 * Gets a frame data structure based on the @p frame_id. 196 * 197 * Note that this is done modulo the number of frame data structs we hold: the data in the frame you receive may not 198 * match the @p frame_id you passed! 199 * 200 * @see create_frame to create a frame id and (partially) initialize the frame data structure, @ref do_clean_slate_frame 201 * for a more complete initialization 202 */ 203static struct frame * 204get_frame(struct pacing_compositor *pc, int64_t frame_id) 205{ 206 assert(frame_id >= 0); 207 assert((uint64_t)frame_id <= (uint64_t)SIZE_MAX); 208 209 size_t index = (size_t)(frame_id % NUM_FRAMES); 210 211 return &pc->frames[index]; 212} 213 214/*! 215 * Assign the next available frame ID, initialize the corresponding frame data with the ID and @p state, and return a 216 * pointer to that frame data. 217 * 218 * Fields other than frame::frame_id and frame::state are not modified, so may have old data in them. This may be a 219 * feature rather than a bug. 220 */ 221static struct frame * 222create_frame(struct pacing_compositor *pc, enum frame_state state) 223{ 224 int64_t frame_id = ++pc->next_frame_id; 225 struct frame *f = get_frame(pc, frame_id); 226 227 f->frame_id = frame_id; 228 f->state = state; 229 230 return f; 231} 232 233/*! 234 * Gets the most recent frame data whose state is greater than or equal to @p state, if any 235 * 236 * @return a frame pointer, or null if no frames have at least @p state 237 */ 238static struct frame * 239get_latest_frame_with_state_at_least(struct pacing_compositor *pc, enum frame_state state) 240{ 241 int64_t start_from = pc->next_frame_id; 242 int64_t count = 1; 243 244 while (start_from >= count && count < NUM_FRAMES) { 245 int64_t frame_id = start_from - count; 246 count++; 247 struct frame *f = get_frame(pc, frame_id); 248 if (f->state >= state && f->frame_id == frame_id) { 249 return f; 250 } 251 } 252 253 return NULL; 254} 255 256/*! 257 * "Create" a frame ID in state @ref frame_state::STATE_PREDICTED (by calling @ref create_frame), and additionally 258 * initialize frame::desired_present_time_ns (with a crude estimate) and frame::when_predict_ns. 259 */ 260static struct frame * 261do_clean_slate_frame(struct pacing_compositor *pc, int64_t now_ns) 262{ 263 struct frame *f = create_frame(pc, STATE_PREDICTED); 264 265 // Wild shot in the dark. 266 int64_t the_time_ns = now_ns + pc->frame_period_ns * 10; 267 f->when_predict_ns = now_ns; 268 f->desired_present_time_ns = the_time_ns; 269 270 return f; 271} 272 273/*! 274 * Find the next possible present time for rendering that has not yet occurred, and create a frame/frame id with that 275 * prediction in it. 276 */ 277static struct frame * 278walk_forward_through_frames(struct pacing_compositor *pc, int64_t last_present_time_ns, int64_t now_ns) 279{ 280 // This is the earliest possible time we could present, assuming rendering still must take place. 281 int64_t from_time_ns = now_ns + calc_total_comp_time(pc); 282 int64_t desired_present_time_ns = last_present_time_ns + pc->frame_period_ns; 283 284 while (desired_present_time_ns <= from_time_ns) { 285 UPC_LOG_D( 286 "Skipped!" // 287 "\n\tfrom_time_ns: %" PRIu64 // 288 "\n\tdesired_present_time_ns: %" PRIu64 // 289 "\n\tdiff_ms: %.2f", // 290 from_time_ns, // 291 desired_present_time_ns, // 292 ns_to_ms(from_time_ns - desired_present_time_ns)); // 293 294 // Try next frame period. 295 desired_present_time_ns += pc->frame_period_ns; 296 } 297 298 struct frame *f = create_frame(pc, STATE_PREDICTED); 299 f->when_predict_ns = now_ns; 300 f->desired_present_time_ns = desired_present_time_ns; 301 302 return f; 303} 304 305static struct frame * 306predict_next_frame(struct pacing_compositor *pc, int64_t now_ns) 307{ 308 struct frame *f = NULL; 309 // Last earliest display time, can be zero. 310 struct frame *last_predicted = get_latest_frame_with_state_at_least(pc, STATE_PREDICTED); 311 struct frame *last_completed = get_latest_frame_with_state_at_least(pc, STATE_INFO); 312 if (last_predicted == NULL && last_completed == NULL) { 313 f = do_clean_slate_frame(pc, now_ns); 314 } else if (last_completed == last_predicted) { 315 // Very high probability that we missed a frame. 316 f = walk_forward_through_frames(pc, last_completed->earliest_present_time_ns, now_ns); 317 } else if (last_completed != NULL) { 318 assert(last_predicted != NULL); 319 assert(last_predicted->frame_id > last_completed->frame_id); 320 321 int64_t diff_id = last_predicted->frame_id - last_completed->frame_id; 322 int64_t diff_ns = last_completed->desired_present_time_ns - last_completed->earliest_present_time_ns; 323 int64_t adjusted_last_present_time_ns = 324 last_completed->earliest_present_time_ns + diff_id * pc->frame_period_ns; 325 326 if (diff_ns > U_TIME_1MS_IN_NS) { 327 UPC_LOG_D("Large diff!"); 328 } 329 if (diff_id > 1) { 330 UPC_LOG_D( 331 "diff_id > 1\n" 332 "\tdiff_id: %" PRIi64 333 "\n" 334 "\tadjusted_last_present_time_ns: %" PRIu64, 335 diff_id, adjusted_last_present_time_ns); 336 } 337 338 if (diff_id > 1) { 339 diff_id = 1; 340 } 341 342 f = walk_forward_through_frames(pc, adjusted_last_present_time_ns, now_ns); 343 } else { 344 assert(last_predicted != NULL); 345 346 f = walk_forward_through_frames(pc, last_predicted->predicted_display_time_ns, now_ns); 347 } 348 349 f->predicted_display_time_ns = calc_display_time_from_present_time(pc, f->desired_present_time_ns); 350 f->wake_up_time_ns = f->desired_present_time_ns - calc_total_comp_time(pc); 351 f->current_comp_time_ns = pc->comp_time_ns; 352 353 return f; 354} 355 356static void 357adjust_comp_time(struct pacing_compositor *pc, struct frame *f) 358{ 359 int64_t comp_time_ns = pc->comp_time_ns; 360 361 if (f->actual_present_time_ns > f->desired_present_time_ns && 362 !is_within_half_ms(f->actual_present_time_ns, f->desired_present_time_ns)) { 363 double missed_ms = ns_to_ms(f->actual_present_time_ns - f->desired_present_time_ns); 364 UPC_LOG_W("Frame %" PRIu64 " missed by %.2f!", f->frame_id, missed_ms); 365 366 comp_time_ns += pc->adjust_missed_ns; 367 if (comp_time_ns > pc->comp_time_max_ns) { 368 comp_time_ns = pc->comp_time_max_ns; 369 } 370 371 pc->comp_time_ns = comp_time_ns; 372 return; 373 } 374 375 // We want the GPU work to stop at margin_ns. 376 if (is_within_of_each_other( // 377 f->present_margin_ns, // 378 pc->margin_ns, // 379 pc->adjust_non_miss_ns)) { 380 // Nothing to do, the GPU ended its work +-adjust_non_miss_ns 381 // of margin_ns before the present started. 382 return; 383 } 384 385 // We didn't miss the frame but we were outside the range: adjust the compositor time. 386 if (f->present_margin_ns > pc->margin_ns) { 387 // Approach the present time. 388 pc->comp_time_ns -= pc->adjust_non_miss_ns; 389 } else { 390 // Back off the present time. 391 pc->comp_time_ns += pc->adjust_non_miss_ns; 392 } 393} 394 395 396/* 397 * 398 * Metrics and tracing. 399 * 400 */ 401 402static void 403do_metrics(struct pacing_compositor *pc, struct frame *f) 404{ 405 if (!u_metrics_is_active()) { 406 return; 407 } 408 409 struct u_metrics_system_present_info umpi = { 410 .frame_id = f->frame_id, 411 .expected_comp_time_ns = f->current_comp_time_ns, 412 .predicted_wake_up_time_ns = f->wake_up_time_ns, 413 .predicted_done_time_ns = f->expected_done_time_ns, 414 .predicted_display_time_ns = f->predicted_display_time_ns, 415 .when_predict_ns = f->when_predict_ns, 416 .when_woke_ns = f->when_woke_ns, 417 .when_began_ns = f->when_began_ns, 418 .when_submitted_ns = f->when_submitted_ns, 419 .when_infoed_ns = f->when_infoed_ns, 420 .desired_present_time_ns = f->desired_present_time_ns, 421 .present_slop_ns = PRESENT_SLOP_NS, 422 .present_margin_ns = f->present_margin_ns, 423 .actual_present_time_ns = f->actual_present_time_ns, 424 .earliest_present_time_ns = f->earliest_present_time_ns, 425 }; 426 427 u_metrics_write_system_present_info(&umpi); 428} 429 430static void 431do_tracing(struct pacing_compositor *pc, struct frame *f) 432{ 433#ifdef U_TRACE_PERCETTO // Uses Percetto specific things. 434 if (!U_TRACE_CATEGORY_IS_ENABLED(timing)) { 435 return; 436 } 437 438#define TE_BEG(TRACK, TIME, NAME) U_TRACE_EVENT_BEGIN_ON_TRACK_DATA(timing, TRACK, TIME, NAME, PERCETTO_I(f->frame_id)) 439#define TE_END(TRACK, TIME) U_TRACE_EVENT_END_ON_TRACK(timing, TRACK, TIME) 440 441 442 /* 443 * 444 * CPU 445 * 446 */ 447 448 TE_BEG(pc_cpu, f->when_predict_ns, "sleep"); 449 TE_END(pc_cpu, f->wake_up_time_ns); 450 451 int64_t oversleep_start_ns = f->wake_up_time_ns + 1; 452 if (f->when_woke_ns > oversleep_start_ns) { 453 TE_BEG(pc_cpu, oversleep_start_ns, "oversleep"); 454 TE_END(pc_cpu, f->when_woke_ns); 455 } 456 457 458 /* 459 * 460 * GPU 461 * 462 */ 463 464 int64_t gpu_end_ns = f->actual_present_time_ns - f->present_margin_ns; 465 if (gpu_end_ns > f->when_submitted_ns) { 466 TE_BEG(pc_gpu, f->when_submitted_ns, "gpu"); 467 TE_END(pc_gpu, gpu_end_ns); 468 } else { 469 TE_BEG(pc_gpu, gpu_end_ns, "gpu-time-travel"); 470 TE_END(pc_gpu, f->when_submitted_ns); 471 } 472 473 474 /* 475 * 476 * Margin 477 * 478 */ 479 480 if (gpu_end_ns < f->desired_present_time_ns) { 481 TE_BEG(pc_margin, gpu_end_ns, "margin"); 482 TE_END(pc_margin, f->desired_present_time_ns); 483 } 484 485 486 /* 487 * 488 * ERROR 489 * 490 */ 491 492 if (!is_within_half_ms(f->actual_present_time_ns, f->desired_present_time_ns)) { 493 if (f->actual_present_time_ns > f->desired_present_time_ns) { 494 TE_BEG(pc_error, f->desired_present_time_ns, "slippage"); 495 TE_END(pc_error, f->actual_present_time_ns); 496 } else { 497 TE_BEG(pc_error, f->actual_present_time_ns, "run-ahead"); 498 TE_END(pc_error, f->desired_present_time_ns); 499 } 500 } 501 502 503 /* 504 * 505 * Info 506 * 507 */ 508 509 if (f->when_infoed_ns >= f->actual_present_time_ns) { 510 TE_BEG(pc_info, f->actual_present_time_ns, "info"); 511 TE_END(pc_info, f->when_infoed_ns); 512 } else { 513 TE_BEG(pc_info, f->when_infoed_ns, "info_before"); 514 TE_END(pc_info, f->actual_present_time_ns); 515 } 516 517 518 /* 519 * 520 * Present 521 * 522 */ 523 524 if (f->actual_present_time_ns != f->earliest_present_time_ns) { 525 U_TRACE_INSTANT_ON_TRACK(timing, pc_present, f->earliest_present_time_ns, "earliest"); 526 } 527 if (!is_within_half_ms(f->desired_present_time_ns, f->earliest_present_time_ns)) { 528 U_TRACE_INSTANT_ON_TRACK(timing, pc_present, f->desired_present_time_ns, "predicted"); 529 } 530 U_TRACE_INSTANT_ON_TRACK(timing, pc_present, f->actual_present_time_ns, "vsync"); 531 532 533 /* 534 * 535 * Compositor time 536 * 537 */ 538 539 TE_BEG(pc_allotted, f->wake_up_time_ns, "allotted"); 540 TE_END(pc_allotted, f->wake_up_time_ns + f->current_comp_time_ns); 541 542#undef TE_BEG 543#undef TE_END 544#endif 545} 546 547 548/* 549 * 550 * Member functions. 551 * 552 */ 553 554static void 555pc_predict(struct u_pacing_compositor *upc, 556 int64_t now_ns, 557 int64_t *out_frame_id, 558 int64_t *out_wake_up_time_ns, 559 int64_t *out_desired_present_time_ns, 560 int64_t *out_present_slop_ns, 561 int64_t *out_predicted_display_time_ns, 562 int64_t *out_predicted_display_period_ns, 563 int64_t *out_min_display_period_ns) 564{ 565 struct pacing_compositor *pc = pacing_compositor(upc); 566 567 struct frame *f = predict_next_frame(pc, now_ns); 568 569 int64_t wake_up_time_ns = f->wake_up_time_ns; 570 int64_t desired_present_time_ns = f->desired_present_time_ns; 571 int64_t present_slop_ns = PRESENT_SLOP_NS; 572 int64_t predicted_display_time_ns = f->predicted_display_time_ns; 573 int64_t predicted_display_period_ns = pc->frame_period_ns; 574 int64_t min_display_period_ns = pc->frame_period_ns; 575 576 *out_frame_id = f->frame_id; 577 *out_wake_up_time_ns = wake_up_time_ns; 578 *out_desired_present_time_ns = desired_present_time_ns; 579 *out_present_slop_ns = present_slop_ns; 580 *out_predicted_display_time_ns = predicted_display_time_ns; 581 *out_predicted_display_period_ns = predicted_display_period_ns; 582 *out_min_display_period_ns = min_display_period_ns; 583} 584 585static void 586pc_mark_point(struct u_pacing_compositor *upc, enum u_timing_point point, int64_t frame_id, int64_t when_ns) 587{ 588 struct pacing_compositor *pc = pacing_compositor(upc); 589 struct frame *f = get_frame(pc, frame_id); 590 if (f->frame_id != frame_id) { 591 UPC_LOG_W("Discarded point marking for unsubmitted or expired frame_id %" PRIx64, frame_id); 592 struct frame *last = get_latest_frame_with_state_at_least(pc, STATE_PREDICTED); 593 if (last != NULL) { 594 UPC_LOG_W("The latest frame_id we have predicted is %" PRIx64, last->frame_id); 595 } 596 return; 597 } 598 599 switch (point) { 600 case U_TIMING_POINT_WAKE_UP: 601 assert(f->state == STATE_PREDICTED); 602 f->state = STATE_WOKE; 603 f->when_woke_ns = when_ns; 604 break; 605 case U_TIMING_POINT_BEGIN: 606 assert(f->state == STATE_WOKE); 607 f->state = STATE_BEGAN; 608 f->when_began_ns = when_ns; 609 break; 610 case U_TIMING_POINT_SUBMIT_BEGIN: 611 // No-op 612 break; 613 case U_TIMING_POINT_SUBMIT_END: 614 assert(f->state == STATE_BEGAN); 615 f->state = STATE_SUBMITTED; 616 f->when_submitted_ns = when_ns; 617 break; 618 default: assert(false); 619 } 620} 621 622static void 623pc_info(struct u_pacing_compositor *upc, 624 int64_t frame_id, 625 int64_t desired_present_time_ns, 626 int64_t actual_present_time_ns, 627 int64_t earliest_present_time_ns, 628 int64_t present_margin_ns, 629 int64_t when_ns) 630{ 631 struct pacing_compositor *pc = pacing_compositor(upc); 632 (void)pc; 633 634 struct frame *last = get_latest_frame_with_state_at_least(pc, STATE_INFO); 635 struct frame *f = get_frame(pc, frame_id); 636 if (f->frame_id != frame_id) { 637 UPC_LOG_W("Discarded info for unsubmitted or expired frame_id %" PRIx64, frame_id); 638 if (last != NULL) { 639 UPC_LOG_W("The latest frame_id we have info for is %" PRIx64, last->frame_id); 640 } 641 return; 642 } 643 644 assert(f->state == STATE_SUBMITTED); 645 XRT_MAYBE_UNUSED int64_t unslopped_desired_present_time_ns = desired_present_time_ns + PRESENT_SLOP_NS; 646 assert(f->desired_present_time_ns == desired_present_time_ns || 647 f->desired_present_time_ns == unslopped_desired_present_time_ns); 648 649 f->when_infoed_ns = when_ns; 650 f->actual_present_time_ns = actual_present_time_ns; 651 f->earliest_present_time_ns = earliest_present_time_ns; 652 f->present_margin_ns = present_margin_ns; 653 f->state = STATE_INFO; 654 655 int64_t since_last_frame_ns = 0; 656 if (last != NULL) { 657 since_last_frame_ns = f->desired_present_time_ns - last->desired_present_time_ns; 658 } 659 660 // Adjust the frame timing. 661 adjust_comp_time(pc, f); 662 663 double present_margin_ms = ns_to_ms(present_margin_ns); 664 double since_last_frame_ms = ns_to_ms(since_last_frame_ns); 665 666 UPC_LOG_T( 667 "Got" 668 "\n\tframe_id: 0x%08" PRIx64 // 669 "\n\twhen_predict_ns: %" PRIu64 // 670 "\n\twhen_woke_ns: %" PRIu64 // 671 "\n\twhen_submitted_ns: %" PRIu64 // 672 "\n\twhen_infoed_ns: %" PRIu64 // 673 "\n\tsince_last_frame_ms: %.2fms" // 674 "\n\tdesired_present_time_ns: %" PRIu64 // 675 "\n\tactual_present_time_ns: %" PRIu64 // 676 "\n\tearliest_present_time_ns: %" PRIu64 // 677 "\n\tpresent_margin_ns: %" PRIu64 // 678 "\n\tpresent_margin_ms: %.2fms", // 679 frame_id, // 680 f->when_predict_ns, // 681 f->when_woke_ns, // 682 f->when_submitted_ns, // 683 f->when_infoed_ns, // 684 since_last_frame_ms, // 685 f->desired_present_time_ns, // 686 f->actual_present_time_ns, // 687 f->earliest_present_time_ns, // 688 f->present_margin_ns, // 689 present_margin_ms); // 690 691 // Write out metrics and tracing data. 692 do_metrics(pc, f); 693 do_tracing(pc, f); 694} 695 696static void 697pc_info_gpu( 698 struct u_pacing_compositor *upc, int64_t frame_id, int64_t gpu_start_ns, int64_t gpu_end_ns, int64_t when_ns) 699{ 700 if (u_metrics_is_active()) { 701 struct u_metrics_system_gpu_info umgi = { 702 .frame_id = frame_id, 703 .gpu_start_ns = gpu_start_ns, 704 .gpu_end_ns = gpu_end_ns, 705 .when_ns = when_ns, 706 }; 707 708 u_metrics_write_system_gpu_info(&umgi); 709 } 710} 711 712static void 713pc_update_vblank_from_display_control(struct u_pacing_compositor *upc, int64_t last_vblank_ns) 714{ 715 /* 716 * This is a no-op, here in case display control is used at the 717 * same time as the google extension. We ignore this call. 718 */ 719} 720 721static void 722pc_update_present_offset(struct u_pacing_compositor *upc, int64_t frame_id, int64_t present_to_display_offset_ns) 723{ 724 struct pacing_compositor *pc = pacing_compositor(upc); 725 726 // not associating with frame IDs right now. 727 (void)frame_id; 728 729 pc->present_to_display_offset_ns = present_to_display_offset_ns; 730} 731 732static void 733pc_destroy(struct u_pacing_compositor *upc) 734{ 735 struct pacing_compositor *pc = pacing_compositor(upc); 736 737 free(pc); 738} 739 740const struct u_pc_display_timing_config U_PC_DISPLAY_TIMING_CONFIG_DEFAULT = { 741 // An arbitrary guess. 742 .present_to_display_offset_ns = U_TIME_1MS_IN_NS * 4, 743 .margin_ns = U_TIME_1MS_IN_NS, 744 // Start by assuming the compositor takes 10% of the frame. 745 .comp_time_fraction = 10, 746 // Don't allow the compositor to take more than 30% of the frame. 747 .comp_time_max_fraction = 30, 748 .adjust_missed_fraction = 4, 749 .adjust_non_miss_fraction = 2, 750}; 751 752xrt_result_t 753u_pc_display_timing_create(int64_t estimated_frame_period_ns, 754 const struct u_pc_display_timing_config *config, 755 struct u_pacing_compositor **out_upc) 756{ 757 struct pacing_compositor *pc = U_TYPED_CALLOC(struct pacing_compositor); 758 pc->base.predict = pc_predict; 759 pc->base.mark_point = pc_mark_point; 760 pc->base.info = pc_info; 761 pc->base.info_gpu = pc_info_gpu; 762 pc->base.update_vblank_from_display_control = pc_update_vblank_from_display_control; 763 pc->base.update_present_offset = pc_update_present_offset; 764 pc->base.destroy = pc_destroy; 765 pc->frame_period_ns = estimated_frame_period_ns; 766 767 // Estimate of how long after "present" the eyes see the photons 768 pc->present_to_display_offset_ns = config->present_to_display_offset_ns; 769 770 // Start at this of frame time. 771 pc->comp_time_ns = get_percent_of_time(estimated_frame_period_ns, config->comp_time_fraction); 772 // Max compositor time: if we ever reach this, write a better compositor. (using too much time per frame on the 773 // compositor) 774 pc->comp_time_max_ns = get_percent_of_time(estimated_frame_period_ns, config->comp_time_max_fraction); 775 // When missing, back off in these increments 776 pc->adjust_missed_ns = get_percent_of_time(estimated_frame_period_ns, config->adjust_missed_fraction); 777 // When not missing frames but adjusting compositor time at these increments 778 pc->adjust_non_miss_ns = get_percent_of_time(estimated_frame_period_ns, config->adjust_non_miss_fraction); 779 // Extra margin that is added to compositor time. 780 pc->margin_ns = config->margin_ns; 781 782 *out_upc = &pc->base; 783 784 double estimated_frame_period_ms = ns_to_ms(estimated_frame_period_ns); 785 UPC_LOG_I("Created compositor pacing (%.2fms)", estimated_frame_period_ms); 786 787 return XRT_SUCCESS; 788}