@@ -235,8 +235,8 @@ void Pacer::vsyncEmulator() {
235235 period.initFromHz (hzNum, hzDen, baseQpc);
236236
237237 if (curDeadlineQpc > 0 ) {
238- Utils::Logf (" resync to hardware vsync, frame: %d, drift: %lld ticks (%.3f ms), phase: %d\n " ,
239- frames, period.nextDeadlineQpc - curDeadlineQpc, QpcToMs (period.nextDeadlineQpc - curDeadlineQpc), phase);
238+ FQLog (" resync to hardware vsync, frame: %d, drift: %lld ticks (%.3f ms), phase: %d\n " ,
239+ frames, period.nextDeadlineQpc - curDeadlineQpc, QpcToMs (period.nextDeadlineQpc - curDeadlineQpc), phase);
240240 }
241241
242242 curDeadlineQpc = baseQpc;
@@ -292,7 +292,6 @@ void Pacer::vsyncHardware() {
292292 }
293293 lastT0 = now;
294294 }
295-
296295}
297296
298297void Pacer::backPacer () {
@@ -405,37 +404,49 @@ void Pacer::handleVsync(int64_t nextDeadlineQpc) {
405404 // Place the first frame on the render queue
406405 AVFrame *frame = std::move (m_PacingQueue.front ());
407406 m_PacingQueue.pop_front ();
408- lock.unlock ();
409407
410- // try to time this as close to deadline as possible
411- remainingQpc = nextDeadlineQpc - QpcNow ();
412- if (remainingQpc > 150 ) {
413- SleepUntilQpc ( nextDeadlineQpc) ;
408+ // Pass along the target timestamp with the frame metadata
409+ if (frame-> opaque_ref ) {
410+ auto *data = reinterpret_cast <MLFrameData *>(frame-> opaque_ref -> data );
411+ data-> presentTargetQpc = nextDeadlineQpc;
414412 }
415413
414+ lock.unlock ();
415+
416+ // Render frame immediately, it will wait to be presented at presentTargetQpc
416417 enqueueFrameForRenderingAndUnlock (frame);
417418}
418419
420+ void Pacer::enqueueFrameForRenderingAndUnlock (AVFrame *frame) {
421+ {
422+ std::scoped_lock<std::mutex> lock (m_FrameQueueLock);
423+ dropFrameForEnqueue (m_RenderQueue, PLOT_DROPPED_PACER_FRONT );
424+ m_RenderQueue.push_back (frame);
425+ }
426+
427+ // notify render loop of new frame
428+ m_RenderQueueNotEmpty.notify_one ();
429+ }
430+
419431// Main thread
420432
421433void Pacer::waitForFrame () {
422434 // Wait for the renderer to be ready for the next frame
423- LARGE_INTEGER t0, t1;
424- QueryPerformanceCounter (&t0);
435+ int64_t t0 = QpcNow ();
425436
426437 HANDLE flw = m_DeviceResources->GetFrameLatencyWaitable ();
427438 WaitForSingleObjectEx (flw, 1000 , true );
428439
429- QueryPerformanceCounter (&t1 );
430- FQLog (" waitForFrame part 1 waited %.3f ms\n " , QpcToMs (t1. QuadPart - t0. QuadPart ));
440+ int64_t t1 = QpcNow ( );
441+ FQLog (" waitForFrame(): FrameLatencyWaitable waited %.3f ms\n " , QpcToMs (t1 - t0));
431442
432443 std::unique_lock<std::mutex> lock (m_FrameQueueLock);
433444 m_RenderQueueNotEmpty.wait (lock, [this ] {
434445 return stopping () || !m_RenderQueue.empty ();
435446 });
436447
437448 int64_t t2 = QpcNow ();
438- FQLog (" waitForFrame part 2 waited %.3f ms\n " , QpcToMs (t2 - t1. QuadPart ));
449+ FQLog (" waitForFrame(): m_RenderQueueNotEmpty waited %.3f ms\n " , QpcToMs (t2 - t1));
439450}
440451
441452bool Pacer::renderOnMainThread (std::shared_ptr<VideoRenderer> &sceneRenderer) {
@@ -453,20 +464,37 @@ bool Pacer::renderOnMainThread(std::shared_ptr<VideoRenderer> &sceneRenderer) {
453464 return false ; // no frame, don't Present()
454465 }
455466
467+ // Extract the target present time for this frame
468+ if (frame->opaque_ref ) {
469+ auto *data = reinterpret_cast <MLFrameData *>(frame->opaque_ref ->data );
470+ m_PresentTargetQpc = data ? data->presentTargetQpc : 0 ;
471+ } else {
472+ m_PresentTargetQpc = 0 ;
473+ }
474+
456475 frontPacer (sceneRenderer, frame);
457476 return true ; // ok to Present()
458477}
459478
460- void Pacer::enqueueFrameForRenderingAndUnlock (AVFrame *frame) {
461- {
462- std::scoped_lock<std::mutex> lock (m_FrameQueueLock);
463- dropFrameForEnqueue (m_RenderQueue, PLOT_DROPPED_PACER_FRONT );
464- m_RenderQueue.push_back (frame);
479+ void Pacer::waitUntilPresentTarget () {
480+ const int64_t target = m_PresentTargetQpc;
481+ if (target <= 0 ) {
482+ return ;
465483 }
466484
467- // notify render loop of new frame
468- FQLog (" enqueueFrame notifying m_RenderQueueNotEmpty\n " );
469- m_RenderQueueNotEmpty.notify_one ();
485+ const int64_t now = QpcNow ();
486+ if (target <= now) {
487+ FQLog (" waitUntilPresentTarget(): target was %.3f ms too late\n " , QpcToMs (now - target));
488+ return ;
489+ }
490+
491+ FQLog (" waitUntilPresentTarget(): waiting for %.3f ms\n " , QpcToMs (target - now));
492+
493+ SleepUntilQpc (target);
494+
495+ // Measure how well we timed things
496+ // const double skewMs = QpcToMs(QpcNow() - target);
497+ // ImGuiPlots::instance().observeFloat(PLOT_PRESENT_ACCURACY, (float)skewMs);
470498}
471499
472500void Pacer::frontPacer (std::shared_ptr<VideoRenderer> &sceneRenderer, AVFrame *frame) {
0 commit comments