The open source OpenXR runtime
at main 15 kB view raw
1// Copyright 2022, Collabora, Ltd. 2// SPDX-License-Identifier: BSL-1.0 3/*! 4 * @file 5 * @brief Frame pacing tests. 6 * @author Rylie Pavlik <rylie.pavlik@collabora.com> 7 */ 8 9#include <util/u_pacing.h> 10 11#include "catch_amalgamated.hpp" 12 13#include "time_utils.hpp" 14 15#include <iostream> 16#include <chrono> 17#include <sstream> 18#include <iomanip> 19#include <queue> 20 21using namespace std::chrono_literals; 22using namespace std::chrono; 23 24static constexpr unanoseconds frame_interval_ns(16ms); 25 26namespace { 27 28int64_t 29getNextPresentAfterTimestampAndKnownPresent(int64_t timestamp_ns, int64_t known_present_ns) 30{ 31 32 while (known_present_ns < timestamp_ns) { 33 known_present_ns += frame_interval_ns.count(); 34 } 35 return known_present_ns; 36} 37int64_t 38getPresentBefore(int64_t timestamp_ns, int64_t known_present_ns) 39{ 40 41 while (known_present_ns >= timestamp_ns && known_present_ns > frame_interval_ns.count()) { 42 known_present_ns -= frame_interval_ns.count(); 43 } 44 return known_present_ns; 45} 46int64_t 47getNextPresentAfterTimestamp(int64_t timestamp_ns, int64_t known_present_ns) 48{ 49 auto present_before_ns = getPresentBefore(timestamp_ns, known_present_ns); 50 return getNextPresentAfterTimestampAndKnownPresent(timestamp_ns, present_before_ns); 51} 52 53struct CompositorPredictions 54{ 55 int64_t frame_id{0}; 56 int64_t wake_up_time_ns{0}; 57 int64_t desired_present_time_ns{0}; 58 int64_t present_slop_ns{0}; 59 int64_t predicted_display_time_ns{0}; 60 int64_t predicted_display_period_ns{0}; 61 int64_t min_display_period_ns{0}; 62}; 63} // namespace 64 65static void 66basicPredictionConsistencyChecks(int64_t now_ns, CompositorPredictions const &predictions) 67{ 68 INFO(predictions.frame_id); 69 INFO(now_ns); 70 CHECK(predictions.wake_up_time_ns >= now_ns); 71 CHECK(predictions.desired_present_time_ns > now_ns); 72 CHECK(predictions.desired_present_time_ns > predictions.wake_up_time_ns); 73 CHECK(predictions.predicted_display_time_ns > now_ns); 74 CHECK(predictions.predicted_display_time_ns > predictions.desired_present_time_ns); 75 // display period predicted to be +- 2ms (arbitrary) 76 CHECK(unanoseconds(predictions.predicted_display_period_ns) < (frame_interval_ns + unanoseconds(2ms))); 77 CHECK(unanoseconds(predictions.predicted_display_period_ns) > (frame_interval_ns - unanoseconds(2ms))); 78} 79 80struct SimulatedDisplayTimingData 81{ 82 SimulatedDisplayTimingData(int64_t id, int64_t desired_present_time, int64_t gpu_finish, int64_t now) 83 : frame_id(id), desired_present_time_ns(desired_present_time), 84 actual_present_time_ns(getNextPresentAfterTimestampAndKnownPresent(gpu_finish, desired_present_time)), 85 earliest_present_time_ns(getNextPresentAfterTimestamp(gpu_finish, desired_present_time)), 86 present_margin_ns(earliest_present_time_ns - gpu_finish), now_ns(now) 87 {} 88 89 int64_t frame_id; 90 int64_t desired_present_time_ns; 91 int64_t actual_present_time_ns; 92 int64_t earliest_present_time_ns; 93 int64_t present_margin_ns; 94 int64_t now_ns; 95 void 96 call_u_pc_info(u_pacing_compositor *upc) const 97 { 98 std::cout << "frame_id: " << frame_id << std::endl; 99 std::cout << "desired_present_time_ns: " << desired_present_time_ns << std::endl; 100 std::cout << "actual_present_time_ns: " << actual_present_time_ns << std::endl; 101 std::cout << "earliest_present_time_ns: " << earliest_present_time_ns << std::endl; 102 std::cout << "present_margin_ns: " << present_margin_ns << std::endl; 103 std::cout << "now_ns: " << now_ns << "\n" << std::endl; 104 u_pc_info(upc, frame_id, desired_present_time_ns, actual_present_time_ns, earliest_present_time_ns, 105 present_margin_ns, now_ns); 106 } 107}; 108static inline bool 109operator>(SimulatedDisplayTimingData const &lhs, SimulatedDisplayTimingData const &rhs) 110{ 111 return lhs.now_ns > rhs.now_ns; 112} 113using SimulatedDisplayTimingQueue = std::priority_queue<SimulatedDisplayTimingData, 114 std::vector<SimulatedDisplayTimingData>, 115 std::greater<SimulatedDisplayTimingData>>; 116 117//! Process all simulated timing data in the queue that should be processed by now. 118static void 119processDisplayTimingQueue(SimulatedDisplayTimingQueue &display_timing_queue, int64_t now_ns, u_pacing_compositor *upc) 120{ 121 while (!display_timing_queue.empty() && display_timing_queue.top().now_ns <= now_ns) { 122 display_timing_queue.top().call_u_pc_info(upc); 123 display_timing_queue.pop(); 124 } 125} 126//! Process all remaining simulated timing data in the queue and return the timestamp of the last one. 127static int64_t 128drainDisplayTimingQueue(SimulatedDisplayTimingQueue &display_timing_queue, int64_t now_ns, u_pacing_compositor *upc) 129{ 130 while (!display_timing_queue.empty()) { 131 now_ns = display_timing_queue.top().now_ns; 132 display_timing_queue.top().call_u_pc_info(upc); 133 display_timing_queue.pop(); 134 } 135 return now_ns; 136} 137 138static void 139doFrame(SimulatedDisplayTimingQueue &display_timing_queue, 140 u_pacing_compositor *upc, 141 MockClock &clock, 142 int64_t wake_time_ns, 143 int64_t desired_present_time_ns, 144 int64_t frame_id, 145 unanoseconds wake_delay, 146 unanoseconds begin_delay, 147 unanoseconds draw_delay, 148 unanoseconds submit_delay, 149 unanoseconds gpu_time_after_submit) 150{ 151 REQUIRE(clock.now() <= wake_time_ns); 152 // wake up (after delay) 153 clock.advance_to(wake_time_ns); 154 clock.advance(wake_delay); 155 processDisplayTimingQueue(display_timing_queue, clock.now(), upc); 156 u_pc_mark_point(upc, U_TIMING_POINT_WAKE_UP, frame_id, clock.now()); 157 158 // begin (after delay) 159 clock.advance(begin_delay); 160 processDisplayTimingQueue(display_timing_queue, clock.now(), upc); 161 u_pc_mark_point(upc, U_TIMING_POINT_BEGIN, frame_id, clock.now()); 162 163 // spend cpu time drawing 164 clock.advance(draw_delay); 165 u_pc_mark_point(upc, U_TIMING_POINT_SUBMIT_BEGIN, frame_id, clock.now()); 166 167 // spend cpu time before submit 168 clock.advance(submit_delay); 169 processDisplayTimingQueue(display_timing_queue, clock.now(), upc); 170 u_pc_mark_point(upc, U_TIMING_POINT_SUBMIT_END, frame_id, clock.now()); 171 172 // spend gpu time before present 173 clock.advance(gpu_time_after_submit); 174 auto gpu_finish = clock.now(); 175 auto next_scanout_timepoint = getNextPresentAfterTimestampAndKnownPresent(gpu_finish, desired_present_time_ns); 176 177 REQUIRE(next_scanout_timepoint >= gpu_finish); 178 179 // our wisdom arrives after scanout 180 MockClock infoClock; 181 infoClock.advance_to(next_scanout_timepoint); 182 infoClock.advance(1ms); 183 display_timing_queue.push({frame_id, desired_present_time_ns, gpu_finish, infoClock.now()}); 184} 185 186// u_pc is for the compositor, we should take way less than a frame to do our job. 187static constexpr auto wakeDelay = microseconds(20); 188 189static constexpr auto shortBeginDelay = microseconds(20); 190static constexpr auto shortDrawDelay = 150us; 191static constexpr auto shortSubmitDelay = 50us; 192static constexpr auto shortGpuTime = 1ms; 193 194 195static constexpr auto longBeginDelay = 1ms; 196static constexpr auto longDrawDelay = 2ms; 197static constexpr auto longGpuTime = 2ms; 198 199TEST_CASE("u_pacing_compositor_display_timing") 200{ 201 u_pacing_compositor *upc = nullptr; 202 MockClock clock; 203 REQUIRE(XRT_SUCCESS == 204 u_pc_display_timing_create(frame_interval_ns.count(), &U_PC_DISPLAY_TIMING_CONFIG_DEFAULT, &upc)); 205 REQUIRE(upc != nullptr); 206 207 clock.advance(1ms); 208 209 CompositorPredictions predictions; 210 u_pc_predict(upc, clock.now(), &predictions.frame_id, &predictions.wake_up_time_ns, 211 &predictions.desired_present_time_ns, &predictions.present_slop_ns, 212 &predictions.predicted_display_time_ns, &predictions.predicted_display_period_ns, 213 &predictions.min_display_period_ns); 214 basicPredictionConsistencyChecks(clock.now(), predictions); 215 216 auto frame_id = predictions.frame_id; 217 SimulatedDisplayTimingQueue queue; 218 219 220 SECTION("faster than expected") 221 { 222 // We have a 16ms period 223 // wake promptly 224 clock.advance(wakeDelay); 225 u_pc_mark_point(upc, U_TIMING_POINT_WAKE_UP, frame_id, clock.now()); 226 227 // start promptly 228 clock.advance(shortBeginDelay); 229 u_pc_mark_point(upc, U_TIMING_POINT_BEGIN, frame_id, clock.now()); 230 231 // spend cpu time drawing 232 clock.advance(shortDrawDelay); 233 u_pc_mark_point(upc, U_TIMING_POINT_SUBMIT_BEGIN, frame_id, clock.now()); 234 235 // spend a little cpu time submitting the work to the GPU 236 clock.advance(shortSubmitDelay); 237 u_pc_mark_point(upc, U_TIMING_POINT_SUBMIT_END, frame_id, clock.now()); 238 239 // spend time in gpu rendering until present 240 clock.advance(shortGpuTime); 241 auto gpu_finish = clock.now(); 242 243 auto next_scanout_timepoint = 244 getNextPresentAfterTimestampAndKnownPresent(gpu_finish, predictions.desired_present_time_ns); 245 246 // our wisdom arrives after scanout 247 MockClock infoClock; 248 infoClock.advance_to(next_scanout_timepoint); 249 infoClock.advance(1ms); 250 queue.push({frame_id, predictions.desired_present_time_ns, gpu_finish, infoClock.now()}); 251 252 // Do basically the same thing a few more frames. 253 for (int i = 0; i < 20; ++i) { 254 CompositorPredictions loopPred; 255 u_pc_predict(upc, clock.now(), &loopPred.frame_id, &loopPred.wake_up_time_ns, 256 &loopPred.desired_present_time_ns, &loopPred.present_slop_ns, 257 &loopPred.predicted_display_time_ns, &loopPred.predicted_display_period_ns, 258 &loopPred.min_display_period_ns); 259 CHECK(loopPred.frame_id > i); 260 INFO("frame id" << loopPred.frame_id); 261 INFO(clock.now()); 262 basicPredictionConsistencyChecks(clock.now(), loopPred); 263 doFrame(queue, upc, clock, loopPred.wake_up_time_ns, loopPred.desired_present_time_ns, 264 loopPred.frame_id, wakeDelay, shortBeginDelay, shortDrawDelay, shortSubmitDelay, 265 shortGpuTime); 266 } 267 // we should now get a shorter time before present to wake up. 268 CompositorPredictions newPred; 269 u_pc_predict(upc, clock.now(), &newPred.frame_id, &newPred.wake_up_time_ns, 270 &newPred.desired_present_time_ns, &newPred.present_slop_ns, 271 &newPred.predicted_display_time_ns, &newPred.predicted_display_period_ns, 272 &newPred.min_display_period_ns); 273 basicPredictionConsistencyChecks(clock.now(), newPred); 274 CHECK(unanoseconds(newPred.desired_present_time_ns - newPred.wake_up_time_ns) < 275 unanoseconds(predictions.desired_present_time_ns - predictions.wake_up_time_ns)); 276 CHECK(unanoseconds(newPred.desired_present_time_ns - newPred.wake_up_time_ns) > 277 unanoseconds(shortDrawDelay + shortSubmitDelay + shortGpuTime)); 278 } 279 280 SECTION("slower than desired") 281 { 282 // We have a 16ms period 283 // wake promptly 284 clock.advance(wakeDelay); 285 u_pc_mark_point(upc, U_TIMING_POINT_WAKE_UP, frame_id, clock.now()); 286 287 // waste time before beginframe 288 clock.advance(longBeginDelay); 289 u_pc_mark_point(upc, U_TIMING_POINT_BEGIN, frame_id, clock.now()); 290 291 // spend cpu time drawing 292 clock.advance(longDrawDelay); 293 u_pc_mark_point(upc, U_TIMING_POINT_SUBMIT_BEGIN, frame_id, clock.now()); 294 295 // spend a little cpu time submitting the work to the GPU 296 clock.advance(shortSubmitDelay); 297 u_pc_mark_point(upc, U_TIMING_POINT_SUBMIT_END, frame_id, clock.now()); 298 299 // spend time in gpu rendering until present 300 clock.advance(longGpuTime); 301 auto gpu_finish = clock.now(); 302 303 auto next_scanout_timepoint = 304 getNextPresentAfterTimestampAndKnownPresent(gpu_finish, predictions.desired_present_time_ns); 305 306 REQUIRE(next_scanout_timepoint > gpu_finish); 307 308 309 // our wisdom arrives after scanout 310 MockClock infoClock; 311 infoClock.advance_to(next_scanout_timepoint); 312 infoClock.advance(1ms); 313 queue.push({frame_id, predictions.desired_present_time_ns, gpu_finish, infoClock.now()}); 314 315 // Do basically the same thing a few more frames. 316 for (int i = 0; i < 50; ++i) { 317 CompositorPredictions loopPred; 318 u_pc_predict(upc, clock.now(), &loopPred.frame_id, &loopPred.wake_up_time_ns, 319 &loopPred.desired_present_time_ns, &loopPred.present_slop_ns, 320 &loopPred.predicted_display_time_ns, &loopPred.predicted_display_period_ns, 321 &loopPred.min_display_period_ns); 322 INFO(loopPred.frame_id); 323 INFO(clock.now()); 324 basicPredictionConsistencyChecks(clock.now(), loopPred); 325 doFrame(queue, upc, clock, loopPred.wake_up_time_ns, loopPred.desired_present_time_ns, 326 loopPred.frame_id, wakeDelay, longBeginDelay, longDrawDelay, shortSubmitDelay, 327 longGpuTime); 328 } 329 330 // we should now get a bigger time before present to wake up. 331 CompositorPredictions newPred; 332 u_pc_predict(upc, clock.now(), &newPred.frame_id, &newPred.wake_up_time_ns, 333 &newPred.desired_present_time_ns, &newPred.present_slop_ns, 334 &newPred.predicted_display_time_ns, &newPred.predicted_display_period_ns, 335 &newPred.min_display_period_ns); 336 basicPredictionConsistencyChecks(clock.now(), newPred); 337 CHECK(unanoseconds(newPred.desired_present_time_ns - newPred.wake_up_time_ns) > 338 unanoseconds(longBeginDelay + longDrawDelay + shortSubmitDelay + longGpuTime)); 339 } 340 341 u_pc_destroy(&upc); 342} 343 344TEST_CASE("u_pacing_compositor_fake") 345{ 346 MockClock clock; 347 u_pacing_compositor *upc = nullptr; 348 REQUIRE(XRT_SUCCESS == u_pc_fake_create(frame_interval_ns.count(), clock.now(), &upc)); 349 REQUIRE(upc != nullptr); 350 351 clock.advance(1ms); 352 353 SECTION("Standalone predictions") 354 { 355 CompositorPredictions predictions; 356 u_pc_predict(upc, clock.now(), &predictions.frame_id, &predictions.wake_up_time_ns, 357 &predictions.desired_present_time_ns, &predictions.present_slop_ns, 358 &predictions.predicted_display_time_ns, &predictions.predicted_display_period_ns, 359 &predictions.min_display_period_ns); 360 basicPredictionConsistencyChecks(clock.now(), predictions); 361 } 362 SECTION("Consistency in loop") 363 { 364 SimulatedDisplayTimingQueue queue; 365 SECTION("Fast") 366 { 367 368 for (int i = 0; i < 10; ++i) { 369 CompositorPredictions predictions; 370 u_pc_predict(upc, clock.now(), &predictions.frame_id, &predictions.wake_up_time_ns, 371 &predictions.desired_present_time_ns, &predictions.present_slop_ns, 372 &predictions.predicted_display_time_ns, 373 &predictions.predicted_display_period_ns, 374 &predictions.min_display_period_ns); 375 INFO(predictions.frame_id); 376 INFO(clock.now()); 377 basicPredictionConsistencyChecks(clock.now(), predictions); 378 doFrame(queue, upc, clock, predictions.wake_up_time_ns, 379 predictions.desired_present_time_ns, predictions.frame_id, wakeDelay, 380 shortBeginDelay, shortDrawDelay, shortSubmitDelay, shortGpuTime); 381 } 382 drainDisplayTimingQueue(queue, clock.now(), upc); 383 } 384 SECTION("Slow") 385 { 386 for (int i = 0; i < 10; ++i) { 387 CompositorPredictions predictions; 388 u_pc_predict(upc, clock.now(), &predictions.frame_id, &predictions.wake_up_time_ns, 389 &predictions.desired_present_time_ns, &predictions.present_slop_ns, 390 &predictions.predicted_display_time_ns, 391 &predictions.predicted_display_period_ns, 392 &predictions.min_display_period_ns); 393 INFO(predictions.frame_id); 394 INFO(clock.now()); 395 basicPredictionConsistencyChecks(clock.now(), predictions); 396 doFrame(queue, upc, clock, predictions.wake_up_time_ns, 397 predictions.desired_present_time_ns, predictions.frame_id, wakeDelay, 398 longBeginDelay, longDrawDelay, shortSubmitDelay, longGpuTime); 399 } 400 drainDisplayTimingQueue(queue, clock.now(), upc); 401 } 402 } 403 u_pc_destroy(&upc); 404}