2014-04-24 26 views
5

Piszę aplikację C++/SDL/OpenGL i miałem najbardziej osobliwy błąd. Wydaje się, że gra działa dobrze z prostym, zmiennym czasem. Ale potem FPS zaczął zachowywać się dziwnie. Doszedłem do wniosku, że zarówno Sleep (1), jak i SDL_Delay (1) ukończą 15 ms.Sleep (1) i SDL_Delay (1) trwa 15 ms

Wszelkie dane wejściowe do tych funkcji między 0-15 trwa 15 ms, aby zakończyć, zamykając FPS około 64. Jeżeli ustawić go do 16, trwa 30 MS o.o.

nie mam pojęcia, dlaczego tak się dzieje. To najdziwniejszy błąd, jaki kiedykolwiek spotkałem.

Moja pętla wygląda następująco:

while (1){ 
    GLuint t = SDL_GetTicks(); 
    Sleep(1); //or SDL_Delay(1) 
    cout << SDL_GetTicks() - t << endl; //outputs 15 
} 

To będzie bardzo rzadko podejmują 1ms jak to ma, ale większość czasu to trwa 15 ms.

Mój system operacyjny to Windows 8.1. CPU to intel i7. Używam SDL2. Wszelkie pomysły zostaną docenione, ponieważ jestem nieświadomy.

+2

możliwy duplikat funkcji [SleepAPI programu WinAPI Sleep sleeps] dłużej niż oczekiwano] (http://stackoverflow.com/questions/9518106/winapi-sleep-function- call-sleeps - przez dłuższy czas niż oczekiwano) –

+0

Nie chcesz, aby wątek/proces był uśpiony, jeśli spodziewasz się, że się obudzi w czasie rzeczywistym. Użyj blokady, jeśli nie chcesz przejmować się planowaniem. –

Odpowiedz

8

Domyślny znacznik to 64 Hz lub 15,625 ms/tyk. Musisz zmienić to na 1000hz == 1ms z timeBeginPeriod (1). MSDN artykuł:

http://msdn.microsoft.com/en-us/library/windows/desktop/dd757624(v=vs.85).aspx

Jeśli celem jest, aby uzyskać stałą sekwencję częstotliwości, należy użyć wyższy licznik rozdzielczości, ale niestety to może być pobrany tylko, więc kombinacja odpytywania i snu w celu zmniejszenia cpu potrzebne jest obciążenie. Przykładowy kod, który zakłada, że ​​uśpienie (1) może zająć do prawie 2 ms (co ma miejsce w systemie Windows XP, ale nie w późniejszych wersjach systemu Windows).

/* code for a thread to run at fixed frequency */ 
#define FREQ 400      /* frequency */ 

typedef unsigned long long UI64;  /* unsigned 64 bit int */ 

LARGE_INTEGER liPerfTemp;    /* used for query */ 
UI64 uFreq = FREQ;      /* process frequency */ 
UI64 uOrig;        /* original tick */ 
UI64 uWait;        /* tick rate/freq */ 
UI64 uRem = 0;       /* tick rate % freq */ 
UI64 uPrev;        /* previous tick based on original tick */ 
UI64 uDelta;       /* current tick - previous */ 
UI64 u2ms;        /* 2ms of ticks */ 
#if 0         /* for optional error check */ 
static DWORD dwLateStep = 0; 
#endif 

    /* wait for some event to start this thread code */ 
    timeBeginPeriod(1);     /* set period to 1ms */ 
    Sleep(128);       /* wait for it to stabilize */ 

    u2ms = ((UI64)(liPerfFreq.QuadPart)+499)/((UI64)500); 

    QueryPerformanceCounter((PLARGE_INTEGER)&liPerfTemp); 
    uOrig = uPrev = liPerfTemp.QuadPart; 

    while(1){ 
     /* update uWait and uRem based on uRem */ 
     uWait = ((UI64)(liPerfFreq.QuadPart) + uRem)/uFreq; 
     uRem = ((UI64)(liPerfFreq.QuadPart) + uRem) % uFreq; 
     /* wait for uWait ticks */ 
     while(1){ 
      QueryPerformanceCounter((PLARGE_INTEGER)&liPerfTemp); 
      uDelta = (UI64)(liPerfTemp.QuadPart - uPrev); 
      if(uDelta >= uWait) 
       break; 
      if((uWait - uDelta) > u2ms) 
       Sleep(1); 
     } 
     #if 0     /* optional error check */ 
     if(uDelta >= (uWait*2)) 
      dwLateStep += 1; 
     #endif 
     uPrev += uWait; 
     /* fixed frequency code goes here */ 
     /* along with some type of break when done */ 
    } 

    timeEndPeriod(1);     /* restore period */ 
+1

Dziękuję bardzo! Nie wiedziałem o tym! – user3346893

+0

Ponieważ uPrev jest oparty na obliczonej liczbie znaczników od czasu oryginalnego odczytu licznika czasu, nie będzie żadnego dryfu w czasie przy użyciu tej metody, w przeciwieństwie do polegania na delcie między bieżącymi i poprzednimi odczytami licznika czasu. Ponieważ uśpienie jest skonfigurowane tak, aby opóźnić do 2 ms, przykładowy kod powinien być dobry na około 400hz. Jeśli obsługa Windows XP nie jest potrzebna, to opóźnienie może zakładać, że Sleep (1) zajmie około 1 ms (może chcieć dodać do tego trochę marginesu), w takim przypadku powinno być dobre na 800hz. – rcgldr

+0

Jeśli ustalony okres jest dokładną wielokrotnością 1 ms, a obsługa systemu Windows XP nie jest potrzebna, można użyć funkcji zdarzenia zegara multimedialnego. Artykuł MSDN: http://msdn.microsoft.com/en-us/library/windows/desktop/dd742877(v=vs.85).aspx. Problem dotyczy Windows XP, ticker będzie działał z prędkością 1024hz, a 1000hz jest symulowany przez dodanie dodatkowego tikania po 42, 42, a następnie 41 taktach, tak aby 128 rzeczywistych tyknięć kończyło się 125 pseudotaskami (aby powrócić na granicę 1ms) . – rcgldr

0

SDL_Delay()/Sleep() nie mogą być używane niezawodnie z czasami poniżej 10-15 milisekund. Tiki procesora nie rejestrują się wystarczająco szybko, aby wykryć różnicę 1 ms.

Zobacz SDL docs here.

+0

Masz rację, dziękuję, nie wiedziałem o tym! – user3346893

+1

Tiki procesora rejestrują się bardziej niż wystarczająco szybko, prawdziwym problemem jest to, co dzieje się, gdy kładziesz bieżący wątek do snu. Przechodzi do tyłu gotowej kolejki, a następnie jest na łasce planowania. Niektóre frameworki są wystarczająco inteligentne, aby uniknąć takiego zachowania, jeśli przerwa jest wystarczająco mała, ale najwyraźniej nie jest to jedna z nich. Alternatywnie możesz ustawić priorytet procesu na czas rzeczywisty, a proces będzie często wyprzedzał inne przed typowym kwantem 10-15 ms. –

1

Wygląda na to, że 15 ms to najmniejszy plaster, który system OS dostarczy. Nie jestem pewien co do twojej konkretnej struktury, ale sen zwykle gwarantuje minimalny czas snu. (to znaczy, że będzie spać przez co najmniej 1 ms.)