]> git.xonotic.org Git - xonotic/darkplaces.git/blob - r_stats.c
.gitignore: Update to ignore .cache directory created by clangd and build directory...
[xonotic/darkplaces.git] / r_stats.c
1 #include "quakedef.h"
2 #include "r_stats.h"
3
4 cvar_t r_speeds_graph = {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph", "0", "display a graph of renderer statistics "};
5 cvar_t r_speeds_graph_filter[8] =
6 {
7         {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_filter_r", "timedelta", "Red - display the specified renderer statistic"},
8         {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_filter_g", "batch_batches", "Green - display the specified renderer statistic"},
9         {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_filter_b", "batch_triangles", "Blue - display the specified renderer statistic"},
10         {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_filter_y", "fast_triangles", "Yellow - display the specified renderer statistic"},
11         {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_filter_c", "copytriangles_triangles", "Cyan - display the specified renderer statistic"},
12         {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_filter_m", "dynamic_triangles", "Magenta - display the specified renderer statistic"},
13         {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_filter_w", "animcache_shade_vertices", "White - display the specified renderer statistic"},
14         {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_filter_o", "animcache_shape_vertices", "Orange - display the specified renderer statistic"},
15 };
16 cvar_t r_speeds_graph_length = {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_length", "1024", "number of frames in statistics graph, can be from 4 to 8192"};
17 cvar_t r_speeds_graph_seconds = {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_seconds", "2", "number of seconds in graph, can be from 0.1 to 120"};
18 cvar_t r_speeds_graph_x = {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_x", "0", "position of graph"};
19 cvar_t r_speeds_graph_y = {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_y", "0", "position of graph"};
20 cvar_t r_speeds_graph_width = {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_width", "256", "size of graph"};
21 cvar_t r_speeds_graph_height = {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_height", "128", "size of graph"};
22 cvar_t r_speeds_graph_maxtimedelta = {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_maxtimedelta", "16667", "maximum timedelta to display in the graph (this value will be the top line)"};
23 cvar_t r_speeds_graph_maxdefault = {CF_CLIENT | CF_ARCHIVE, "r_speeds_graph_maxdefault", "100", "if the minimum and maximum observed values are closer than this, use this value as the graph range (keeps small numbers from being big graphs)"};
24
25
26 const char *r_stat_name[r_stat_count] =
27 {
28         "timedelta",
29         "quality",
30         "renders",
31         "entities",
32         "entities_surfaces",
33         "entities_triangles",
34         "world_leafs",
35         "world_portals",
36         "world_surfaces",
37         "world_triangles",
38         "lightmapupdates",
39         "lightmapupdatepixels",
40         "particles",
41         "drawndecals",
42         "totaldecals",
43         "draws",
44         "draws_vertices",
45         "draws_elements",
46         "lights",
47         "lights_clears",
48         "lights_scissored",
49         "lights_lighttriangles",
50         "lights_shadowtriangles",
51         "lights_dynamicshadowtriangles",
52         "bouncegrid_lights",
53         "bouncegrid_particles",
54         "bouncegrid_traces",
55         "bouncegrid_hits",
56         "bouncegrid_splats",
57         "bouncegrid_bounces",
58         "photoncache_animated",
59         "photoncache_cached",
60         "photoncache_traced",
61         "bloom",
62         "bloom_copypixels",
63         "bloom_drawpixels",
64         "rendertargets_used",
65         "rendertargets_pixels",
66         "indexbufferuploadcount",
67         "indexbufferuploadsize",
68         "vertexbufferuploadcount",
69         "vertexbufferuploadsize",
70         "framedatacurrent",
71         "framedatasize",
72         "bufferdatacurrent_vertex", // R_BUFFERDATA_ types are added to this index
73         "bufferdatacurrent_index16",
74         "bufferdatacurrent_index32",
75         "bufferdatacurrent_uniform",
76         "bufferdatasize_vertex", // R_BUFFERDATA_ types are added to this index
77         "bufferdatasize_index16",
78         "bufferdatasize_index32",
79         "bufferdatasize_uniform",
80         "animcache_skeletal_count",
81         "animcache_skeletal_bones",
82         "animcache_skeletal_maxbones",
83         "animcache_shade_count",
84         "animcache_shade_vertices",
85         "animcache_shade_maxvertices",
86         "animcache_shape_count",
87         "animcache_shape_vertices",
88         "animcache_shape_maxvertices",
89         "batch_batches",
90         "batch_withgaps",
91         "batch_surfaces",
92         "batch_vertices",
93         "batch_triangles",
94         "fast_batches",
95         "fast_surfaces",
96         "fast_vertices",
97         "fast_triangles",
98         "copytriangles_batches",
99         "copytriangles_surfaces",
100         "copytriangles_vertices",
101         "copytriangles_triangles",
102         "dynamic_batches",
103         "dynamic_surfaces",
104         "dynamic_vertices",
105         "dynamic_triangles",
106         "dynamicskeletal_batches",
107         "dynamicskeletal_surfaces",
108         "dynamicskeletal_vertices",
109         "dynamicskeletal_triangles",
110         "dynamic_batches_because_cvar",
111         "dynamic_surfaces_because_cvar",
112         "dynamic_vertices_because_cvar",
113         "dynamic_triangles_because_cvar",
114         "dynamic_batches_because_lightmapvertex",
115         "dynamic_surfaces_because_lightmapvertex",
116         "dynamic_vertices_because_lightmapvertex",
117         "dynamic_triangles_because_lightmapvertex",
118         "dynamic_batches_because_deformvertexes_autosprite",
119         "dynamic_surfaces_because_deformvertexes_autosprite",
120         "dynamic_vertices_because_deformvertexes_autosprite",
121         "dynamic_triangles_because_deformvertexes_autosprite",
122         "dynamic_batches_because_deformvertexes_autosprite2",
123         "dynamic_surfaces_because_deformvertexes_autosprite2",
124         "dynamic_vertices_because_deformvertexes_autosprite2",
125         "dynamic_triangles_because_deformvertexes_autosprite2",
126         "dynamic_batches_because_deformvertexes_normal",
127         "dynamic_surfaces_because_deformvertexes_normal",
128         "dynamic_vertices_because_deformvertexes_normal",
129         "dynamic_triangles_because_deformvertexes_normal",
130         "dynamic_batches_because_deformvertexes_wave",
131         "dynamic_surfaces_because_deformvertexes_wave",
132         "dynamic_vertices_because_deformvertexes_wave",
133         "dynamic_triangles_because_deformvertexes_wave",
134         "dynamic_batches_because_deformvertexes_bulge",
135         "dynamic_surfaces_because_deformvertexes_bulge",
136         "dynamic_vertices_because_deformvertexes_bulge",
137         "dynamic_triangles_because_deformvertexes_bulge",
138         "dynamic_batches_because_deformvertexes_move",
139         "dynamic_surfaces_because_deformvertexes_move",
140         "dynamic_vertices_because_deformvertexes_move",
141         "dynamic_triangles_because_deformvertexes_move",
142         "dynamic_batches_because_tcgen_lightmap",
143         "dynamic_surfaces_because_tcgen_lightmap",
144         "dynamic_vertices_because_tcgen_lightmap",
145         "dynamic_triangles_because_tcgen_lightmap",
146         "dynamic_batches_because_tcgen_vector",
147         "dynamic_surfaces_because_tcgen_vector",
148         "dynamic_vertices_because_tcgen_vector",
149         "dynamic_triangles_because_tcgen_vector",
150         "dynamic_batches_because_tcgen_environment",
151         "dynamic_surfaces_because_tcgen_environment",
152         "dynamic_vertices_because_tcgen_environment",
153         "dynamic_triangles_because_tcgen_environment",
154         "dynamic_batches_because_tcmod_turbulent",
155         "dynamic_surfaces_because_tcmod_turbulent",
156         "dynamic_vertices_because_tcmod_turbulent",
157         "dynamic_triangles_because_tcmod_turbulent",
158         "dynamic_batches_because_nogaps",
159         "dynamic_surfaces_because_nogaps",
160         "dynamic_vertices_because_nogaps",
161         "dynamic_triangles_because_nogaps",
162         "dynamic_batches_because_derived",
163         "dynamic_surfaces_because_derived",
164         "dynamic_vertices_because_derived",
165         "dynamic_triangles_because_derived",
166         "entitycache_count",
167         "entitycache_surfaces",
168         "entitycache_vertices",
169         "entitycache_triangles",
170         "entityanimate_count",
171         "entityanimate_surfaces",
172         "entityanimate_vertices",
173         "entityanimate_triangles",
174         "entityskeletal_count",
175         "entityskeletal_surfaces",
176         "entityskeletal_vertices",
177         "entityskeletal_triangles",
178         "entitystatic_count",
179         "entitystatic_surfaces",
180         "entitystatic_vertices",
181         "entitystatic_triangles",
182         "entitycustom_count",
183         "entitycustom_surfaces",
184         "entitycustom_vertices",
185         "entitycustom_triangles",
186 };
187
188 char r_speeds_timestring[4096];
189 int speedstringcount, r_timereport_active;
190 double r_timereport_temp = 0, r_timereport_current = 0, r_timereport_start = 0;
191 int r_speeds_longestitem = 0;
192
193 void R_TimeReport(const char *desc)
194 {
195         char tempbuf[256];
196         int length;
197         int t;
198
199         if (r_speeds.integer < 2 || !r_timereport_active)
200                 return;
201
202         CHECKGLERROR
203         if (r_speeds.integer == 2)
204                 GL_Finish();
205         CHECKGLERROR
206         r_timereport_temp = r_timereport_current;
207         r_timereport_current = Sys_DirtyTime();
208         t = (int) ((r_timereport_current - r_timereport_temp) * 1000000.0 + 0.5);
209
210         length = dpsnprintf(tempbuf, sizeof(tempbuf), "%8i %s", t, desc);
211         if (length < 0)
212                 length = (int)sizeof(tempbuf) - 1;
213         if (r_speeds_longestitem < length)
214                 r_speeds_longestitem = length;
215         for (;length < r_speeds_longestitem;length++)
216                 tempbuf[length] = ' ';
217         tempbuf[length] = 0;
218
219         if (speedstringcount + length > (vid_conwidth.integer / 8))
220         {
221                 strlcat(r_speeds_timestring, "\n", sizeof(r_speeds_timestring));
222                 speedstringcount = 0;
223         }
224         strlcat(r_speeds_timestring, tempbuf, sizeof(r_speeds_timestring));
225         speedstringcount += length;
226 }
227
228 void R_TimeReport_BeginFrame(void)
229 {
230         speedstringcount = 0;
231         r_speeds_timestring[0] = 0;
232         r_timereport_active = false;
233         memset(&r_refdef.stats, 0, sizeof(r_refdef.stats));
234
235         if (r_speeds.integer >= 2)
236         {
237                 r_timereport_active = true;
238                 r_timereport_start = r_timereport_current = Sys_DirtyTime();
239         }
240 }
241
242 static int R_CountLeafTriangles(const model_t *model, const mleaf_t *leaf)
243 {
244         int i, triangles = 0;
245         for (i = 0;i < leaf->numleafsurfaces;i++)
246                 triangles += model->data_surfaces[leaf->firstleafsurface[i]].num_triangles;
247         return triangles;
248 }
249
250 #define R_SPEEDS_GRAPH_COLORS 8
251 #define R_SPEEDS_GRAPH_TEXTLENGTH 64
252 static float r_speeds_graph_colors[R_SPEEDS_GRAPH_COLORS][4] = {{1, 0, 0, 1}, {0, 1, 0, 1}, {0, 0, 1, 1}, {1, 1, 0, 1}, {0, 1, 1, 1}, {1, 0, 1, 1}, {1, 1, 1, 1}, {1, 0.5f, 0, 1}};
253
254 extern float viewscalefpsadjusted;
255 void R_TimeReport_EndFrame(void)
256 {
257         int j, lines;
258         cl_locnode_t *loc;
259         char string[1024+4096];
260         mleaf_t *viewleaf;
261         static double oldtime = 0;
262
263         r_refdef.stats[r_stat_timedelta] = (int)((host.realtime - oldtime) * 1000000.0);
264         oldtime = host.realtime;
265         r_refdef.stats[r_stat_quality] = (int)(100 * r_refdef.view.quality);
266
267         string[0] = 0;
268         if (r_speeds.integer)
269         {
270                 // put the location name in the r_speeds display as it greatly helps
271                 // when creating loc files
272                 loc = CL_Locs_FindNearest(cl.movement_origin);
273                 viewleaf = (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf) ? r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, r_refdef.view.origin) : NULL;
274                 dpsnprintf(string, sizeof(string),
275 "%6ius time delta %s%s %.3f cl.time%2.4f brightness\n"
276 "%3i renders org:'%+8.2f %+8.2f %+8.2f' dir:'%+2.3f %+2.3f %+2.3f'\n"
277 "%5i viewleaf%5i cluster%3i area%4i brushes%4i surfaces(%7i triangles)\n"
278 "%7i surfaces%7i triangles %5i entities (%7i surfaces%7i triangles)\n"
279 "%5i leafs%5i portals%6i/%6i particles%6i/%6i decals %3i%% quality\n"
280 "%7i lightmap updates (%7i pixels)%8i/%8i framedata\n"
281 "%4i lights%4i clears%4i scissored%7i light%7i shadow%7i dynamic\n"
282 "bouncegrid:%4i lights%6i particles%6i traces%6i hits%6i splats%6i bounces\n"
283 "photon cache efficiency:%6i cached%6i traced%6ianimated\n"
284 "%6i draws%8i vertices%8i triangles bloompixels%8i copied%8i drawn\n"
285 "%3i rendertargets%8i pixels\n"
286 "updated%5i indexbuffers%8i bytes%5i vertexbuffers%8i bytes\n"
287 "animcache%5ib gpuskeletal%7i vertices (%7i with normals)\n"
288 "fastbatch%5i count%5i surfaces%7i vertices %7i triangles\n"
289 "copytris%5i count%5i surfaces%7i vertices %7i triangles\n"
290 "dynamic%5i count%5i surfaces%7i vertices%7i triangles\n"
291 "%s"
292 , r_refdef.stats[r_stat_timedelta], loc ? "Location: " : "", loc ? loc->name : "", cl.time, r_refdef.view.colorscale
293 , r_refdef.stats[r_stat_renders], r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], r_refdef.view.forward[0], r_refdef.view.forward[1], r_refdef.view.forward[2]
294 , viewleaf ? (int)(viewleaf - r_refdef.scene.worldmodel->brush.data_leafs) : -1, viewleaf ? viewleaf->clusterindex : -1, viewleaf ? viewleaf->areaindex : -1, viewleaf ? viewleaf->numleafbrushes : 0, viewleaf ? viewleaf->numleafsurfaces : 0, viewleaf ? R_CountLeafTriangles(r_refdef.scene.worldmodel, viewleaf) : 0
295 , r_refdef.stats[r_stat_world_surfaces], r_refdef.stats[r_stat_world_triangles], r_refdef.stats[r_stat_entities], r_refdef.stats[r_stat_entities_surfaces], r_refdef.stats[r_stat_entities_triangles]
296 , r_refdef.stats[r_stat_world_leafs], r_refdef.stats[r_stat_world_portals], r_refdef.stats[r_stat_particles], cl.num_particles, r_refdef.stats[r_stat_drawndecals], r_refdef.stats[r_stat_totaldecals], r_refdef.stats[r_stat_quality]
297 , r_refdef.stats[r_stat_lightmapupdates], r_refdef.stats[r_stat_lightmapupdatepixels], r_refdef.stats[r_stat_framedatacurrent], r_refdef.stats[r_stat_framedatasize]
298 , r_refdef.stats[r_stat_lights], r_refdef.stats[r_stat_lights_clears], r_refdef.stats[r_stat_lights_scissored], r_refdef.stats[r_stat_lights_lighttriangles], r_refdef.stats[r_stat_lights_shadowtriangles], r_refdef.stats[r_stat_lights_dynamicshadowtriangles]
299 , r_refdef.stats[r_stat_bouncegrid_lights], r_refdef.stats[r_stat_bouncegrid_particles], r_refdef.stats[r_stat_bouncegrid_traces], r_refdef.stats[r_stat_bouncegrid_hits], r_refdef.stats[r_stat_bouncegrid_splats], r_refdef.stats[r_stat_bouncegrid_bounces]
300 , r_refdef.stats[r_stat_photoncache_cached], r_refdef.stats[r_stat_photoncache_traced], r_refdef.stats[r_stat_photoncache_animated]
301 , r_refdef.stats[r_stat_draws], r_refdef.stats[r_stat_draws_vertices], r_refdef.stats[r_stat_draws_elements] / 3, r_refdef.stats[r_stat_bloom_copypixels], r_refdef.stats[r_stat_bloom_drawpixels]
302 , r_refdef.stats[r_stat_rendertargets_used], r_refdef.stats[r_stat_rendertargets_pixels]
303 , r_refdef.stats[r_stat_indexbufferuploadcount], r_refdef.stats[r_stat_indexbufferuploadsize], r_refdef.stats[r_stat_vertexbufferuploadcount], r_refdef.stats[r_stat_vertexbufferuploadsize]
304 , r_refdef.stats[r_stat_animcache_skeletal_bones], r_refdef.stats[r_stat_animcache_shape_vertices], r_refdef.stats[r_stat_animcache_shade_vertices]
305 , r_refdef.stats[r_stat_batch_fast_batches], r_refdef.stats[r_stat_batch_fast_surfaces], r_refdef.stats[r_stat_batch_fast_vertices], r_refdef.stats[r_stat_batch_fast_triangles]
306 , r_refdef.stats[r_stat_batch_copytriangles_batches], r_refdef.stats[r_stat_batch_copytriangles_surfaces], r_refdef.stats[r_stat_batch_copytriangles_vertices], r_refdef.stats[r_stat_batch_copytriangles_triangles]
307 , r_refdef.stats[r_stat_batch_dynamic_batches], r_refdef.stats[r_stat_batch_dynamic_surfaces], r_refdef.stats[r_stat_batch_dynamic_vertices], r_refdef.stats[r_stat_batch_dynamic_triangles]
308 , r_speeds_timestring);
309         }
310
311         speedstringcount = 0;
312         r_speeds_timestring[0] = 0;
313         r_timereport_active = false;
314
315         if (r_speeds.integer >= 2)
316         {
317                 r_timereport_active = true;
318                 r_timereport_start = r_timereport_current = Sys_DirtyTime();
319         }
320
321         if (string[0])
322         {
323                 int i, y;
324                 if (string[strlen(string)-1] == '\n')
325                         string[strlen(string)-1] = 0;
326                 lines = 1;
327                 for (i = 0;string[i];i++)
328                         if (string[i] == '\n')
329                                 lines++;
330                 y = vid_conheight.integer - sb_lines - lines * 8;
331                 i = j = 0;
332                 r_draw2d_force = true;
333                 DrawQ_Fill(0, y, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0);
334                 while (string[i])
335                 {
336                         j = i;
337                         while (string[i] && string[i] != '\n')
338                                 i++;
339                         if (i - j > 0)
340                                 DrawQ_String(0, y, string + j, i - j, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);
341                         if (string[i] == '\n')
342                                 i++;
343                         y += 8;
344                 }
345                 r_draw2d_force = false;
346         }
347
348         if (r_speeds_graph_length.integer != bound(4, r_speeds_graph_length.integer, 8192))
349                 Cvar_SetValueQuick(&r_speeds_graph_length, bound(4, r_speeds_graph_length.integer, 8192));
350         if (fabs(r_speeds_graph_seconds.value - bound(0.1f, r_speeds_graph_seconds.value, 120.0f)) > 0.01f)
351                 Cvar_SetValueQuick(&r_speeds_graph_seconds, bound(0.1f, r_speeds_graph_seconds.value, 120.0f));
352         if (r_speeds_graph.integer)
353         {
354                 // if we currently have no graph data, reset the graph data entirely
355                 int i;
356                 if (!cls.r_speeds_graph_data)
357                         for (i = 0;i < r_stat_count;i++)
358                                 cls.r_speeds_graph_datamin[i] = cls.r_speeds_graph_datamax[i] = 0;
359                 if (cls.r_speeds_graph_length != r_speeds_graph_length.integer)
360                 {
361                         int stat, index, d, graph_length, *graph_data;
362                         cls.r_speeds_graph_length = r_speeds_graph_length.integer;
363                         cls.r_speeds_graph_current = 0;
364                         if (cls.r_speeds_graph_data)
365                                 Mem_Free(cls.r_speeds_graph_data);
366                         cls.r_speeds_graph_data = (int *)Mem_Alloc(cls.permanentmempool, cls.r_speeds_graph_length * sizeof(r_refdef.stats));
367                         // initialize the graph to have the current values throughout history
368                         graph_data = cls.r_speeds_graph_data;
369                         graph_length = cls.r_speeds_graph_length;
370                         index = 0;
371                         for (stat = 0;stat < r_stat_count;stat++)
372                         {
373                                 d = r_refdef.stats[stat];
374                                 if (stat == r_stat_timedelta)
375                                         d = 0;
376                                 for (i = 0;i < graph_length;i++)
377                                         graph_data[index++] = d;
378                         }
379                 }
380         }
381         else
382         {
383                 if (cls.r_speeds_graph_length)
384                 {
385                         cls.r_speeds_graph_length = 0;
386                         Mem_Free(cls.r_speeds_graph_data);
387                         cls.r_speeds_graph_data = NULL;
388                         cls.r_speeds_graph_current = 0;
389                 }
390         }
391
392         if (cls.r_speeds_graph_length)
393         {
394                 char legend[128];
395                 int i;
396                 const int *data;
397                 float x, y, width, height, scalex, scaley;
398                 int range_default = max(r_speeds_graph_maxdefault.integer, 1);
399                 int color, stat, stats, index, range_min, range_max;
400                 int graph_current, graph_length, *graph_data;
401                 int statindex[R_SPEEDS_GRAPH_COLORS];
402                 int sum;
403
404                 // add current stats to the graph_data
405                 cls.r_speeds_graph_current++;
406                 if (cls.r_speeds_graph_current >= cls.r_speeds_graph_length)
407                         cls.r_speeds_graph_current = 0;
408                 // poke each new stat into the current offset of its graph
409                 graph_data = cls.r_speeds_graph_data;
410                 graph_current = cls.r_speeds_graph_current;
411                 graph_length = cls.r_speeds_graph_length;
412                 for (stat = 0;stat < r_stat_count;stat++)
413                         graph_data[stat * graph_length + graph_current] = r_refdef.stats[stat];
414
415                 // update the graph ranges
416                 for (stat = 0;stat < r_stat_count;stat++)
417                 {
418                         if (cls.r_speeds_graph_datamin[stat] > r_refdef.stats[stat])
419                                 cls.r_speeds_graph_datamin[stat] = r_refdef.stats[stat];
420                         if (cls.r_speeds_graph_datamax[stat] < r_refdef.stats[stat])
421                                 cls.r_speeds_graph_datamax[stat] = r_refdef.stats[stat];
422                 }
423
424                 // force 2D drawing to occur even if r_render is 0
425                 r_draw2d_force = true;
426
427                 // position the graph
428                 width = r_speeds_graph_width.value;
429                 height = r_speeds_graph_height.value;
430                 x = bound(0, r_speeds_graph_x.value, vid_conwidth.value - width);
431                 y = bound(0, r_speeds_graph_y.value, vid_conheight.value - height);
432
433                 // fill background with a pattern of gray and black at one second intervals
434                 scalex = (float)width / (float)r_speeds_graph_seconds.value;
435                 for (i = 0;i < r_speeds_graph_seconds.integer + 1;i++)
436                 {
437                         float x1 = x + width - (i + 1) * scalex;
438                         float x2 = x + width - i * scalex;
439                         if (x1 < x)
440                                 x1 = x;
441                         if (i & 1)
442                                 DrawQ_Fill(x1, y, x2 - x1, height, 0.0f, 0.0f, 0.0f, 0.5f, 0);
443                         else
444                                 DrawQ_Fill(x1, y, x2 - x1, height, 0.2f, 0.2f, 0.2f, 0.5f, 0);
445                 }
446
447                 // count how many stats match our pattern
448                 stats = 0;
449                 color = 0;
450                 for (color = 0;color < R_SPEEDS_GRAPH_COLORS;color++)
451                 {
452                         // look at all stat names and find ones matching the filter
453                         statindex[color] = -1;
454                         if (!r_speeds_graph_filter[color].string)
455                                 continue;
456                         for (stat = 0;stat < r_stat_count;stat++)
457                                 if (!strcmp(r_stat_name[stat], r_speeds_graph_filter[color].string))
458                                         break;
459                         if (stat >= r_stat_count)
460                                 continue;
461                         // record that this color is this stat for the line drawing loop
462                         statindex[color] = stat;
463                         // draw the legend text in the background of the graph
464                         dpsnprintf(legend, sizeof(legend), "%10i :%s", graph_data[stat * graph_length + graph_current], r_stat_name[stat]);
465                         DrawQ_String(x, y + stats * 8, legend, 0, 8, 8, r_speeds_graph_colors[color][0], r_speeds_graph_colors[color][1], r_speeds_graph_colors[color][2], r_speeds_graph_colors[color][3] * 1.00f, 0, NULL, true, FONT_DEFAULT);
466                         // count how many stats we need to graph in vertex buffer
467                         stats++;
468                 }
469
470                 if (stats)
471                 {
472                         // legend text is drawn after the graphs
473                         // render the graph lines, we'll go back and render the legend text later
474                         scalex = (float)width / (1000000.0 * r_speeds_graph_seconds.value);
475                         stats = 0;
476                         for (color = 0;color < R_SPEEDS_GRAPH_COLORS;color++)
477                         {
478                                 // look at all stat names and find ones matching the filter
479                                 stat = statindex[color];
480                                 if (stat < 0)
481                                         continue;
482                                 // prefer to graph stats with 0 base, but if they are
483                                 // negative we have no choice
484                                 range_min = cls.r_speeds_graph_datamin[stat];
485                                 range_max = max(cls.r_speeds_graph_datamax[stat], range_min + range_default);
486                                 // some stats we specifically override the graph scale on
487                                 if (stat == r_stat_timedelta)
488                                         range_max = r_speeds_graph_maxtimedelta.integer;
489                                 scaley = height / (range_max - range_min);
490                                 // generate lines (2 vertices each)
491                                 // to deal with incomplete data we walk right to left
492                                 data = graph_data + stat * graph_length;
493                                 index = graph_current;
494                                 sum = 0;
495                                 for (i = 0;i < graph_length - 1;)
496                                 {
497                                         float x1, y1, x2, y2;
498                                         x1 = max(x, x + width - sum * scalex);
499                                         y1 = y + height - (data[index] - range_min) * scaley;
500                                         sum += graph_data[r_stat_timedelta * graph_length + index];
501                                         index--;
502                                         if (index < 0)
503                                                 index = graph_length - 1;
504                                         i++;
505                                         x2 = max(x, x + width - sum * scalex);
506                                         y2 = y + height - (data[index] - range_min) * scaley;
507                                         DrawQ_Line(1, x1, y1, x2, y2, r_speeds_graph_colors[color][0], r_speeds_graph_colors[color][1], r_speeds_graph_colors[color][2], r_speeds_graph_colors[color][3], 0);
508                                 }
509                         }
510                 }
511
512                 // return to not drawing anything if r_render is 0
513                 r_draw2d_force = false;
514         }
515
516         memset(&r_refdef.stats, 0, sizeof(r_refdef.stats));
517 }