2014-04-24 14 views
5

Sto scrivendo un'applicazione C++/SDL/OpenGL e ho avuto il bug più strano. Il gioco sembrava funzionare bene con un semplice timestep variabile. Ma poi l'FPS ha iniziato a comportarsi in modo strano. Ho capito che sia Sleep (1) che SDL_Delay (1) richiedono 15 ms per essere completati.Sleep (1) e SDL_Delay (1) richiede 15 ms

Qualsiasi ingresso in tali funzioni tra 0-15 prende 15ms per completare, bloccando FPS a circa 64. Se impostato a 16, ci vuole 30 MS o.o

ho idea perché questo sta avvenendo. Questo è il bug più strano che abbia mai incontrato.

Il mio ciclo si presenta così:

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

Sarà molto raramente prendere 1ms come si suppone, ma la maggior parte del tempo che ci vuole 15ms.

Il mio sistema operativo è Windows 8.1. La CPU è un Intel i7. Sto usando SDL2. Qualsiasi idea sarebbe apprezzata, dal momento che io sono senza tracce.

+2

possibile duplicato della chiamata di funzione [WinAPI Sleep() per un periodo di inattività superiore a quello previsto] (http://stackoverflow.com/questions/9518106/winapi-sleep-function- call-sleeps-for-longer-than-expected) –

+0

Non si desidera mettere un thread/processo in stop se ci si aspetta che si attivi in ​​tempo reale. Usa uno spinlock se non vuoi preoccuparti della programmazione. –

risposta

8

Il valore predefinito è 64 hz o 15,66 ms/tick. È necessario modificare questo a 1000hz == 1 ms con timeBeginPeriod (1). MSDN articolo:

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

Se l'obiettivo è quello di ottenere una sequenza di frequenza fissa, si dovrebbe utilizzare un timer risoluzione più alta, ma purtroppo questi possono essere interrogati solo, quindi una combinazione di sondaggi e dormire per ridurre cpu è necessario un sovraccarico. Codice di esempio, che presuppone che un Sleep (1) possa richiedere fino a quasi 2 ms (cosa che accade con Windows XP, ma non con le versioni successive di 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

Grazie mille! Non ero a conoscenza di questo! – user3346893

+0

Poiché uPrev si basa su un numero calcolato di zecche a partire da una lettura originale del timer, non ci sarà alcuna deriva nel tempo usando questo metodo invece di fare affidamento su un delta tra le letture correnti e precedenti del timer. Poiché il sonno è impostato per consentire fino a 2 ms di ritardo, il codice di esempio dovrebbe essere valido fino a circa 400 Hz. Se il supporto di Windows XP non è necessario, il ritardo può presumere che Sleep (1) impiegherà circa 1 ms (potrebbe essere utile aggiungere un margine a questo), nel qual caso dovrebbe essere buono per 800hz. – rcgldr

+0

Se il periodo fisso è un multiplo esatto di 1 ms e il supporto di Windows XP non è necessario, è possibile utilizzare le funzioni di evento del timer multimediale. Articolo MSDN: http://msdn.microsoft.com/en-us/library/windows/desktop/dd742877(v=vs.85).aspx. Windows XP è un problema, il ticker verrà eseguito a 1024hz e 1000hz è simulato includendo un tick extra dopo 42, 42, quindi 41 tick in modo che 128 tick effettivi finiscano a 125 pseudo tick (per tornare sul limite di 1ms) . – rcgldr

0

SDL_Delay()/sleep() non può essere utilizzato in modo affidabile con tempi inferiori a 10-15 millisecondi. I tick della CPU non si registrano abbastanza velocemente da rilevare una differenza di 1 ms.

Vedere SDL docs here.

+0

Hai ragione, grazie non ne ero consapevole! – user3346893

+1

I tick della CPU si registrano più che abbastanza velocemente, il vero problema è cosa succede quando si mette un thread in esecuzione in sleep. Va alla fine di una coda pronta, e quindi è in balia della programmazione. Alcuni framework sono abbastanza intelligenti da evitare questo comportamento se l'intervallo è abbastanza piccolo, ma a quanto pare questo non è uno di questi. In alternativa, è possibile impostare la priorità del processo in tempo reale e il processo invierà frequentemente altri prima del tipico valore di 10-15 ms. –

1

Sembra che 15 ms sia la porzione più piccola che il sistema operativo fornirà all'utente. Non sono sicuro della tua struttura specifica, ma il sonno di solito garantisce un tempo di sonno minimo. (cioè dormirà per almeno 1 ms)