LookingGlass/client/src/overlay/graphs.c
2024-02-01 17:16:31 +11:00

277 lines
6.0 KiB
C

/**
* Looking Glass
* Copyright © 2017-2024 The Looking Glass Authors
* https://looking-glass.io
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "interface/overlay.h"
#include "cimgui.h"
#include "../main.h"
#include "common/debug.h"
#include "overlay_utils.h"
struct GraphState
{
bool show;
struct ll * graphs;
};
static struct GraphState gs = {0};
struct OverlayGraph
{
const char * name;
RingBuffer buffer;
bool enabled;
float min;
float max;
GraphFormatFn formatFn;
};
static void configCallback(void * udata, int * id)
{
igCheckbox("Show timing graphs", &gs.show);
igSeparator();
igBeginTable("split", 2, 0, (ImVec2){}, 0);
GraphHandle graph;
ll_lock(gs.graphs);
ll_forEachNL(gs.graphs, item, graph)
{
igTableNextColumn();
igCheckbox(graph->name, &graph->enabled);
}
ll_unlock(gs.graphs);
igEndTable();
}
static void showTimingKeybind(int sc, void * opaque)
{
gs.show ^= true;
app_invalidateWindow(false);
}
static void graphs_earlyInit(void)
{
gs.graphs = ll_new();
}
static bool graphs_init(void ** udata, const void * params)
{
app_overlayConfigRegister("Performance Metrics", configCallback, NULL);
app_registerKeybind(0, 'T', showTimingKeybind, NULL,
"Show frame timing information");
return true;
}
static void graphs_free(void * udata)
{
struct OverlayGraph * graph;
while(ll_shift(gs.graphs, (void **)&graph))
free(graph);
ll_free(gs.graphs);
gs.graphs = NULL;
}
struct BufferMetrics
{
float min;
float max;
float sum;
float avg;
float freq;
float last;
};
static bool rbCalcMetrics(int index, void * value_, void * udata_)
{
float * value = value_;
struct BufferMetrics * udata = udata_;
if (index == 0)
{
udata->min = *value;
udata->max = *value;
udata->sum = *value;
return true;
}
if (udata->min > *value)
udata->min = *value;
if (udata->max < *value)
udata->max = *value;
udata->sum += *value;
udata->last = *value;
return true;
}
static int graphs_render(void * udata, bool interactive,
struct Rect * windowRects, int maxRects)
{
if (!gs.show)
return 0;
float fontSize = igGetFontSize();
GraphHandle graph;
int graphCount = 0;
ll_lock(gs.graphs);
ll_forEachNL(gs.graphs, item, graph)
{
if (graph->enabled)
++graphCount;
}
if (!graphCount)
{
ll_unlock(gs.graphs);
return 0;
}
ImVec2 pos = {0.0f, 0.0f};
igSetNextWindowBgAlpha(0.4f);
igSetNextWindowPos(pos, ImGuiCond_FirstUseEver, pos);
igSetNextWindowSize(
(ImVec2){
28.0f * fontSize,
7.0f * fontSize * graphCount
},
ImGuiCond_FirstUseEver);
ImGuiWindowFlags flags = ImGuiWindowFlags_NoNav;
if (!interactive)
flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDecoration;
igBegin("Performance Metrics", NULL, flags);
ImVec2 winSize;
igGetContentRegionAvail(&winSize);
const float height = (winSize.y / graphCount)
- igGetStyle()->ItemSpacing.y;
ll_forEachNL(gs.graphs, item, graph)
{
if (!graph->enabled)
continue;
struct BufferMetrics metrics = {};
ringbuffer_forEach(graph->buffer, rbCalcMetrics, &metrics, false);
if (metrics.sum > 0.0f)
{
metrics.avg = metrics.sum / ringbuffer_getCount(graph->buffer);
metrics.freq = 1000.0f / metrics.avg;
}
const char * title;
if (graph->formatFn)
title = graph->formatFn(graph->name,
metrics.min, metrics.max, metrics.avg, metrics.freq, metrics.last);
else
{
static char _title[64];
snprintf(_title, sizeof(_title),
"%s: min:%4.2f max:%4.2f avg:%4.2f/%4.2fHz",
graph->name, metrics.min, metrics.max, metrics.avg, metrics.freq);
title = _title;
}
igPlotLines_FloatPtr(
"",
(float *)ringbuffer_getValues(graph->buffer),
ringbuffer_getLength(graph->buffer),
ringbuffer_getStart (graph->buffer),
title,
graph->min,
graph->max,
(ImVec2){ winSize.x, height },
sizeof(float));
};
ll_unlock(gs.graphs);
overlayGetImGuiRect(windowRects);
igEnd();
return 1;
}
struct LG_OverlayOps LGOverlayGraphs =
{
.name = "Graphs",
.earlyInit = graphs_earlyInit,
.init = graphs_init,
.free = graphs_free,
.render = graphs_render
};
GraphHandle overlayGraph_register(const char * name, RingBuffer buffer,
float min, float max, GraphFormatFn formatFn)
{
struct OverlayGraph * graph = malloc(sizeof(*graph));
if (!graph)
{
DEBUG_ERROR("out of memory");
return NULL;
}
graph->name = name;
graph->buffer = buffer;
graph->enabled = true;
graph->min = min;
graph->max = max;
graph->formatFn = formatFn;
ll_push(gs.graphs, graph);
return graph;
}
void overlayGraph_unregister(GraphHandle handle)
{
if (!gs.graphs)
return;
ll_removeData(gs.graphs, handle);
free(handle);
if (gs.show)
app_invalidateWindow(false);
}
void overlayGraph_iterate(void (*callback)(GraphHandle handle, const char * name,
bool * enabled, void * udata), void * udata)
{
GraphHandle graph;
ll_lock(gs.graphs);
ll_forEachNL(gs.graphs, item, graph)
callback(graph, graph->name, &graph->enabled, udata);
ll_unlock(gs.graphs);
}
void overlayGraph_invalidate(GraphHandle handle)
{
if (!gs.show)
return;
if (handle->enabled)
app_invalidateWindow(false);
}