Merge branch 'framecounter_5th_try'

Like the branch name suggests this was a rather hard ride. Lessons
learned:

* Quake IIs timing is one big clusterfuck.
* The Radeon driver for Windows has a questionable vsync implementation.

What should be done:

* Get rid of the global currenttime.
* DEDICATED_ONLY needs to be refactored.

This commit closes issue #222.
This commit is contained in:
Yamagi Burmeister 2017-09-06 19:34:52 +02:00
commit 143c5d1e40
9 changed files with 348 additions and 239 deletions

View file

@ -38,13 +38,10 @@ qboolean is_portable;
int
main(int argc, char **argv)
{
int time, oldtime, newtime;
int verLen, i;
long long oldtime, newtime;
const char* versionString;
#ifndef BUSY_WAIT
struct timespec t;
#endif
struct timespec t = {0, 5000};
/* register signal handler */
registerHandler();
@ -134,27 +131,16 @@ main(int argc, char **argv)
/* Do not delay reads on stdin*/
fcntl(fileno(stdin), F_SETFL, fcntl(fileno(stdin), F_GETFL, NULL) | FNDELAY);
oldtime = Sys_Milliseconds();
t.tv_sec = 0;
oldtime = Sys_Microseconds();
/* The legendary Quake II mainloop */
/* The mainloop. The legend. */
while (1)
{
/* find time spent rendering last frame */
do
{
#ifndef BUSY_WAIT
/* Sleep 10 microseconds */
t.tv_nsec = 10000;
nanosleep(&t, NULL);
#endif
// Throttle the game a little bit.
nanosleep(&t, NULL);
newtime = Sys_Milliseconds();
time = newtime - oldtime;
}
while (time < 1);
Qcommon_Frame(time);
newtime = Sys_Microseconds();
Qcommon_Frame(newtime - oldtime);
oldtime = newtime;
}

View file

@ -44,6 +44,7 @@
#include <errno.h>
#include <dlfcn.h>
#include <dirent.h>
#include <time.h>
#include "../../common/header/common.h"
#include "../../common/header/glob.h"
@ -79,32 +80,41 @@ Sys_Init(void)
{
}
long long
Sys_Microseconds(void)
{
static struct timespec last;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if(last.tv_sec == 0)
{
clock_gettime(CLOCK_MONOTONIC, &last);
return last.tv_nsec / 1000ll;
}
long long sec = now.tv_sec - last.tv_sec;
long long nsec = now.tv_nsec - last.tv_nsec;
if(nsec < 0)
{
nsec += 1000000000ll; // 1s in ns
--sec;
}
curtime = (int)((sec*1000000ll + nsec/1000ll) / 1000ll);
return sec*1000000ll + nsec/1000ll;
}
int
Sys_Milliseconds(void)
{
struct timeval tp;
struct timezone tzp;
static int secbase;
gettimeofday(&tp, &tzp);
if (!secbase)
{
secbase = tp.tv_sec;
return tp.tv_usec / 1000;
}
curtime = (tp.tv_sec - secbase) * 1000 + tp.tv_usec / 1000;
curtime = (int)(Sys_Microseconds()/1000ll);
return curtime;
}
void
Sys_Sleep(int msec)
{
usleep((unsigned int)1000 * msec);
}
void
Sys_Mkdir(char *path)
{

View file

@ -414,19 +414,39 @@ ParseCommandLine(LPSTR lpCmdLine)
/* ======================================================================= */
long long
Sys_Microseconds(void)
{
long long microseconds;
static long long uSecbase;
FILETIME ft;
unsigned long long tmpres = 0;
GetSystemTimeAsFileTime(&ft);
tmpres |= ft.dwHighDateTime;
tmpres <<= 32;
tmpres |= ft.dwLowDateTime;
tmpres /= 10; // Convert to microseconds.
tmpres -= 11644473600000000ULL; // ...and to unix epoch.
microseconds = tmpres;
if (!uSecbase)
{
uSecbase = microseconds - 1001;
}
curtime = (int)((microseconds - uSecbase) / 1000ll);
return microseconds - uSecbase;
}
int
Sys_Milliseconds(void)
{
static int base;
static qboolean initialized = false;
if (!initialized)
{ /* let base retain 16 bits of effectively random data */
base = timeGetTime() & 0xffff0000;
initialized = true;
}
curtime = timeGetTime() - base;
curtime = (int)(Sys_Microseconds()/1000ll);
return curtime;
}
@ -437,6 +457,22 @@ Sys_Sleep(int msec)
Sleep(msec);
}
void Sys_Nanosleep(int nanosec)
{
HANDLE timer;
LARGE_INTEGER li;
timer = CreateWaitableTimer(NULL, TRUE, NULL);
// Windows has a max. resolution of 100ns.
li.QuadPart = -nanosec / 100;
SetWaitableTimer(timer, &li, 0, NULL, NULL, FALSE);
WaitForSingleObject(timer, INFINITE);
CloseHandle(timer);
}
/* ======================================================================= */
static qboolean
@ -745,7 +781,7 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
int time, oldtime, newtime;
long long oldtime, newtime;
/* Previous instances do not exist in Win32 */
if (hPrevInstance)
@ -817,7 +853,7 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
Qcommon_Init(argc, argv);
/* Save our time */
oldtime = Sys_Milliseconds();
oldtime = Sys_Microseconds();
/* The legendary main loop */
while (1)
@ -840,14 +876,11 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
DispatchMessage(&msg);
}
do
{
newtime = Sys_Milliseconds();
time = newtime - oldtime;
}
while (time < 1);
// Throttle the game a little bit
Sys_Nanosleep(5000);
Qcommon_Frame(time);
newtime = Sys_Microseconds();
Qcommon_Frame(newtime - oldtime);
oldtime = newtime;
}

