Vuo  2.0.0
VuoDisplayRefresh.c
Go to the documentation of this file.
1 
10 #include "VuoDisplayRefresh.h"
11 #include <CoreVideo/CoreVideo.h>
12 
13 #ifdef VUO_COMPILER
15  "title" : "VuoDisplayRefresh",
16  "dependencies" : [
17  "ApplicationServices.framework",
18  "CoreVideo.framework"
19  ]
20  });
21 #endif
22 
26 typedef struct
27 {
28  void (*requestedFrameTrigger)(VuoReal);
29  void (*requestedFrameTriggerWithContext)(VuoReal, void *context);
31 
32  bool firstRequest;
33  int64_t initialTime;
35 
36  CVDisplayLinkRef displayLink;
38  dispatch_semaphore_t displayLinkCanceledAndCompleted;
40 
44 static CVReturn VuoDisplayRefresh_displayLinkCallback(CVDisplayLinkRef displayLink,
45  const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime,
46  CVOptionFlags flagsIn, CVOptionFlags *flagsOut,
47  void *ctx)
48 {
49  int64_t plannedFrameTime = inOutputTime->videoTime;
50 
52  if (displayRefresh->firstRequest)
53  {
54  displayRefresh->initialTime = plannedFrameTime;
55  displayRefresh->firstRequest = false;
56  }
57 
58  if (displayRefresh->displayLinkCancelRequested)
59  {
60  CVDisplayLinkStop(displayRefresh->displayLink);
61  dispatch_semaphore_signal(displayRefresh->displayLinkCanceledAndCompleted);
62  return kCVReturnSuccess;
63  }
64 
65  VuoReal timestamp = (double)(plannedFrameTime - displayRefresh->initialTime)/(double)inOutputTime->videoTimeScale;
66 
67  if (displayRefresh->requestedFrameTrigger)
68  displayRefresh->requestedFrameTrigger(timestamp);
69 
70  if (displayRefresh->requestedFrameTriggerWithContext)
71  displayRefresh->requestedFrameTriggerWithContext(timestamp, displayRefresh->triggerContext);
72 
73  return kCVReturnSuccess;
74 }
75 
84 {
86  VuoRegister(displayRefresh, free);
87 
88  displayRefresh->firstRequest = true;
89  // displayRefresh->initialTime is initialized by VuoDisplayRefresh_displayLinkCallback().
90  displayRefresh->frameCount = 0;
91 
92  displayRefresh->triggerContext = context;
93 
94  return (VuoDisplayRefresh)displayRefresh;
95 }
96 
105 (
107  void (*requestedFrameTrigger)(VuoReal),
108  void (*requestedFrameTriggerWithContext)(VuoReal, void *context)
109 )
110 {
112 
113  displayRefresh->requestedFrameTrigger = requestedFrameTrigger;
114  displayRefresh->requestedFrameTriggerWithContext = requestedFrameTriggerWithContext;
115 
116  if (CVDisplayLinkCreateWithCGDisplay(kCGDirectMainDisplay, &displayRefresh->displayLink) == kCVReturnSuccess)
117  {
118  displayRefresh->displayLinkCancelRequested = false;
119  displayRefresh->displayLinkCanceledAndCompleted = dispatch_semaphore_create(0);
120  CVDisplayLinkSetOutputCallback(displayRefresh->displayLink, VuoDisplayRefresh_displayLinkCallback, displayRefresh);
121  CVDisplayLinkStart(displayRefresh->displayLink);
122 
123  CVTime nominal = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayRefresh->displayLink);
124  VDebugLog("Refresh: %g Hz (%d/%lld)", (double)nominal.timeScale/nominal.timeValue, nominal.timeScale, nominal.timeValue);
125 
126  CVTime latency = CVDisplayLinkGetOutputVideoLatency(displayRefresh->displayLink);
127  if (latency.timeScale)
128  {
129  double latencyFrames = ((double)latency.timeValue/latency.timeScale)/((double)nominal.timeValue/nominal.timeScale);
130  VDebugLog("Latency: %g frame%s (%d/%lld)", latencyFrames, fabs(latencyFrames-1)<0.00001 ? "" : "s", latency.timeScale, latency.timeValue);
131  }
132  else
133  VDebugLog("Latency: unknown");
134  }
135  else
136  {
137  VUserLog("Failed to create CVDisplayLink.");
138  displayRefresh->displayLink = NULL;
139  }
140 }
141 
148 {
150  if (displayRefresh->displayLink)
151  {
152  displayRefresh->displayLinkCancelRequested = true;
153 
154  // Wait for the last sendoff to complete.
155  dispatch_semaphore_wait(displayRefresh->displayLinkCanceledAndCompleted, DISPATCH_TIME_FOREVER);
156  }
157 }