2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
22 #include "utils/StdString.h"
23 #include "VideoReferenceClock.h"
24 #include "utils/MathUtils.h"
25 #include "utils/log.h"
26 #include "utils/TimeUtils.h"
27 #include "utils/StringUtils.h"
28 #include "threads/SingleLock.h"
30 #if defined(HAS_GLX) && defined(HAS_XRANDR)
32 #include <X11/extensions/Xrandr.h>
33 #include "windowing/WindowingFactory.h"
34 #define NVSETTINGSCMD "nvidia-settings -nt -q RefreshRate3"
35 #elif defined(TARGET_DARWIN_OSX)
36 #include <QuartzCore/CVDisplayLink.h>
37 #include "osx/CocoaInterface.h"
38 #elif defined(TARGET_DARWIN_IOS)
39 #include "windowing/WindowingFactory.h"
40 #elif defined(TARGET_WINDOWS) && defined(HAS_DX)
41 #pragma comment (lib,"d3d9.lib")
42 #if (D3DX_SDK_VERSION >= 42) //aug 2009 sdk and up there is no dxerr9 anymore
44 #pragma comment (lib,"DxErr.lib")
47 #define DXGetErrorString(hr) DXGetErrorString9(hr)
48 #define DXGetErrorDescription(hr) DXGetErrorDescription9(hr)
49 #pragma comment (lib,"Dxerr9.lib")
51 #include "windowing/WindowingFactory.h"
52 #include "settings/AdvancedSettings.h"
57 #if defined(TARGET_WINDOWS) && defined(HAS_DX)
59 void CD3DCallback::Reset()
65 void CD3DCallback::OnDestroyDevice()
67 CSingleLock lock(m_critsection);
68 m_devicevalid = false;
72 m_releaseevent.Wait();
77 void CD3DCallback::OnCreateDevice()
79 CSingleLock lock(m_critsection);
84 void CD3DCallback::Aquire()
86 CSingleLock lock(m_critsection);
96 void CD3DCallback::Release()
98 CSingleLock lock(m_critsection);
100 m_releaseevent.Set();
103 bool CD3DCallback::IsValid()
105 return m_devicevalid;
110 CVideoReferenceClock::CVideoReferenceClock() : CThread("VideoReferenceClock")
112 m_SystemFrequency = CurrentHostFrequency();
115 m_TotalMissedVblanks = 0;
121 m_CurrTimeFract = 0.0;
122 m_LastRefreshTime = 0;
125 m_PrevRefreshRate = 0;
127 m_RefreshChanged = 0;
130 #if defined(HAS_GLX) && defined(HAS_XRANDR)
131 m_glXWaitVideoSyncSGI = NULL;
132 m_glXGetVideoSyncSGI = NULL;
140 m_UseNvSettings = true;
145 void CVideoReferenceClock::Process()
147 bool SetupSuccess = false;
150 #if defined(TARGET_WINDOWS) && defined(HAS_DX)
152 m_D3dCallback.Reset();
153 g_Windowing.Register(&m_D3dCallback);
158 //set up the vblank clock
159 #if defined(HAS_GLX) && defined(HAS_XRANDR)
160 SetupSuccess = SetupGLX();
161 #elif defined(TARGET_WINDOWS) && defined(HAS_DX)
162 SetupSuccess = SetupD3D();
163 #elif defined(TARGET_DARWIN)
164 SetupSuccess = SetupCocoa();
165 #elif defined(HAS_GLX)
166 CLog::Log(LOGDEBUG, "CVideoReferenceClock: compiled without RandR support");
167 #elif defined(TARGET_WINDOWS)
168 CLog::Log(LOGDEBUG, "CVideoReferenceClock: only available on directx build");
170 CLog::Log(LOGDEBUG, "CVideoReferenceClock: no implementation available");
173 CSingleLock SingleLock(m_CritSection);
174 Now = CurrentHostCounter();
175 m_CurrTime = Now + m_ClockOffset; //add the clock offset from the previous time we stopped
176 m_LastIntTime = m_CurrTime;
177 m_CurrTimeFract = 0.0;
179 m_TotalMissedVblanks = 0;
181 m_RefreshChanged = 0;
186 m_UseVblank = true; //tell other threads we're using vblank as clock
187 m_VblankTime = Now; //initialize the timestamp of the last vblank
191 #if defined(HAS_GLX) && defined(HAS_XRANDR)
193 #elif defined(TARGET_WINDOWS) && defined(HAS_DX)
195 #elif defined(TARGET_DARWIN)
203 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Setup failed, falling back to CurrentHostCounter()");
207 m_UseVblank = false; //we're back to using the systemclock
208 Now = CurrentHostCounter(); //set the clockoffset between the vblank clock and systemclock
209 m_ClockOffset = m_CurrTime - Now;
212 //clean up the vblank clock
213 #if defined(HAS_GLX) && defined(HAS_XRANDR)
215 #elif defined(TARGET_WINDOWS) && defined(HAS_DX)
217 #elif defined(TARGET_DARWIN)
220 if (!SetupSuccess) break;
223 #if defined(TARGET_WINDOWS) && defined(HAS_DX)
224 g_Windowing.Unregister(&m_D3dCallback);
228 bool CVideoReferenceClock::WaitStarted(int MSecs)
230 //not waiting here can cause issues with alsa
231 return m_Started.WaitMSec(MSecs);
234 #if defined(HAS_GLX) && defined(HAS_XRANDR)
235 bool CVideoReferenceClock::SetupGLX()
237 int singleBufferAttributes[] = {
245 int ReturnV, SwaMask;
246 unsigned int GlxTest;
247 XSetWindowAttributes Swa;
255 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Setting up GLX");
259 m_Dpy = XOpenDisplay(NULL);
262 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Unable to open display");
267 if (!glXQueryExtension(m_Dpy, NULL, NULL))
269 CLog::Log(LOGDEBUG, "CVideoReferenceClock: X server does not support GLX");
273 bool ExtensionFound = false;
274 istringstream Extensions(glXQueryExtensionsString(m_Dpy, DefaultScreen(m_Dpy)));
277 while (!ExtensionFound)
279 Extensions >> ExtensionStr;
280 if (Extensions.fail())
283 if (ExtensionStr == "GLX_SGI_video_sync")
284 ExtensionFound = true;
289 CLog::Log(LOGDEBUG, "CVideoReferenceClock: X server does not support GLX_SGI_video_sync");
293 CStdString Vendor = g_Windowing.GetRenderVendor();
294 if (StringUtils::StartsWithNoCase(Vendor, "ati"))
296 CLog::Log(LOGDEBUG, "CVideoReferenceClock: GL_VENDOR: %s, using ati workaround", Vendor.c_str());
300 m_vInfo = glXChooseVisual(m_Dpy, DefaultScreen(m_Dpy), singleBufferAttributes);
303 CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXChooseVisual returned NULL");
309 Swa.border_pixel = 0;
310 Swa.event_mask = StructureNotifyMask;
311 Swa.colormap = XCreateColormap(m_Dpy, RootWindow(m_Dpy, m_vInfo->screen), m_vInfo->visual, AllocNone );
312 SwaMask = CWBorderPixel | CWColormap | CWEventMask;
314 m_Window = XCreateWindow(m_Dpy, RootWindow(m_Dpy, m_vInfo->screen), 0, 0, 256, 256, 0,
315 m_vInfo->depth, InputOutput, m_vInfo->visual, SwaMask, &Swa);
319 m_pixmap = XCreatePixmap(m_Dpy, DefaultRootWindow(m_Dpy), 256, 256, m_vInfo->depth);
322 CLog::Log(LOGDEBUG, "CVideoReferenceClock: unable to create pixmap");
325 m_glPixmap = glXCreateGLXPixmap(m_Dpy, m_vInfo, m_pixmap);
328 m_Context = glXCreateContext(m_Dpy, m_vInfo, NULL, True);
331 CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXCreateContext returned NULL");
336 ReturnV = glXMakeCurrent(m_Dpy, m_Window, m_Context);
338 ReturnV = glXMakeCurrent(m_Dpy, m_glPixmap, m_Context);
342 CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXMakeCurrent returned %i", ReturnV);
348 m_glXWaitVideoSyncSGI = (int (*)(int, int, unsigned int*))glXGetProcAddress((const GLubyte*)"glXWaitVideoSyncSGI");
349 if (!m_glXWaitVideoSyncSGI)
351 CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXWaitVideoSyncSGI not found");
355 ReturnV = m_glXWaitVideoSyncSGI(2, 0, &GlxTest);
358 CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXWaitVideoSyncSGI returned %i", ReturnV);
363 m_glXGetVideoSyncSGI = (int (*)(unsigned int*))glXGetProcAddress((const GLubyte*)"glXGetVideoSyncSGI");
364 if (!m_glXGetVideoSyncSGI)
366 CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXGetVideoSyncSGI not found");
370 ReturnV = m_glXGetVideoSyncSGI(&GlxTest);
373 CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXGetVideoSyncSGI returned %i", ReturnV);
377 XRRSizes(m_Dpy, m_vInfo->screen, &ReturnV);
380 CLog::Log(LOGDEBUG, "CVideoReferenceClock: RandR not supported");
384 //set up receiving of RandR events, we'll get one when the refreshrate changes
385 XRRQueryExtension(m_Dpy, &m_RREventBase, &ReturnV);
386 XRRSelectInput(m_Dpy, RootWindow(m_Dpy, m_vInfo->screen), RRScreenChangeNotifyMask);
388 UpdateRefreshrate(true); //forced refreshrate update
394 bool CVideoReferenceClock::ParseNvSettings(int& RefreshRate)
400 struct lconv *Locale = localeconv();
405 const char* VendorPtr = (const char*)glGetString(GL_VENDOR);
408 CLog::Log(LOGDEBUG, "CVideoReferenceClock: glGetString(GL_VENDOR) returned NULL, not using nvidia-settings");
412 CStdString Vendor = VendorPtr;
413 StringUtils::ToLower(Vendor);
414 if (Vendor.find("nvidia") == std::string::npos)
416 CLog::Log(LOGDEBUG, "CVideoReferenceClock: GL_VENDOR:%s, not using nvidia-settings", Vendor.c_str());
420 NvSettings = popen(NVSETTINGSCMD, "r");
423 CLog::Log(LOGDEBUG, "CVideoReferenceClock: %s: %s", NVSETTINGSCMD, strerror(errno));
427 fd = fileno(NvSettings);
430 CLog::Log(LOGDEBUG, "CVideoReferenceClock: unable to get nvidia-settings file descriptor: %s", strerror(errno));
435 now = CurrentHostCounter();
437 while (CurrentHostCounter() - now < CurrentHostFrequency() * 5)
442 struct timeval timeout = {1, 0};
443 ReturnV = select(fd + 1, &set, NULL, NULL, &timeout);
446 CLog::Log(LOGDEBUG, "CVideoReferenceClock: select failed on %s: %s", NVSETTINGSCMD, strerror(errno));
450 else if (FD_ISSET(fd, &set))
452 ReturnV = read(fd, Buff + buffpos, (int)sizeof(Buff) - buffpos);
455 CLog::Log(LOGDEBUG, "CVideoReferenceClock: read failed on %s: %s", NVSETTINGSCMD, strerror(errno));
459 else if (ReturnV > 0)
462 if (buffpos >= (int)sizeof(Buff) - 1)
474 CLog::Log(LOGDEBUG, "CVideoReferenceClock: %s produced no output", NVSETTINGSCMD);
475 //calling pclose() here might hang
476 //what should be done instead is fork, call nvidia-settings
477 //then kill the process if it hangs
480 else if (buffpos > (int)sizeof(Buff) - 1)
482 buffpos = sizeof(Buff) - 1;
487 CLog::Log(LOGDEBUG, "CVideoReferenceClock: output of %s: %s", NVSETTINGSCMD, Buff);
489 if (!strchr(Buff, '\n'))
491 CLog::Log(LOGDEBUG, "CVideoReferenceClock: %s incomplete output (no newline)", NVSETTINGSCMD);
495 for (int i = 0; i < buffpos; i++)
497 //workaround for locale mismatch
498 if (Buff[i] == '.' || Buff[i] == ',')
499 Buff[i] = *Locale->decimal_point;
502 ReturnV = sscanf(Buff, "%lf", &fRefreshRate);
503 if (ReturnV != 1 || fRefreshRate <= 0.0)
505 CLog::Log(LOGDEBUG, "CVideoReferenceClock: can't make sense of that");
509 RefreshRate = MathUtils::round_int(fRefreshRate);
510 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Detected refreshrate by nvidia-settings: %f hertz, rounding to %i hertz",
511 fRefreshRate, RefreshRate);
516 int CVideoReferenceClock::GetRandRRate()
519 XRRScreenConfiguration *CurrInfo;
521 CurrInfo = XRRGetScreenInfo(m_Dpy, RootWindow(m_Dpy, m_vInfo->screen));
522 RefreshRate = XRRConfigCurrentRate(CurrInfo);
523 XRRFreeScreenConfigInfo(CurrInfo);
528 void CVideoReferenceClock::CleanupGLX()
530 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Cleaning up GLX");
539 glXMakeCurrent(m_Dpy, None, NULL);
540 glXDestroyContext(m_Dpy, m_Context);
545 XDestroyWindow(m_Dpy, m_Window);
550 glXDestroyPixmap(m_Dpy, m_glPixmap);
555 XFreePixmap(m_Dpy, m_pixmap);
559 //ati saves the Display* in their libGL, if we close it here, we crash
560 if (m_Dpy && !m_bIsATI)
562 XCloseDisplay(m_Dpy);
567 void CVideoReferenceClock::RunGLX()
569 unsigned int PrevVblankCount;
570 unsigned int VblankCount;
572 bool IsReset = false;
575 CSingleLock SingleLock(m_CritSection);
578 //get the current vblank counter
579 m_glXGetVideoSyncSGI(&VblankCount);
580 PrevVblankCount = VblankCount;
582 uint64_t lastVblankTime = CurrentHostCounter();
583 int sleepTime, correction;
588 //wait for the next vblank
591 ReturnV = m_glXWaitVideoSyncSGI(2, (VblankCount + 1) % 2, &VblankCount);
592 m_glXGetVideoSyncSGI(&VblankCount); //the vblank count returned by glXWaitVideoSyncSGI is not always correct
596 // calculate sleep time in micro secs
597 // we start with 50% of interval
598 sleepTime = 500000LL / m_RefreshRate;
600 // correct sleepTime by time used for processing since last vblank
601 correction = (CurrentHostCounter() - lastVblankTime) * 1000000LL / m_SystemFrequency;
602 sleepTime -= correction;
604 // correct sleep time by integral term
605 // consider 10 cycles as desired
606 sleepTime += integral;
608 // clamp sleepTime to a min value of 30% of interval
609 // integral is already clamped to a max value
610 sleepTime = std::max(int(300000LL/m_RefreshRate), sleepTime);
612 unsigned int iterations = 0;
613 while (VblankCount == PrevVblankCount && !m_bStop)
616 m_glXGetVideoSyncSGI(&VblankCount);
617 sleepTime = sleepTime > 200 ? sleepTime/2 : 100;
622 else if (iterations < 10)
625 // clamp integral to an absolute value of 20% of interval
626 if (integral > 200000LL/m_RefreshRate)
627 integral = 200000LL/m_RefreshRate;
628 else if (integral < -200000LL/m_RefreshRate)
629 integral = -200000LL/m_RefreshRate;
631 lastVblankTime = CurrentHostCounter();
635 Now = CurrentHostCounter(); //get the timestamp of this vblank
639 CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXWaitVideoSyncSGI returned %i", ReturnV);
643 if (VblankCount > PrevVblankCount)
645 //update the vblank timestamp, update the clock and send a signal that we got a vblank
648 UpdateClock((int)(VblankCount - PrevVblankCount), true);
656 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Vblank counter has reset");
660 //only try reattaching once
664 //because of a bug in the nvidia driver, glXWaitVideoSyncSGI breaks when the vblank counter resets
665 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Detaching glX context");
666 ReturnV = glXMakeCurrent(m_Dpy, None, NULL);
669 CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXMakeCurrent returned %i", ReturnV);
673 //sleep here so we don't busy spin when this constantly happens, for example when the display went to sleep
676 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Attaching glX context");
678 ReturnV = glXMakeCurrent(m_Dpy, m_Window, m_Context);
680 ReturnV = glXMakeCurrent(m_Dpy, m_glPixmap, m_Context);
684 CLog::Log(LOGDEBUG, "CVideoReferenceClock: glXMakeCurrent returned %i", ReturnV);
688 m_glXGetVideoSyncSGI(&VblankCount);
692 PrevVblankCount = VblankCount;
696 #elif defined(TARGET_WINDOWS) && defined(HAS_DX)
698 void CVideoReferenceClock::RunD3D()
700 D3DRASTER_STATUS RasterStatus;
702 int64_t LastVBlankTime;
703 unsigned int LastLine;
708 CSingleLock SingleLock(m_CritSection);
711 //get the scanline we're currently at
712 m_D3dDev->GetRasterStatus(0, &RasterStatus);
713 if (RasterStatus.InVBlank) LastLine = 0;
714 else LastLine = RasterStatus.ScanLine;
716 //init the vblanktime
717 Now = CurrentHostCounter();
718 LastVBlankTime = Now;
720 while(!m_bStop && m_D3dCallback.IsValid())
722 //get the scanline we're currently at
723 ReturnV = m_D3dDev->GetRasterStatus(0, &RasterStatus);
724 if (ReturnV != D3D_OK)
726 CLog::Log(LOGDEBUG, "CVideoReferenceClock: GetRasterStatus returned returned %s: %s",
727 DXGetErrorString(ReturnV), DXGetErrorDescription(ReturnV));
731 //if InVBlank is set, or the current scanline is lower than the previous scanline, a vblank happened
732 if ((RasterStatus.InVBlank && LastLine > 0) || (RasterStatus.ScanLine < LastLine))
734 //calculate how many vblanks happened
735 Now = CurrentHostCounter();
736 VBlankTime = (double)(Now - LastVBlankTime) / (double)m_SystemFrequency;
737 NrVBlanks = MathUtils::round_int(VBlankTime * (double)m_RefreshRate);
739 //update the vblank timestamp, update the clock and send a signal that we got a vblank
742 UpdateClock(NrVBlanks, true);
746 if (UpdateRefreshrate())
748 //we have to measure the refreshrate again
749 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Displaymode changed");
753 //save the timestamp of this vblank so we can calculate how many vblanks happened next time
754 LastVBlankTime = Now;
756 //because we had a vblank, sleep until half the refreshrate period
757 Now = CurrentHostCounter();
758 int SleepTime = (int)((LastVBlankTime + (m_SystemFrequency / m_RefreshRate / 2) - Now) * 1000 / m_SystemFrequency);
759 if (SleepTime > 100) SleepTime = 100; //failsafe
760 if (SleepTime > 0) ::Sleep(SleepTime);
767 if (RasterStatus.InVBlank) LastLine = 0;
768 else LastLine = RasterStatus.ScanLine;
772 //how many times we measure the refreshrate
774 //how long to measure in milliseconds
775 #define MEASURETIME 250
777 bool CVideoReferenceClock::SetupD3D()
781 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Setting up Direct3d");
783 m_D3dCallback.Aquire();
786 m_D3dDev = g_Windowing.Get3DDevice();
788 //we need a high priority thread to get accurate timing
789 if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL))
790 CLog::Log(LOGDEBUG, "CVideoReferenceClock: SetThreadPriority failed");
793 ReturnV = m_D3dDev->GetDeviceCaps(&DevCaps);
794 if (ReturnV != D3D_OK)
796 CLog::Log(LOGDEBUG, "CVideoReferenceClock: GetDeviceCaps returned %s: %s",
797 DXGetErrorString(ReturnV), DXGetErrorDescription(ReturnV));
801 if ((DevCaps.Caps & D3DCAPS_READ_SCANLINE) != D3DCAPS_READ_SCANLINE)
803 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Hardware does not support GetRasterStatus");
807 D3DRASTER_STATUS RasterStatus;
808 ReturnV = m_D3dDev->GetRasterStatus(0, &RasterStatus);
809 if (ReturnV != D3D_OK)
811 CLog::Log(LOGDEBUG, "CVideoReferenceClock: GetRasterStatus returned returned %s: %s",
812 DXGetErrorString(ReturnV), DXGetErrorDescription(ReturnV));
816 D3DDISPLAYMODE DisplayMode;
817 ReturnV = m_D3dDev->GetDisplayMode(0, &DisplayMode);
818 if (ReturnV != D3D_OK)
820 CLog::Log(LOGDEBUG, "CVideoReferenceClock: GetDisplayMode returned returned %s: %s",
821 DXGetErrorString(ReturnV), DXGetErrorDescription(ReturnV));
825 //forced update of windows refreshrate
826 UpdateRefreshrate(true);
828 if (g_advancedSettings.m_measureRefreshrate)
830 //measure the refreshrate a couple times
831 list<double> Measures;
832 for (int i = 0; i < NRMEASURES; i++)
833 Measures.push_back(MeasureRefreshrate(MEASURETIME));
835 //build up a string of measured rates
837 for (list<double>::iterator it = Measures.begin(); it != Measures.end(); it++)
838 StrRates += StringUtils::Format("%.2f ", *it);
840 //get the top half of the measured rates
842 double RefreshRate = 0.0;
843 int NrMeasurements = 0;
844 while (NrMeasurements < NRMEASURES / 2 && !Measures.empty())
846 if (Measures.back() > 0.0)
848 RefreshRate += Measures.back();
854 if (NrMeasurements < NRMEASURES / 2)
856 CLog::Log(LOGDEBUG, "CVideoReferenceClock: refreshrate measurements: %s, unable to get a good measurement",
857 StrRates.c_str(), m_RefreshRate);
861 RefreshRate /= NrMeasurements;
862 m_RefreshRate = MathUtils::round_int(RefreshRate);
864 CLog::Log(LOGDEBUG, "CVideoReferenceClock: refreshrate measurements: %s, assuming %i hertz", StrRates.c_str(), m_RefreshRate);
868 m_RefreshRate = m_PrevRefreshRate;
869 if (m_RefreshRate == 23 || m_RefreshRate == 29 || m_RefreshRate == 59)
875 CLog::Log(LOGDEBUG, "CVideoReferenceClock: display is interlaced");
878 CLog::Log(LOGDEBUG, "CVideoReferenceClock: detected refreshrate: %i hertz, assuming %i hertz", m_PrevRefreshRate, (int)m_RefreshRate);
886 double CVideoReferenceClock::MeasureRefreshrate(int MSecs)
888 D3DRASTER_STATUS RasterStatus;
893 int64_t MeasureCount;
894 unsigned int LastLine;
897 Now = CurrentHostCounter();
898 Target = Now + (m_SystemFrequency * MSecs / 1000);
903 //start measuring vblanks
907 ReturnV = m_D3dDev->GetRasterStatus(0, &RasterStatus);
908 Now = CurrentHostCounter();
909 if (ReturnV != D3D_OK)
911 CLog::Log(LOGDEBUG, "CVideoReferenceClock: GetRasterStatus returned returned %s: %s",
912 DXGetErrorString(ReturnV), DXGetErrorDescription(ReturnV));
916 if ((RasterStatus.InVBlank && LastLine != 0) || (!RasterStatus.InVBlank && RasterStatus.ScanLine < LastLine))
918 if (Prev != -1) //need two for a measurement
920 AvgInterval += Now - Prev; //save how long this vblank lasted
923 Prev = Now; //save this time for the next measurement
926 //save the current scanline
927 if (RasterStatus.InVBlank)
930 LastLine = RasterStatus.ScanLine;
935 if (MeasureCount < 1)
937 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Didn't measure any vblanks");
941 double fRefreshRate = 1.0 / ((double)AvgInterval / (double)MeasureCount / (double)m_SystemFrequency);
946 void CVideoReferenceClock::CleanupD3D()
948 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Cleaning up Direct3d");
949 m_D3dCallback.Release();
952 #elif defined(TARGET_DARWIN)
953 #if defined(TARGET_DARWIN_OSX)
954 // Called by the Core Video Display Link whenever it's appropriate to render a frame.
955 static CVReturn DisplayLinkCallBack(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow, const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
959 if (inNow->videoRefreshPeriod > 0)
960 fps = (double)inOutputTime->videoTimeScale / (double)inOutputTime->videoRefreshPeriod;
962 // Create an autorelease pool (necessary to call into non-Obj-C code from Obj-C code)
963 void* pool = Cocoa_Create_AutoReleasePool();
965 CVideoReferenceClock *VideoReferenceClock = reinterpret_cast<CVideoReferenceClock*>(displayLinkContext);
966 VideoReferenceClock->VblankHandler(inNow->hostTime, fps);
968 // Destroy the autorelease pool
969 Cocoa_Destroy_AutoReleasePool(pool);
971 return kCVReturnSuccess;
974 bool CVideoReferenceClock::SetupCocoa()
976 CLog::Log(LOGDEBUG, "CVideoReferenceClock: setting up Cocoa");
978 //init the vblank timestamp
979 m_LastVBlankTime = CurrentHostCounter();
981 m_RefreshRate = 60; //init the refreshrate so we don't get any division by 0 errors
983 #if defined(TARGET_DARWIN_IOS)
985 g_Windowing.InitDisplayLink();
988 if (!Cocoa_CVDisplayLinkCreate((void*)DisplayLinkCallBack, reinterpret_cast<void*>(this)))
990 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Cocoa_CVDisplayLinkCreate failed");
996 UpdateRefreshrate(true);
1001 void CVideoReferenceClock::RunCocoa()
1003 //because cocoa has a vblank callback, we just keep sleeping until we're asked to stop the thread
1010 void CVideoReferenceClock::CleanupCocoa()
1012 CLog::Log(LOGDEBUG, "CVideoReferenceClock: cleaning up Cocoa");
1013 #if defined(TARGET_DARWIN_IOS)
1014 g_Windowing.DeinitDisplayLink();
1016 Cocoa_CVDisplayLinkRelease();
1020 void CVideoReferenceClock::VblankHandler(int64_t nowtime, double fps)
1024 int RefreshRate = MathUtils::round_int(fps);
1026 CSingleLock SingleLock(m_CritSection);
1028 if (RefreshRate != m_RefreshRate)
1030 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Detected refreshrate: %f hertz, rounding to %i hertz", fps, RefreshRate);
1031 m_RefreshRate = RefreshRate;
1033 m_LastRefreshTime = m_CurrTime;
1035 //calculate how many vblanks happened
1036 VBlankTime = (double)(nowtime - m_LastVBlankTime) / (double)m_SystemFrequency;
1037 NrVBlanks = MathUtils::round_int(VBlankTime * (double)m_RefreshRate);
1039 //save the timestamp of this vblank so we can calculate how many happened next time
1040 m_LastVBlankTime = nowtime;
1042 //update the vblank timestamp, update the clock and send a signal that we got a vblank
1043 m_VblankTime = nowtime;
1044 UpdateClock(NrVBlanks, true);
1049 UpdateRefreshrate();
1053 //this is called from the vblank run function and from CVideoReferenceClock::Wait in case of a late update
1054 void CVideoReferenceClock::UpdateClock(int NrVBlanks, bool CheckMissed)
1056 if (CheckMissed) //set to true from the vblank run function, set to false from Wait and GetTime
1058 if (NrVBlanks < m_MissedVblanks) //if this is true the vblank detection in the run function is wrong
1059 CLog::Log(LOGDEBUG, "CVideoReferenceClock: detected %i vblanks, missed %i, refreshrate might have changed",
1060 NrVBlanks, m_MissedVblanks);
1062 NrVBlanks -= m_MissedVblanks; //subtract the vblanks we missed
1063 m_MissedVblanks = 0;
1067 m_MissedVblanks += NrVBlanks; //tell the vblank clock how many vblanks it missed
1068 m_TotalMissedVblanks += NrVBlanks; //for the codec information screen
1069 m_VblankTime += m_SystemFrequency * (int64_t)NrVBlanks / m_RefreshRate; //set the vblank time forward
1072 if (NrVBlanks > 0) //update the clock with the adjusted frequency if we have any vblanks
1074 double increment = UpdateInterval() * NrVBlanks;
1075 double integer = floor(increment);
1076 m_CurrTime += (int64_t)(integer + 0.5); //make sure it gets correctly converted to int
1078 //accumulate what we lost due to rounding in m_CurrTimeFract, then add the integer part of that to m_CurrTime
1079 m_CurrTimeFract += increment - integer;
1080 integer = floor(m_CurrTimeFract);
1081 m_CurrTime += (int64_t)(integer + 0.5);
1082 m_CurrTimeFract -= integer;
1086 double CVideoReferenceClock::UpdateInterval()
1088 return m_ClockSpeed * m_fineadjust / (double)m_RefreshRate * (double)m_SystemFrequency;
1091 //called from dvdclock to get the time
1092 int64_t CVideoReferenceClock::GetTime(bool interpolated /* = true*/)
1094 CSingleLock SingleLock(m_CritSection);
1096 //when using vblank, get the time from that, otherwise use the systemclock
1102 Now = CurrentHostCounter(); //get current system time
1103 NextVblank = TimeOfNextVblank(); //get time when the next vblank should happen
1105 while(Now >= NextVblank) //keep looping until the next vblank is in the future
1107 UpdateClock(1, false); //update clock when next vblank should have happened already
1108 NextVblank = TimeOfNextVblank(); //get time when the next vblank should happen
1113 //interpolate from the last time the clock was updated
1114 double elapsed = (double)(Now - m_VblankTime) * m_ClockSpeed * m_fineadjust;
1115 //don't interpolate more than 2 vblank periods
1116 elapsed = min(elapsed, UpdateInterval() * 2.0);
1118 //make sure the clock doesn't go backwards
1119 int64_t intTime = m_CurrTime + (int64_t)elapsed;
1120 if (intTime > m_LastIntTime)
1121 m_LastIntTime = intTime;
1123 return m_LastIntTime;
1132 return CurrentHostCounter() + m_ClockOffset;
1136 //called from dvdclock to get the clock frequency
1137 int64_t CVideoReferenceClock::GetFrequency()
1139 return m_SystemFrequency;
1142 void CVideoReferenceClock::SetSpeed(double Speed)
1144 CSingleLock SingleLock(m_CritSection);
1145 //dvdplayer can change the speed to fit the rereshrate
1148 if (Speed != m_ClockSpeed)
1150 m_ClockSpeed = Speed;
1151 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Clock speed %f%%", GetSpeed() * 100.0);
1156 double CVideoReferenceClock::GetSpeed()
1158 CSingleLock SingleLock(m_CritSection);
1160 //dvdplayer needs to know the speed for the resampler
1162 return m_ClockSpeed;
1167 bool CVideoReferenceClock::UpdateRefreshrate(bool Forced /*= false*/)
1169 //if the graphicscontext signaled that the refreshrate changed, we check it about one second later
1170 if (m_RefreshChanged == 1 && !Forced)
1172 m_LastRefreshTime = m_CurrTime;
1173 m_RefreshChanged = 2;
1177 //update the refreshrate about once a second, or update immediately if a forced update is required
1178 if (m_CurrTime - m_LastRefreshTime < m_SystemFrequency && !Forced)
1182 m_LastRefreshTime = 0;
1184 m_LastRefreshTime = m_CurrTime;
1186 #if defined(HAS_GLX) && defined(HAS_XRANDR)
1188 //check for RandR events
1189 bool GotEvent = Forced || m_RefreshChanged == 2;
1191 while (XCheckTypedEvent(m_Dpy, m_RREventBase + RRScreenChangeNotify, &Event))
1193 if (Event.type == m_RREventBase + RRScreenChangeNotify)
1195 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Received RandR event %i", Event.type);
1198 XRRUpdateConfiguration(&Event);
1202 m_RefreshChanged = 0;
1204 if (!GotEvent) //refreshrate did not change
1207 //the refreshrate can be wrong on nvidia drivers, so read it from nvidia-settings when it's available
1208 if (m_UseNvSettings)
1211 //if this fails we can't get the refreshrate from nvidia-settings
1212 m_UseNvSettings = ParseNvSettings(NvRefreshRate);
1214 if (m_UseNvSettings)
1216 CSingleLock SingleLock(m_CritSection);
1217 m_RefreshRate = NvRefreshRate;
1221 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Using RandR for refreshrate detection");
1224 CSingleLock SingleLock(m_CritSection);
1225 m_RefreshRate = GetRandRRate();
1227 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Detected refreshrate: %i hertz", (int)m_RefreshRate);
1231 #elif defined(TARGET_WINDOWS) && defined(HAS_DX)
1233 D3DDISPLAYMODE DisplayMode;
1234 m_D3dDev->GetDisplayMode(0, &DisplayMode);
1236 //0 indicates adapter default
1237 if (DisplayMode.RefreshRate == 0)
1238 DisplayMode.RefreshRate = 60;
1240 if (m_PrevRefreshRate != DisplayMode.RefreshRate || m_Width != DisplayMode.Width || m_Height != DisplayMode.Height ||
1241 m_Interlaced != g_Windowing.Interlaced() || Forced )
1243 m_PrevRefreshRate = DisplayMode.RefreshRate;
1244 m_Width = DisplayMode.Width;
1245 m_Height = DisplayMode.Height;
1246 m_Interlaced = g_Windowing.Interlaced();
1252 #elif defined(TARGET_DARWIN)
1253 #if defined(TARGET_DARWIN_IOS)
1254 int RefreshRate = round(g_Windowing.GetDisplayLinkFPS() + 0.5);
1256 int RefreshRate = MathUtils::round_int(Cocoa_GetCVDisplayLinkRefreshPeriod());
1259 if (RefreshRate != m_RefreshRate || Forced)
1261 CSingleLock SingleLock(m_CritSection);
1262 CLog::Log(LOGDEBUG, "CVideoReferenceClock: Detected refreshrate: %i hertz", RefreshRate);
1263 m_RefreshRate = RefreshRate;
1272 //dvdplayer needs to know the refreshrate for matching the fps of the video playing to it
1273 int CVideoReferenceClock::GetRefreshRate(double* interval /*= NULL*/)
1275 CSingleLock SingleLock(m_CritSection);
1280 *interval = m_ClockSpeed / m_RefreshRate;
1282 return (int)m_RefreshRate;
1289 //this is called from CDVDClock::WaitAbsoluteClock, which is called from CXBMCRenderManager::WaitPresentTime
1290 //it waits until a certain timestamp has passed, used for displaying videoframes at the correct moment
1291 int64_t CVideoReferenceClock::Wait(int64_t Target)
1296 CSingleLock SingleLock(m_CritSection);
1298 if (m_UseVblank) //when true the vblank is used as clock source
1300 while (m_CurrTime < Target)
1302 //calculate how long to sleep before we should have gotten a signal that a vblank happened
1303 Now = CurrentHostCounter();
1304 int64_t NextVblank = TimeOfNextVblank();
1305 SleepTime = (int)((NextVblank - Now) * 1000 / m_SystemFrequency);
1307 int64_t CurrTime = m_CurrTime; //save current value of the clock
1310 if (SleepTime <= 0) //if sleeptime is 0 or lower, the vblank clock is already late in updating
1316 m_VblankEvent.Reset();
1318 if (!m_VblankEvent.WaitMSec(SleepTime)) //if this returns false, it means the vblank event was not set within
1319 Late = true; //the required time
1323 //if the vblank clock was late with its update, we update the clock ourselves
1324 if (Late && CurrTime == m_CurrTime)
1325 UpdateClock(1, false); //update the clock by 1 vblank
1332 int64_t ClockOffset = m_ClockOffset;
1334 Now = CurrentHostCounter();
1335 //sleep until the timestamp has passed
1336 SleepTime = (int)((Target - (Now + ClockOffset)) * 1000 / m_SystemFrequency);
1340 Now = CurrentHostCounter();
1341 return Now + ClockOffset;
1346 void CVideoReferenceClock::SendVblankSignal()
1348 m_VblankEvent.Set();
1351 #define MAXVBLANKDELAY 13LL
1352 //guess when the next vblank should happen,
1353 //based on the refreshrate and when the previous one happened
1354 //increase that by 30% to allow for errors
1355 int64_t CVideoReferenceClock::TimeOfNextVblank()
1357 return m_VblankTime + (m_SystemFrequency / m_RefreshRate * MAXVBLANKDELAY / 10LL);
1360 //for the codec information screen
1361 bool CVideoReferenceClock::GetClockInfo(int& MissedVblanks, double& ClockSpeed, int& RefreshRate)
1365 MissedVblanks = m_TotalMissedVblanks;
1366 ClockSpeed = m_ClockSpeed * 100.0;
1367 RefreshRate = (int)m_RefreshRate;
1373 void CVideoReferenceClock::SetFineAdjust(double fineadjust)
1375 CSingleLock SingleLock(m_CritSection);
1376 m_fineadjust = fineadjust;
1379 CVideoReferenceClock g_VideoReferenceClock;