]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_demo.c
expose r_refdef.view.quality to CSQC R_SetView VF_MINFPS_QUALITY
[xonotic/darkplaces.git] / cl_demo.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "quakedef.h"
22
23 extern cvar_t cl_capturevideo;
24 int old_vsync = 0;
25
26 static void CL_FinishTimeDemo (void);
27
28 /*
29 ==============================================================================
30
31 DEMO CODE
32
33 When a demo is playing back, all outgoing network messages are skipped, and
34 incoming messages are read from the demo file.
35
36 Whenever cl.time gets past the last received message, another message is
37 read from the demo file.
38 ==============================================================================
39 */
40
41 /*
42 =====================
43 CL_NextDemo
44
45 Called to play the next demo in the demo loop
46 =====================
47 */
48 void CL_NextDemo (void)
49 {
50         char    str[MAX_INPUTLINE];
51
52         if (cls.demonum == -1)
53                 return;         // don't play demos
54
55         if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
56         {
57                 cls.demonum = 0;
58                 if (!cls.demos[cls.demonum][0])
59                 {
60                         Con_Print("No demos listed with startdemos\n");
61                         cls.demonum = -1;
62                         return;
63                 }
64         }
65
66         dpsnprintf (str, sizeof(str), "playdemo %s\n", cls.demos[cls.demonum]);
67         Cbuf_InsertText (str);
68         cls.demonum++;
69 }
70
71 /*
72 ==============
73 CL_StopPlayback
74
75 Called when a demo file runs out, or the user starts a game
76 ==============
77 */
78 // LordHavoc: now called only by CL_Disconnect
79 void CL_StopPlayback (void)
80 {
81         if (!cls.demoplayback)
82                 return;
83
84         FS_Close (cls.demofile);
85         cls.demoplayback = false;
86         cls.demofile = NULL;
87
88         if (cls.timedemo)
89                 CL_FinishTimeDemo ();
90
91         if (!cls.demostarting) // only quit if not starting another demo
92                 if (COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
93                         Host_Quit_f();
94
95 }
96
97 /*
98 ====================
99 CL_WriteDemoMessage
100
101 Dumps the current net message, prefixed by the length and view angles
102 #====================
103 */
104 void CL_WriteDemoMessage (sizebuf_t *message)
105 {
106         int             len;
107         int             i;
108         float   f;
109
110         if (cls.demopaused) // LordHavoc: pausedemo
111                 return;
112
113         len = LittleLong (message->cursize);
114         FS_Write (cls.demofile, &len, 4);
115         for (i=0 ; i<3 ; i++)
116         {
117                 f = LittleFloat (cl.viewangles[i]);
118                 FS_Write (cls.demofile, &f, 4);
119         }
120         FS_Write (cls.demofile, message->data, message->cursize);
121 }
122
123 /*
124 ====================
125 CL_CutDemo
126
127 Dumps the current demo to a buffer, and resets the demo to its starting point.
128 Used to insert csprogs.dat files as a download to the beginning of a demo file.
129 ====================
130 */
131 void CL_CutDemo (unsigned char **buf, fs_offset_t *filesize)
132 {
133         *buf = NULL;
134         *filesize = 0;
135
136         FS_Close(cls.demofile);
137         *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize);
138
139         // restart the demo recording
140         cls.demofile = FS_OpenRealFile(cls.demoname, "wb", false);
141         if(!cls.demofile)
142                 Sys_Error("failed to reopen the demo file");
143         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
144 }
145
146 /*
147 ====================
148 CL_PasteDemo
149
150 Adds the cut stuff back to the demo. Also frees the buffer.
151 Used to insert csprogs.dat files as a download to the beginning of a demo file.
152 ====================
153 */
154 void CL_PasteDemo (unsigned char **buf, fs_offset_t *filesize)
155 {
156         fs_offset_t startoffset = 0;
157
158         if(!*buf)
159                 return;
160
161         // skip cdtrack
162         while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n')
163                 ++startoffset;
164         if(startoffset < *filesize)
165                 ++startoffset;
166
167         FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset);
168
169         Mem_Free(*buf);
170         *buf = NULL;
171         *filesize = 0;
172 }
173
174 /*
175 ====================
176 CL_ReadDemoMessage
177
178 Handles playback of demos
179 ====================
180 */
181 void CL_ReadDemoMessage(void)
182 {
183         int i;
184         float f;
185
186         if (!cls.demoplayback)
187                 return;
188
189         // LordHavoc: pausedemo
190         if (cls.demopaused)
191                 return;
192
193         for (;;)
194         {
195                 // decide if it is time to grab the next message
196                 // always grab until fully connected
197                 if (cls.signon == SIGNONS)
198                 {
199                         if (cls.timedemo)
200                         {
201                                 cls.td_frames++;
202                                 cls.td_onesecondframes++;
203                                 // if this is the first official frame we can now grab the real
204                                 // td_starttime so the bogus time on the first frame doesn't
205                                 // count against the final report
206                                 if (cls.td_frames == 0)
207                                 {
208                                         cls.td_starttime = realtime;
209                                         cls.td_onesecondnexttime = cl.time + 1;
210                                         cls.td_onesecondrealtime = realtime;
211                                         cls.td_onesecondframes = 0;
212                                         cls.td_onesecondminfps = 0;
213                                         cls.td_onesecondmaxfps = 0;
214                                         cls.td_onesecondavgfps = 0;
215                                         cls.td_onesecondavgcount = 0;
216                                 }
217                                 if (cl.time >= cls.td_onesecondnexttime)
218                                 {
219                                         double fps = cls.td_onesecondframes / (realtime - cls.td_onesecondrealtime);
220                                         if (cls.td_onesecondavgcount == 0)
221                                         {
222                                                 cls.td_onesecondminfps = fps;
223                                                 cls.td_onesecondmaxfps = fps;
224                                         }
225                                         cls.td_onesecondrealtime = realtime;
226                                         cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps);
227                                         cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps);
228                                         cls.td_onesecondavgfps += fps;
229                                         cls.td_onesecondavgcount++;
230                                         cls.td_onesecondframes = 0;
231                                         cls.td_onesecondnexttime++;
232                                 }
233                         }
234                         else if (cl.time <= cl.mtime[0])
235                         {
236                                 // don't need another message yet
237                                 return;
238                         }
239                 }
240
241                 // get the next message
242                 FS_Read(cls.demofile, &cl_message.cursize, 4);
243                 cl_message.cursize = LittleLong(cl_message.cursize);
244                 if(cl_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
245                 {
246                         // skip over demo packet
247                         FS_Seek(cls.demofile, 12 + (cl_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
248                         continue;
249                 }
250                 if (cl_message.cursize > cl_message.maxsize)
251                 {
252                         Con_Printf("Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize);
253                         cl_message.cursize = 0;
254                         CL_Disconnect();
255                         return;
256                 }
257                 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
258                 for (i = 0;i < 3;i++)
259                 {
260                         FS_Read(cls.demofile, &f, 4);
261                         cl.mviewangles[0][i] = LittleFloat(f);
262                 }
263
264                 if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize)
265                 {
266                         MSG_BeginReading(&cl_message);
267                         CL_ParseServerMessage();
268
269                         if (cls.signon != SIGNONS)
270                                 Cbuf_Execute(); // immediately execute svc_stufftext if in the demo before connect!
271
272                         // In case the demo contains a "svc_disconnect" message
273                         if (!cls.demoplayback)
274                                 return;
275
276                         if (cls.timedemo)
277                                 return;
278                 }
279                 else
280                 {
281                         CL_Disconnect();
282                         return;
283                 }
284         }
285 }
286
287
288 /*
289 ====================
290 CL_Stop_f
291
292 stop recording a demo
293 ====================
294 */
295 void CL_Stop_f (void)
296 {
297         sizebuf_t buf;
298         unsigned char bufdata[64];
299
300         if (!cls.demorecording)
301         {
302                 Con_Print("Not recording a demo.\n");
303                 return;
304         }
305
306 // write a disconnect message to the demo file
307         // LordHavoc: don't replace the cl_message when doing this
308         buf.data = bufdata;
309         buf.maxsize = sizeof(bufdata);
310         SZ_Clear(&buf);
311         MSG_WriteByte(&buf, svc_disconnect);
312         CL_WriteDemoMessage(&buf);
313
314 // finish up
315         if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
316         {
317                 FS_RemoveOnClose(cls.demofile);
318                 Con_Print("Completed and deleted demo\n");
319         }
320         else
321                 Con_Print("Completed demo\n");
322         FS_Close (cls.demofile);
323         cls.demofile = NULL;
324         cls.demorecording = false;
325 }
326
327 /*
328 ====================
329 CL_Record_f
330
331 record <demoname> <map> [cd track]
332 ====================
333 */
334 void CL_Record_f (void)
335 {
336         int c, track;
337         char name[MAX_OSPATH];
338         char vabuf[1024];
339
340         c = Cmd_Argc();
341         if (c != 2 && c != 3 && c != 4)
342         {
343                 Con_Print("record <demoname> [<map> [cd track]]\n");
344                 return;
345         }
346
347         if (strstr(Cmd_Argv(1), ".."))
348         {
349                 Con_Print("Relative pathnames are not allowed.\n");
350                 return;
351         }
352
353         if (c == 2 && cls.state == ca_connected)
354         {
355                 Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n");
356                 return;
357         }
358
359         if (cls.state == ca_connected)
360                 CL_Disconnect();
361
362         // write the forced cd track number, or -1
363         if (c == 4)
364         {
365                 track = atoi(Cmd_Argv(3));
366                 Con_Printf("Forcing CD track to %i\n", cls.forcetrack);
367         }
368         else
369                 track = -1;
370
371         // get the demo name
372         strlcpy (name, Cmd_Argv(1), sizeof (name));
373         FS_DefaultExtension (name, ".dem", sizeof (name));
374
375         // start the map up
376         if (c > 2)
377                 Cmd_ExecuteString ( va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(2)), src_command, false);
378
379         // open the demo file
380         Con_Printf("recording to %s.\n", name);
381         cls.demofile = FS_OpenRealFile(name, "wb", false);
382         if (!cls.demofile)
383         {
384                 Con_Print("ERROR: couldn't open.\n");
385                 return;
386         }
387         strlcpy(cls.demoname, name, sizeof(cls.demoname));
388
389         cls.forcetrack = track;
390         FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
391
392         cls.demorecording = true;
393         cls.demo_lastcsprogssize = -1;
394         cls.demo_lastcsprogscrc = -1;
395 }
396
397
398 /*
399 ====================
400 CL_PlayDemo_f
401
402 play [demoname]
403 ====================
404 */
405 void CL_PlayDemo_f (void)
406 {
407         char    name[MAX_QPATH];
408         int c;
409         qboolean neg = false;
410         qfile_t *f;
411
412         if (Cmd_Argc() != 2)
413         {
414                 Con_Print("play <demoname> : plays a demo\n");
415                 return;
416         }
417
418         // open the demo file
419         strlcpy (name, Cmd_Argv(1), sizeof (name));
420         FS_DefaultExtension (name, ".dem", sizeof (name));
421         f = FS_OpenVirtualFile(name, false);
422         if (!f)
423         {
424                 Con_Printf("ERROR: couldn't open %s.\n", name);
425                 cls.demonum = -1;               // stop demo loop
426                 return;
427         }
428
429         cls.demostarting = true;
430
431         // disconnect from server
432         CL_Disconnect ();
433         Host_ShutdownServer ();
434
435         // update networking ports (this is mainly just needed at startup)
436         NetConn_UpdateSockets();
437
438         cls.protocol = PROTOCOL_QUAKE;
439
440         Con_Printf("Playing demo %s.\n", name);
441         cls.demofile = f;
442         strlcpy(cls.demoname, name, sizeof(cls.demoname));
443
444         cls.demoplayback = true;
445         cls.state = ca_connected;
446         cls.forcetrack = 0;
447
448         while ((c = FS_Getc (cls.demofile)) != '\n')
449                 if (c == '-')
450                         neg = true;
451                 else
452                         cls.forcetrack = cls.forcetrack * 10 + (c - '0');
453
454         if (neg)
455                 cls.forcetrack = -cls.forcetrack;
456
457         cls.demostarting = false;
458 }
459
460 typedef struct
461 {
462         int frames;
463         double time, totalfpsavg;
464         double fpsmin, fpsavg, fpsmax;
465 }
466 benchmarkhistory_t;
467 static size_t doublecmp_offset;
468 static int doublecmp_withoffset(const void *a_, const void *b_)
469 {
470         const double *a = (const double *) ((const char *) a_ + doublecmp_offset);
471         const double *b = (const double *) ((const char *) b_ + doublecmp_offset);
472         if(*a > *b)
473                 return +1;
474         if(*a < *b)
475                 return -1;
476         return 0;
477 }
478
479 /*
480 ====================
481 CL_FinishTimeDemo
482
483 ====================
484 */
485 static void CL_FinishTimeDemo (void)
486 {
487         int frames;
488         int i;
489         double time, totalfpsavg;
490         double fpsmin, fpsavg, fpsmax; // report min/avg/max fps
491         static int benchmark_runs = 0;
492         char vabuf[1024];
493
494         cls.timedemo = false;
495
496         frames = cls.td_frames;
497         time = realtime - cls.td_starttime;
498         totalfpsavg = time > 0 ? frames / time : 0;
499         fpsmin = cls.td_onesecondminfps;
500         fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0;
501         fpsmax = cls.td_onesecondmaxfps;
502         // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max
503         Con_Printf("%i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
504         Log_Printf("benchmark.log", "date %s | enginedate %s | demo %s | commandline %s | run %d | result %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", Sys_TimeString("%Y-%m-%d %H:%M:%S"), buildstring, cls.demoname, cmdline.string, benchmark_runs + 1, frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
505         if (COM_CheckParm("-benchmark"))
506         {
507                 ++benchmark_runs;
508                 i = COM_CheckParm("-benchmarkruns");
509                 if(i && i + 1 < com_argc)
510                 {
511                         static benchmarkhistory_t *history = NULL;
512                         if(!history)
513                                 history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(com_argv[i + 1]));
514
515                         history[benchmark_runs - 1].frames = frames;
516                         history[benchmark_runs - 1].time = time;
517                         history[benchmark_runs - 1].totalfpsavg = totalfpsavg;
518                         history[benchmark_runs - 1].fpsmin = fpsmin;
519                         history[benchmark_runs - 1].fpsavg = fpsavg;
520                         history[benchmark_runs - 1].fpsmax = fpsmax;
521
522                         if(atoi(com_argv[i + 1]) > benchmark_runs)
523                         {
524                                 // restart the benchmark
525                                 Cbuf_AddText(va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname));
526                                 // cannot execute here
527                         }
528                         else
529                         {
530                                 // print statistics
531                                 int first = COM_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0;
532                                 if(benchmark_runs > first)
533                                 {
534 #define DO_MIN(f) \
535                                         for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f
536
537 #define DO_MAX(f) \
538                                         for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f
539
540 #define DO_MED(f) \
541                                         doublecmp_offset = (char *)&history->f - (char *)history; \
542                                         qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \
543                                         if((first + benchmark_runs) & 1) \
544                                                 f = history[(first + benchmark_runs - 1) / 2].f; \
545                                         else \
546                                                 f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2
547
548                                         DO_MIN(frames);
549                                         DO_MAX(time);
550                                         DO_MIN(totalfpsavg);
551                                         DO_MIN(fpsmin);
552                                         DO_MIN(fpsavg);
553                                         DO_MIN(fpsmax);
554                                         Con_Printf("MIN: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
555
556                                         DO_MED(frames);
557                                         DO_MED(time);
558                                         DO_MED(totalfpsavg);
559                                         DO_MED(fpsmin);
560                                         DO_MED(fpsavg);
561                                         DO_MED(fpsmax);
562                                         Con_Printf("MED: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
563
564                                         DO_MAX(frames);
565                                         DO_MIN(time);
566                                         DO_MAX(totalfpsavg);
567                                         DO_MAX(fpsmin);
568                                         DO_MAX(fpsavg);
569                                         DO_MAX(fpsmax);
570                                         Con_Printf("MAX: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount);
571                                 }
572                                 Z_Free(history);
573                                 history = NULL;
574                                 Host_Quit_f();
575                         }
576                 }
577                 else
578                         Host_Quit_f();
579         }
580 }
581
582 /*
583 ====================
584 CL_TimeDemo_f
585
586 timedemo [demoname]
587 ====================
588 */
589 void CL_TimeDemo_f (void)
590 {
591         if (Cmd_Argc() != 2)
592         {
593                 Con_Print("timedemo <demoname> : gets demo speeds\n");
594                 return;
595         }
596
597         srand(0); // predictable random sequence for benchmarking
598
599         CL_PlayDemo_f ();
600
601 // cls.td_starttime will be grabbed at the second frame of the demo, so
602 // all the loading time doesn't get counted
603
604         // instantly hide console and deactivate it
605         key_dest = key_game;
606         key_consoleactive = 0;
607         scr_con_current = 0;
608
609         cls.timedemo = true;
610         cls.td_frames = -2;             // skip the first frame
611         cls.demonum = -1;               // stop demo loop
612         cls.demonum = -1;               // stop demo loop
613 }
614