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