The open source OpenXR runtime
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}