+
+ if (r_speeds_graph_length.integer != bound(4, r_speeds_graph_length.integer, 8192))
+ Cvar_SetValueQuick(&r_speeds_graph_length, bound(4, r_speeds_graph_length.integer, 8192));
+ if (fabs(r_speeds_graph_seconds.value - bound(0.1f, r_speeds_graph_seconds.value, 120.0f)) > 0.01f)
+ Cvar_SetValueQuick(&r_speeds_graph_seconds, bound(0.1f, r_speeds_graph_seconds.value, 120.0f));
+ if (r_speeds_graph.integer)
+ {
+ // if we currently have no graph data, reset the graph data entirely
+ if (!cls.r_speeds_graph_data)
+ for (i = 0;i < r_stat_count;i++)
+ cls.r_speeds_graph_datamin[i] = cls.r_speeds_graph_datamax[i] = r_refdef.stats[i];
+ if (cls.r_speeds_graph_length != r_speeds_graph_length.integer)
+ {
+ int i, stat, index, d, graph_length, *graph_data;
+ cls.r_speeds_graph_length = r_speeds_graph_length.integer;
+ cls.r_speeds_graph_current = 0;
+ if (cls.r_speeds_graph_data)
+ Mem_Free(cls.r_speeds_graph_data);
+ cls.r_speeds_graph_data = Mem_Alloc(cls.permanentmempool, cls.r_speeds_graph_length * sizeof(r_refdef.stats));
+ // initialize the graph to have the current values throughout history
+ graph_data = cls.r_speeds_graph_data;
+ graph_length = cls.r_speeds_graph_length;
+ index = 0;
+ for (stat = 0;stat < r_stat_count;stat++)
+ {
+ d = r_refdef.stats[stat];
+ if (stat == r_stat_timedelta)
+ d = 0;
+ for (i = 0;i < graph_length;i++)
+ graph_data[index++] = d;
+ }
+ }
+ }
+ else
+ {
+ if (cls.r_speeds_graph_length)
+ {
+ cls.r_speeds_graph_length = 0;
+ Mem_Free(cls.r_speeds_graph_data);
+ cls.r_speeds_graph_data = NULL;
+ cls.r_speeds_graph_current = 0;
+ }
+ }
+
+ if (cls.r_speeds_graph_length)
+ {
+ char legend[128];
+ r_vertexgeneric_t *v;
+ int numlines;
+ const int *data;
+ float x, y, width, height, scalex, scaley;
+ int color, stat, stats, index, range_min, range_max;
+ int graph_current, graph_length, *graph_data;
+ int statindex[R_SPEEDS_GRAPH_COLORS];
+ int frameslastsecond;
+ int sum;
+
+ // add current stats to the graph_data
+ cls.r_speeds_graph_current++;
+ if (cls.r_speeds_graph_current >= cls.r_speeds_graph_length)
+ cls.r_speeds_graph_current = 0;
+ // poke each new stat into the current offset of its graph
+ graph_data = cls.r_speeds_graph_data;
+ graph_current = cls.r_speeds_graph_current;
+ graph_length = cls.r_speeds_graph_length;
+ for (stat = 0;stat < r_stat_count;stat++)
+ graph_data[stat * graph_length + graph_current] = r_refdef.stats[stat];
+
+ // update the graph ranges
+ for (stat = 0;stat < r_stat_count;stat++)
+ {
+ if (cls.r_speeds_graph_datamin[stat] > r_refdef.stats[stat])
+ cls.r_speeds_graph_datamin[stat] = r_refdef.stats[stat];
+ if (cls.r_speeds_graph_datamax[stat] < r_refdef.stats[stat])
+ cls.r_speeds_graph_datamax[stat] = r_refdef.stats[stat];
+ }
+
+ // force 2D drawing to occur even if r_render is 0
+ r_draw2d_force = true;
+
+ // position the graph
+ width = r_speeds_graph_width.value;
+ height = r_speeds_graph_height.value;
+ x = bound(0, r_speeds_graph_x.value, vid_conwidth.value - width);
+ y = bound(0, r_speeds_graph_y.value, vid_conheight.value - height);
+
+ // count how many frames were in the last second
+ data = graph_data + r_stat_timedelta * graph_length;
+ index = graph_current;
+ sum = 0;
+ for (i = 0;i < graph_length;i++)
+ {
+ sum += data[index];
+ if (sum >= 1000000)
+ break;
+ index--;
+ if (index < 0)
+ index = graph_length - 1;
+ }
+ frameslastsecond = i;
+
+ // fill background with a pattern of gray and black at one second intervals
+ scalex = (float)width / (float)r_speeds_graph_seconds.value;
+ for (i = 0;i < r_speeds_graph_seconds.integer + 1;i++)
+ {
+ float x1 = x + width - (i + 1) * scalex;
+ float x2 = x + width - i * scalex;
+ if (x1 < x)
+ x1 = x;
+ if (i & 1)
+ DrawQ_Fill(x1, y, x2 - x1, height, 0.0f, 0.0f, 0.0f, 0.5f, 0);
+ else
+ DrawQ_Fill(x1, y, x2 - x1, height, 0.2f, 0.2f, 0.2f, 0.5f, 0);
+ }
+
+ // count how many stats match our pattern
+ stats = 0;
+ color = 0;
+ for (color = 0;color < R_SPEEDS_GRAPH_COLORS;color++)
+ {
+ // look at all stat names and find ones matching the filter
+ statindex[color] = -1;
+ if (!r_speeds_graph_filter[color].string)
+ continue;
+ for (stat = 0;stat < r_stat_count;stat++)
+ if (!strcmp(r_stat_name[stat], r_speeds_graph_filter[color].string))
+ break;
+ if (stat >= r_stat_count)
+ continue;
+ // record that this color is this stat for the line drawing loop
+ statindex[color] = stat;
+ // draw the legend text in the background of the graph
+ dpsnprintf(legend, sizeof(legend), "%10i :%s", graph_data[stat * graph_length + graph_current], r_stat_name[stat]);
+ 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);
+ // count how many stats we need to graph in vertex buffer
+ stats++;
+ }
+ dpsnprintf(legend, sizeof(legend), "%10i frames last second", frameslastsecond);
+ DrawQ_String(x, y + stats * 8, legend, 0, 8, 8, 0.5f, 0.5f, 0.5f, 0.5f, 0, NULL, true, FONT_DEFAULT);
+
+ if (stats)
+ {
+ // legend text is drawn after the graphs
+ // render the graph lines, we'll go back and render the legend text later
+ scalex = (float)width / (1000000.0 * r_speeds_graph_seconds.value);
+ // get space in a vertex buffer to draw this
+ numlines = stats * (graph_length - 1);
+ v = R_Mesh_PrepareVertices_Generic_Lock(numlines * 2);
+ stats = 0;
+ for (color = 0;color < R_SPEEDS_GRAPH_COLORS;color++)
+ {
+ // look at all stat names and find ones matching the filter
+ stat = statindex[color];
+ if (stat < 0)
+ continue;
+ // prefer to graph stats with 0 base, but if they are
+ // negative we have no choice
+ range_min = min(cls.r_speeds_graph_datamin[stat], 0);
+ range_max = cls.r_speeds_graph_datamax[stat];
+ // some stats we specifically override the graph scale on
+ if (stat == r_stat_timedelta)
+ range_max = 100000;
+ if (range_max == range_min)
+ range_max++;
+ scaley = height / (range_max - range_min);
+ // generate lines (2 vertices each)
+ // to deal with incomplete data we walk right to left
+ data = graph_data + stat * graph_length;
+ index = graph_current;
+ sum = 0;
+ for (i = 0;i < graph_length - 1;)
+ {
+ v->vertex3f[0] = x + width - sum * scalex;
+ if (v->vertex3f[0] < x)
+ v->vertex3f[0] = x;
+ v->vertex3f[1] = y + height - (data[index] - range_min) * scaley;
+ v->vertex3f[2] = 0;
+ v->color4f[0] = r_speeds_graph_colors[color][0];
+ v->color4f[1] = r_speeds_graph_colors[color][1];
+ v->color4f[2] = r_speeds_graph_colors[color][2];
+ v->color4f[3] = r_speeds_graph_colors[color][3];
+ v->texcoord2f[0] = 0;
+ v->texcoord2f[1] = 0;
+ v++;
+ sum += graph_data[r_stat_timedelta * graph_length + index];
+ index--;
+ if (index < 0)
+ index = graph_length - 1;
+ i++;
+ v->vertex3f[0] = x + width - sum * scalex;
+ if (v->vertex3f[0] < x)
+ v->vertex3f[0] = x;
+ v->vertex3f[1] = y + height - (data[index] - range_min) * scaley;
+ v->vertex3f[2] = 0;
+ v->color4f[0] = r_speeds_graph_colors[color][0];
+ v->color4f[1] = r_speeds_graph_colors[color][1];
+ v->color4f[2] = r_speeds_graph_colors[color][2];
+ v->color4f[3] = r_speeds_graph_colors[color][3];
+ v->texcoord2f[0] = 0;
+ v->texcoord2f[1] = 0;
+ v++;
+ }
+ }
+ R_Mesh_PrepareVertices_Generic_Unlock();
+ DrawQ_Lines(0.0f, numlines, 0, false);
+ }
+
+ // return to not drawing anything if r_render is 0
+ r_draw2d_force = false;
+ }
+
+ memset(&r_refdef.stats, 0, sizeof(r_refdef.stats));