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