View file

@ -35,12 +35,6 @@ extern char key_lines[NUM_KEY_LINES][MAXCMDLINE];
extern int edit_line;
extern int key_linepos;
void
DrawString(int x, int y, char *s)
{
DrawStringScaled(x, y, s, 1.0f);
}
void
DrawStringScaled(int x, int y, char *s, float factor)
{
@ -52,12 +46,6 @@ DrawStringScaled(int x, int y, char *s, float factor)
}
}
void
DrawAltString(int x, int y, char *s)
{
DrawAltStringScaled(x, y, s, 1.0f);
}
void
DrawAltStringScaled(int x, int y, char *s, float factor)
{
@ -452,29 +440,6 @@ Con_Print(char *txt)
}
}
void
Con_CenteredPrint(char *text)
{
int l;
char buffer[1024];
l = strlen(text);
l = (con.linewidth - l) / 2;
if (l <= 0)
{
l = 0;
}
else
{
memset(buffer, ' ', l);
}
strcpy(buffer + l, text);
strcat(buffer, "\n");
Con_Print(buffer);
}
/*
* The input line scrolls horizontally if
* typing goes beyond the right edge

View file

@ -44,21 +44,18 @@ cvar_t *cl_noskins;
cvar_t *cl_footsteps;
cvar_t *cl_timeout;
cvar_t *cl_predict;
cvar_t *cl_maxfps;
cvar_t *cl_drawfps;
cvar_t *cl_gun;
cvar_t *cl_add_particles;
cvar_t *cl_add_lights;
cvar_t *cl_add_entities;
cvar_t *cl_add_blend;
cvar_t *cl_async;
cvar_t *cl_shownet;
cvar_t *cl_showmiss;
cvar_t *cl_showclamp;
cvar_t *cl_paused;
cvar_t *cl_timedemo;
cvar_t *lookspring;
cvar_t *lookstrafe;
@ -83,8 +80,6 @@ cvar_t *hand;
cvar_t *gender;
cvar_t *gender_auto;
cvar_t *gl_maxfps;
cvar_t *gl_stereo;
cvar_t *gl_stereo_separation;
cvar_t *gl_stereo_convergence;
@ -484,9 +479,7 @@ CL_InitLocal(void)
cl_footsteps = Cvar_Get("cl_footsteps", "1", 0);
cl_noskins = Cvar_Get("cl_noskins", "0", 0);
cl_predict = Cvar_Get("cl_predict", "1", 0);
cl_maxfps = Cvar_Get("cl_maxfps", "60", CVAR_ARCHIVE);
cl_drawfps = Cvar_Get("cl_drawfps", "0", CVAR_ARCHIVE);
cl_async = Cvar_Get("cl_async", "1", CVAR_ARCHIVE);
cl_upspeed = Cvar_Get("cl_upspeed", "200", 0);
cl_forwardspeed = Cvar_Get("cl_forwardspeed", "200", 0);
@ -511,9 +504,6 @@ CL_InitLocal(void)
cl_showclamp = Cvar_Get("showclamp", "0", 0);
cl_timeout = Cvar_Get("cl_timeout", "120", 0);
cl_paused = Cvar_Get("paused", "0", 0);
cl_timedemo = Cvar_Get("timedemo", "0", 0);
gl_maxfps = Cvar_Get("gl_maxfps", "95", CVAR_ARCHIVE);
gl_stereo = Cvar_Get( "gl_stereo", "0", CVAR_ARCHIVE );
gl_stereo_separation = Cvar_Get( "gl_stereo_separation", "1", CVAR_ARCHIVE );
@ -711,60 +701,25 @@ CL_UpdateWindowedMouse(void)
}
}
int GLimp_GetRefreshRate(void);
qboolean R_IsVSyncActive(void);
void
CL_Frame(int msec)
CL_Frame(int packetdelta, int renderdelta, int miscdelta, int timedelta,
qboolean packetframe, qboolean renderframe, qboolean miscframe)
{
int nfps;
int rfps;
static int lasttimecalled;
static int packetdelta = 1000;
static int renderdelta = 1000;
static int miscdelta = 1000;
qboolean packetframe = true;
qboolean renderframe = true;
qboolean miscframe = true;
// Dedicated?
if (dedicated->value)
{
return;
}
// Target render frame rate
if (R_IsVSyncActive())
{
rfps = GLimp_GetRefreshRate();
if (rfps > gl_maxfps->value)
{
rfps = (int)gl_maxfps->value;
}
}
else
{
rfps = (int)gl_maxfps->value;
}
// The network framerate must not be higher then the render framerate
nfps = (cl_maxfps->value > rfps) ? rfps : cl_maxfps->value;
// Adjust deltas
packetdelta += msec;
renderdelta += msec;
miscdelta += msec;
// Calculate simulation time
cls.nframetime = packetdelta * 0.001f;
cls.rframetime = renderdelta * 0.001f;
// Calculate simulation time.
cls.nframetime = packetdelta / 1000000.0f;
cls.rframetime = renderdelta / 1000000.0f;
cls.realtime = curtime;
cl.time += msec;
cl.time += timedelta / 1000;
// Don't extrapolate too far ahead
// Don't extrapolate too far ahead.
if (cls.nframetime > 0.5f)
{
cls.nframetime = 0.5f;
@ -775,72 +730,19 @@ CL_Frame(int msec)
cls.rframetime = 0.5f;
}
/* if in the debugger last frame, don't timeout */
if (msec > 5000)
// if in the debugger last frame, don't timeout.
if (timedelta > 5000000)
{
cls.netchan.last_received = Sys_Milliseconds();
}
if (!cl_timedemo->value)
{
// Don't flood while connecting
if ((cls.state == ca_connected) && (packetdelta < 100))
// Don't throttle too much when connecting / loading.
if ((cls.state == ca_connected) && (packetdelta > 100000))
{
packetframe = false;
packetframe = true;
}
if (cl_async->value)
{
// Network frames
if (packetdelta < (1000.0f / nfps))
{
packetframe = false;
}
else if (cls.nframetime == cls.rframetime)
{
packetframe = false;
}
// Render frames
if (renderdelta < (1000.0f / rfps))
{
renderframe = false;
}
// Misc. stuff at 10 FPS
if (miscdelta < 100.0f)
{
miscframe = false;
}
}
else
{
// Cap frames at gl_maxfps
if (renderdelta < (1000.0f / rfps))
{
renderframe = false;
packetframe = false;
miscframe = false;
}
}
// Throttle the game a little bit. 1000 FPS are enough.
if (!packetframe && !renderframe && !cls.forcePacket && !userinfo_modified)
{
double frametime = (1000.0 / cl_maxfps->value - packetdelta) <= (1000.0 / gl_maxfps->value - renderdelta) ?
(1000.0 / cl_maxfps->value - packetdelta) : (1000.0 / gl_maxfps->value - renderdelta);
if (frametime > 1) // FIXME: why > ??
{
Sys_Sleep(1);
}
return;
}
}
else if (msec < 1)
{
return;
}
// Update input stuff
@ -870,20 +772,14 @@ CL_Frame(int msec)
if (packetframe)
{
packetdelta = 0;
CL_SendCmd();
CL_CheckForResend();
}
if (renderframe)
{
renderdelta = 0;
if (miscframe)
{
miscdelta = 0;
VID_CheckChanges();
}

View file

@ -1410,6 +1410,84 @@ SCR_DrawLayout(void)
SCR_ExecuteLayoutString(cl.layout);
}
// ----
void
SCR_Framecounter(void) {
long long newtime;
static int frame;
static int frametimes[60] = {0};
static long long oldtime;
newtime = Sys_Microseconds();
frametimes[frame] = (int)(newtime - oldtime);
oldtime = newtime;
frame++;
if (frame > 59) {
frame = 0;
}
float scale = SCR_GetConsoleScale();
if (cl_drawfps->value == 1) {
// Calculate average of frames.
int avg = 0;
int num = 0;
for (int i = 0; i < 60; i++) {
if (frametimes[i] != 0) {
avg += frametimes[i];
num++;
}
}
char str[10];
snprintf(str, sizeof(str), "%3.2ffps", (1000.0 * 1000.0) / (avg / num));
DrawStringScaled(scale*(viddef.width - 80), 0, str, scale);
} else if (cl_drawfps->value >= 2) {
// Calculate average of frames.
int avg = 0;
int num = 0;
for (int i = 0; i < 60; i++) {
if (frametimes[i] != 0) {
avg += frametimes[i];
num++;
}
}
// Find lowest and highest
int min = frametimes[0];
int max = frametimes[1];
for (int i = 1; i < 60; i++) {
if ((frametimes[i] > 0) && (min < frametimes[i])) {
min = frametimes[i];
}
if ((frametimes[i] > 0) && (max > frametimes[i])) {
max = frametimes[i];
}
}
char str[64];
snprintf(str, sizeof(str), "Min: %7.2ffps, Max: %7.2ffps, Avg: %7.2ffps",
(1000.0 * 1000.0) / min, (1000.0 * 1000.0) / max, (1000.0 * 1000.0) / (avg / num));
DrawStringScaled(viddef.width - scale*(strlen(str)*8 + 2), 0, str, scale);
if (cl_drawfps->value > 2)
{
snprintf(str, sizeof(str), "Max: %5.2fms, Min: %5.2fms, Avg: %5.2fms",
0.001f*min, 0.001f*max, 0.001f*(avg / num));
DrawStringScaled(viddef.width - scale*(strlen(str)*8 + 2), scale*10, str, scale);
}
}
}
// ----
/*
* This is called every frame, and can also be called
* explicitly to flush text to the screen.
@ -1535,13 +1613,6 @@ SCR_UpdateScreen(void)
SCR_DrawNet();
SCR_CheckDrawCenterString();
if (cl_drawfps->value)
{
char s[8];
sprintf(s, "%3.0ffps", 1 / cls.rframetime);
DrawString(viddef.width - 64, 0, s);
}
if (scr_timegraph->value)
{
SCR_DebugGraph(cls.rframetime * 300, 0);
@ -1563,6 +1634,7 @@ SCR_UpdateScreen(void)
}
}
SCR_Framecounter();
R_EndFrame();
}

View file

@ -766,8 +766,7 @@ void Sys_Error(char *error, ...);
void Sys_Quit(void);
char *Sys_GetHomeDir(void);
const char *Sys_GetBinaryDir(void);
void Sys_Sleep(int msec);
long long Sys_Microseconds(void);
void Sys_FreeLibrary(void *handle);
void *Sys_LoadLibrary(const char *path, const char *sym, void **handle);
void *Sys_GetProcAddress(void *handle, const char *sym);
@ -778,7 +777,8 @@ void Sys_RedirectStdout(void);
void CL_Init(void);
void CL_Drop(void);
void CL_Shutdown(void);
void CL_Frame(int msec);
void CL_Frame(int packetdelta, int renderdelta, int miscdelta, int timedelta, qboolean packetframe, qboolean renderframe,
qboolean miscframe);
void Con_Print(char *text);
void SCR_BeginLoadingPlaque(void);

View file

@ -35,7 +35,12 @@ cvar_t *developer;
cvar_t *modder;
cvar_t *timescale;
cvar_t *fixedtime;
cvar_t *portable;
// For timing calculations.
cvar_t *cl_maxfps;
cvar_t *gl_maxfps;
cvar_t *cl_async;
cvar_t *cl_timedemo;
#ifndef DEDICATED_ONLY
cvar_t *showtrace;
@ -46,6 +51,12 @@ extern cvar_t *logfile_active;
extern jmp_buf abortframe; /* an ERR_DROP occured, exit the entire frame */
extern zhead_t z_chain;
// Forward declarations
#ifndef DEDICATED_ONLY
int GLimp_GetRefreshRate(void);
qboolean R_IsVSyncActive(void);
#endif
static byte chktbl[1024] = {
0x84, 0x47, 0x51, 0xc1, 0x93, 0x22, 0x21, 0x24, 0x2f, 0x66, 0x60, 0x4d, 0xb0, 0x7c, 0xda,
0x88, 0x54, 0x15, 0x2b, 0xc6, 0x6c, 0x89, 0xc5, 0x9d, 0x48, 0xee, 0xe6, 0x8a, 0xb5, 0xf4,
@ -234,6 +245,7 @@ Qcommon_Init(int argc, char **argv)
timescale = Cvar_Get("timescale", "1", 0);
fixedtime = Cvar_Get("fixedtime", "0", 0);
logfile_active = Cvar_Get("logfile", "1", CVAR_ARCHIVE);
cl_timedemo = Cvar_Get("timedemo", "0", 0);
#ifndef DEDICATED_ONLY
showtrace = Cvar_Get("showtrace", "0", 0);
#endif
@ -244,6 +256,11 @@ Qcommon_Init(int argc, char **argv)
dedicated = Cvar_Get("dedicated", "0", CVAR_NOSET);
#endif
// For timing calculations.
cl_maxfps = Cvar_Get("cl_maxfps", "60", CVAR_ARCHIVE);
gl_maxfps = Cvar_Get("gl_maxfps", "95", CVAR_ARCHIVE);
cl_async = Cvar_Get("cl_async", "1", CVAR_ARCHIVE);
s = va("%s %s %s %s", YQ2VERSION, YQ2ARCH, BUILD_DATE, YQ2OSTYPE);
Cvar_Get("version", s, CVAR_SERVERINFO | CVAR_NOSET);
@ -299,11 +316,55 @@ Qcommon_Frame(int msec)
int time_after;
#endif
// Target packetframerate.
int pfps;
//Target renderframerate.
int rfps;
// Time since last packetframe in microsec.
static int packetdelta = 1000000;
// Time since last renderframe in microsec.
static int renderdelta = 1000000;
// Time since last misc. frame in microsec.
static int miscdelta = 100000;
// Accumulated time since last client run.
static int clienttimedelta = 0;
// Accumulated time since last server run.
static int servertimedelta = 0;
/* A packetframe runs the server and the client,
but not the renderer. The minimal interval of
packetframes is about 10.000 microsec. If run
more often the movement prediction in pmove.c
breaks. That's the Q2 variant if the famous
125hz bug. */
qboolean packetframe = true;
/* A rendererframe runs the renderer, but not the
client. The minimal interval is about 1000
microseconds. */
qboolean renderframe = true;
/* A miscframe runs several maintenance task like
loading sound samples for the background music.
An interval of 100.000 microseconds is enough. */
qboolean miscframe = true;
/* In case of ERR_DROP we're jumping here. Don't know
if that' really save but it seems to work. So leave
it alone. */
if (setjmp(abortframe))
{
return; /* an ERR_DROP was thrown */
return;
}
if (log_stats->modified)
{
log_stats->modified = false;
@ -333,20 +394,18 @@ Qcommon_Frame(int msec)
}
}
// Timing debug crap. Just for historical reasons.
if (fixedtime->value)
{
msec = fixedtime->value;
msec = (int)fixedtime->value;
}
else if (timescale->value)
{
msec *= timescale->value;
if (msec < 1)
{
msec = 1;
}
}
#ifndef DEDICATED_ONLY
if (showtrace->value)
{
@ -360,19 +419,80 @@ Qcommon_Frame(int msec)
}
#endif
do
{
s = Sys_ConsoleInput();
if (s)
// Calculate target packet- and renderframerate.
#ifndef DEDICATED_ONLY
if (R_IsVSyncActive())
{
rfps = GLimp_GetRefreshRate();
if (rfps > gl_maxfps->value)
{
Cbuf_AddText(va("%s\n", s));
rfps = (int)gl_maxfps->value;
}
}
while (s);
else
{
rfps = (int)gl_maxfps->value;
}
pfps = (cl_maxfps->value > rfps) ? rfps : cl_maxfps->value;
#else
pfps = (int)cl_maxfps->value;
rfps = (int)gl_maxfps->value;
#endif
// Calculate timings.
packetdelta += msec;
renderdelta += msec;
miscdelta += msec;
clienttimedelta += msec;
servertimedelta += msec;
if (!cl_timedemo->value) {
if (cl_async->value) {
// Network frames..
if (packetdelta < (1000000.0f / pfps)) {
packetframe = false;
}
// Render frames.
if (renderdelta < (1000000.0f / rfps)) {
renderframe = false;
}
// Misc. frames.
if (miscdelta < 100000.0f) {
miscframe = false;
}
} else {
// Cap frames at target framerate.
if (renderdelta < (1000000.0f / rfps)) {
renderframe = false;
packetframe = false;
miscframe = false;
}
}
}
else if (clienttimedelta < 1000 || servertimedelta < 1000)
{
return;
}
// Dedicated server terminal console.
do {
s = Sys_ConsoleInput();
if (s) {
Cbuf_AddText(va("%s\n", s));
}
} while (s);
Cbuf_Execute();
#ifndef DEDICATED_ONLY
if (host_speeds->value)
{
@ -380,7 +500,13 @@ Qcommon_Frame(int msec)
}
#endif
SV_Frame(msec);
// Run the serverframe.
if (packetframe) {
SV_Frame(servertimedelta);
servertimedelta = 0;
}
#ifndef DEDICATED_ONLY
if (host_speeds->value)
@ -388,7 +514,14 @@ Qcommon_Frame(int msec)
time_between = Sys_Milliseconds();
}
CL_Frame(msec);
// Run the client frame.
if (packetframe || renderframe || miscframe) {
CL_Frame(packetdelta, renderdelta, miscdelta, clienttimedelta,
packetframe, renderframe, miscframe);
clienttimedelta = 0;
}
if (host_speeds->value)
{
@ -406,6 +539,20 @@ Qcommon_Frame(int msec)
all, sv, gm, cl, rf);
}
#endif
// Reset deltas if necessary.
if (packetframe) {
packetdelta = 0;
}
if (renderframe) {
renderdelta = 0;
}
if (miscframe) {
miscdelta = 0;
}
}
void

View file

@ -388,7 +388,7 @@ SV_Frame(int msec)
return;
}
svs.realtime += msec;
svs.realtime += msec / 1000;
/* keep the random time dependent */
randk();