mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-04 06:12:04 +00:00
[client] add ImPlot and rework latency graphs
This commit is contained in:
@@ -917,6 +917,16 @@ void app_invalidateGraph(GraphHandle handle)
|
||||
overlayGraph_invalidate(handle);
|
||||
}
|
||||
|
||||
void app_setGraphCompact(GraphHandle handle, bool compact)
|
||||
{
|
||||
overlayGraph_setCompact(handle, compact);
|
||||
}
|
||||
|
||||
void app_setFrameImportTime(uint64_t time)
|
||||
{
|
||||
g_state.frameImportTime = time;
|
||||
}
|
||||
|
||||
void app_registerOverlay(const struct LG_OverlayOps * ops, const void * params)
|
||||
{
|
||||
ASSERT_LG_OVERLAY_VALID(ops);
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#endif
|
||||
#define STBTT_STATIC
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include "repos/cimgui/imgui/imstb_truetype.h"
|
||||
#include "repos/gui/cimgui/imgui/imstb_truetype.h"
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
@@ -202,18 +202,73 @@ static bool tickTimerFn(void * unused)
|
||||
struct RenderTiming
|
||||
{
|
||||
uint64_t renderStart;
|
||||
uint64_t producerTime;
|
||||
uint64_t captureTime;
|
||||
uint64_t postProcessTime;
|
||||
uint64_t copyTime;
|
||||
uint64_t importTime;
|
||||
};
|
||||
|
||||
static struct RenderTiming frameTimingLoad(void)
|
||||
{
|
||||
struct RenderTiming timing = {};
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const unsigned sequence = atomic_load_explicit(
|
||||
&g_state.frameTimingSequence, memory_order_seq_cst);
|
||||
if (sequence & 1)
|
||||
continue;
|
||||
|
||||
timing.captureTime = atomic_load_explicit(
|
||||
&g_state.producerCaptureTime, memory_order_seq_cst);
|
||||
timing.postProcessTime = atomic_load_explicit(
|
||||
&g_state.producerPostProcessTime, memory_order_seq_cst);
|
||||
timing.copyTime = atomic_load_explicit(
|
||||
&g_state.producerCopyTime, memory_order_seq_cst);
|
||||
timing.importTime = atomic_load_explicit(
|
||||
&g_state.clientImportTime, memory_order_seq_cst);
|
||||
|
||||
if (sequence == atomic_load_explicit(
|
||||
&g_state.frameTimingSequence, memory_order_seq_cst))
|
||||
return timing;
|
||||
}
|
||||
}
|
||||
|
||||
static void frameTimingStore(const LG_TransportFrameTiming * timing,
|
||||
uint64_t importTime)
|
||||
{
|
||||
atomic_fetch_add_explicit(
|
||||
&g_state.frameTimingSequence, 1, memory_order_seq_cst);
|
||||
atomic_store_explicit(&g_state.producerCaptureTime,
|
||||
timing->captureTime, memory_order_seq_cst);
|
||||
atomic_store_explicit(&g_state.producerPostProcessTime,
|
||||
timing->postProcessTime, memory_order_seq_cst);
|
||||
atomic_store_explicit(&g_state.producerCopyTime,
|
||||
timing->copyTime, memory_order_seq_cst);
|
||||
atomic_store_explicit(&g_state.clientImportTime,
|
||||
importTime, memory_order_seq_cst);
|
||||
atomic_fetch_add_explicit(
|
||||
&g_state.frameTimingSequence, 1, memory_order_seq_cst);
|
||||
}
|
||||
|
||||
static void preSwapCallback(void * udata)
|
||||
{
|
||||
const struct RenderTiming * timing = (const struct RenderTiming *)udata;
|
||||
const uint64_t renderTime = nanotime() - timing->renderStart;
|
||||
ringbuffer_push(g_state.renderDuration, &(float) {renderTime * 1e-6f});
|
||||
|
||||
if (timing->producerTime)
|
||||
ringbuffer_push(g_state.endToEndDuration,
|
||||
&(float) {(timing->producerTime + renderTime) * 1e-6f});
|
||||
const uint64_t timestamp = nanotime();
|
||||
const uint64_t renderTime = timestamp - timing->renderStart;
|
||||
if (timing->captureTime || timing->postProcessTime || timing->copyTime ||
|
||||
timing->importTime)
|
||||
{
|
||||
const OverlayFrameTiming frameTiming = {
|
||||
.timestamp = timestamp,
|
||||
.capture = timing->captureTime * 1e-6f,
|
||||
.postProcess = timing->postProcessTime * 1e-6f,
|
||||
.copy = timing->copyTime * 1e-6f,
|
||||
.import = timing->importTime * 1e-6f,
|
||||
.render = renderTime * 1e-6f,
|
||||
};
|
||||
ringbuffer_push(g_state.frameLatency, &frameTiming);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
if (!l_testCapture.enabled || l_testCapture.complete)
|
||||
@@ -375,7 +430,7 @@ static int renderThread(void * unused)
|
||||
.x = g_state.windowScale,
|
||||
.y = g_state.windowScale,
|
||||
};
|
||||
g_state.io->FontGlobalScale = 1.0f / g_state.windowScale;
|
||||
igGetStyle()->FontScaleMain = 1.0f / g_state.windowScale;
|
||||
}
|
||||
|
||||
const bool fontDirty = atomic_exchange(&g_state.fontDirty, false);
|
||||
@@ -407,11 +462,9 @@ static int renderThread(void * unused)
|
||||
|
||||
const bool invalidate = atomic_exchange(&g_state.invalidateWindow, false);
|
||||
|
||||
const struct RenderTiming renderTiming = {
|
||||
.renderStart = nanotime(),
|
||||
.producerTime = newFrame ? atomic_load_explicit(
|
||||
&g_state.producerFrameTime, memory_order_acquire) : 0,
|
||||
};
|
||||
struct RenderTiming renderTiming =
|
||||
newFrame ? frameTimingLoad() : (struct RenderTiming) {};
|
||||
renderTiming.renderStart = nanotime();
|
||||
LG_LOCK(g_state.lgrLock);
|
||||
|
||||
renderQueue_process();
|
||||
@@ -783,6 +836,7 @@ int main_frameThread(void * unused)
|
||||
damageCount = 0;
|
||||
}
|
||||
|
||||
g_state.frameImportTime = 0;
|
||||
if (!RENDERER(onFrame, frame.framebuffer, frame.dmaFD,
|
||||
frame.damageRects, damageCount))
|
||||
{
|
||||
@@ -797,15 +851,7 @@ int main_frameThread(void * unused)
|
||||
g_state.transportOps->getFrameTiming(
|
||||
g_state.transport, &frame, &timing);
|
||||
|
||||
ringbuffer_push(g_state.captureDuration,
|
||||
&(float) {timing.captureTime * 1e-6f});
|
||||
ringbuffer_push(g_state.postProcessDuration,
|
||||
&(float) {timing.postProcessTime * 1e-6f});
|
||||
ringbuffer_push(g_state.copyDuration,
|
||||
&(float) {timing.copyTime * 1e-6f});
|
||||
atomic_store_explicit(&g_state.producerFrameTime,
|
||||
timing.captureTime + timing.postProcessTime + timing.copyTime,
|
||||
memory_order_release);
|
||||
frameTimingStore(&timing, g_state.frameImportTime);
|
||||
|
||||
overlaySplash_show(false);
|
||||
if ((frame.flags & LG_TRANSPORT_FRAME_REQUEST_ACTIVATION) &&
|
||||
@@ -824,13 +870,6 @@ int main_frameThread(void * unused)
|
||||
g_state.autoIdleInhibitState = blockScreensaver;
|
||||
}
|
||||
|
||||
const uint64_t now = nanotime();
|
||||
const uint64_t delta = now - g_state.lastFrameTime;
|
||||
g_state.lastFrameTime = now;
|
||||
if (g_state.lastFrameTimeValid)
|
||||
ringbuffer_push(g_state.uploadTimings, &(float) { delta * 1e-6f });
|
||||
g_state.lastFrameTimeValid = true;
|
||||
|
||||
atomic_fetch_add_explicit(&g_state.frameCount, 1, memory_order_relaxed);
|
||||
#ifdef ENABLE_TESTS
|
||||
atomic_store_explicit(&l_testFrameSerial, frame.serial,
|
||||
@@ -1309,7 +1348,7 @@ static int lg_run(void)
|
||||
|
||||
/* setup imgui */
|
||||
igCreateContext(NULL);
|
||||
g_state.io = igGetIO();
|
||||
g_state.io = igGetIO_Nil();
|
||||
g_state.style = igGetStyle();
|
||||
|
||||
g_state.style->Colors[ImGuiCol_ModalWindowDimBg] = (ImVec4) { 0.0f, 0.0f, 0.0f, 0.4f };
|
||||
@@ -1334,20 +1373,11 @@ static int lg_run(void)
|
||||
DEBUG_INFO("Using font: %s", g_state.fontName);
|
||||
|
||||
// initialize metrics ringbuffers
|
||||
g_state.renderTimings = ringbuffer_new(256, sizeof(float));
|
||||
g_state.uploadTimings = ringbuffer_new(256, sizeof(float));
|
||||
g_state.renderDuration = ringbuffer_new(256, sizeof(float));
|
||||
g_state.captureDuration = ringbuffer_new(256, sizeof(float));
|
||||
g_state.postProcessDuration = ringbuffer_new(256, sizeof(float));
|
||||
g_state.copyDuration = ringbuffer_new(256, sizeof(float));
|
||||
g_state.endToEndDuration = ringbuffer_new(256, sizeof(float));
|
||||
overlayGraph_register("FRAME" , g_state.renderTimings , 0.0f, 50.0f, NULL);
|
||||
overlayGraph_register("UPLOAD" , g_state.uploadTimings , 0.0f, 50.0f, NULL);
|
||||
overlayGraph_register("RENDER" , g_state.renderDuration , 0.0f, 10.0f, NULL);
|
||||
overlayGraph_register("CAPTURE" , g_state.captureDuration , 0.0f, 20.0f, NULL);
|
||||
overlayGraph_register("POST PROCESS", g_state.postProcessDuration, 0.0f, 20.0f, NULL);
|
||||
overlayGraph_register("COPY" , g_state.copyDuration , 0.0f, 20.0f, NULL);
|
||||
overlayGraph_register("END TO END" , g_state.endToEndDuration , 0.0f, 50.0f, NULL);
|
||||
g_state.renderTimings = ringbuffer_new(256, sizeof(float));
|
||||
g_state.frameLatency = ringbuffer_new(4096, sizeof(OverlayFrameTiming));
|
||||
overlayGraph_setCompact(overlayGraph_register(
|
||||
"FRAME", g_state.renderTimings, 0.0f, 50.0f, NULL), true);
|
||||
overlayGraph_registerFrameTiming("FRAME LATENCY", g_state.frameLatency);
|
||||
|
||||
// unknown guest OS at this time
|
||||
g_state.guestOS = LG_TRANSPORT_OS_OTHER;
|
||||
@@ -1830,12 +1860,7 @@ static void lg_shutdown(void)
|
||||
|
||||
// free metrics ringbuffers
|
||||
ringbuffer_free(&g_state.renderTimings);
|
||||
ringbuffer_free(&g_state.uploadTimings);
|
||||
ringbuffer_free(&g_state.renderDuration);
|
||||
ringbuffer_free(&g_state.captureDuration);
|
||||
ringbuffer_free(&g_state.postProcessDuration);
|
||||
ringbuffer_free(&g_state.copyDuration);
|
||||
ringbuffer_free(&g_state.endToEndDuration);
|
||||
ringbuffer_free(&g_state.frameLatency);
|
||||
|
||||
free(g_state.fontName);
|
||||
igDestroyContext(NULL);
|
||||
|
||||
@@ -143,20 +143,18 @@ struct AppState
|
||||
bool formatValid;
|
||||
uint64_t frameTime;
|
||||
uint64_t overlayFrameTime;
|
||||
uint64_t lastFrameTime;
|
||||
bool lastFrameTimeValid;
|
||||
uint64_t lastRenderTime;
|
||||
bool lastRenderTimeValid;
|
||||
RingBuffer renderTimings;
|
||||
RingBuffer renderDuration;
|
||||
RingBuffer uploadTimings;
|
||||
RingBuffer captureDuration;
|
||||
RingBuffer postProcessDuration;
|
||||
RingBuffer copyDuration;
|
||||
RingBuffer endToEndDuration;
|
||||
RingBuffer frameLatency;
|
||||
uint64_t frameImportTime;
|
||||
|
||||
atomic_uint_least64_t pendingCount;
|
||||
atomic_uint_least64_t producerFrameTime;
|
||||
atomic_uint frameTimingSequence;
|
||||
atomic_uint_least64_t producerCaptureTime;
|
||||
atomic_uint_least64_t producerPostProcessTime;
|
||||
atomic_uint_least64_t producerCopyTime;
|
||||
atomic_uint_least64_t clientImportTime;
|
||||
atomic_uint_least64_t renderCount, frameCount;
|
||||
_Atomic(float) fps, ups;
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ static int alert_render(void * udata, bool interactive, struct Rect * windowRect
|
||||
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar
|
||||
);
|
||||
|
||||
igPushFont(g_state.fontLarge);
|
||||
igPushFont(g_state.fontLarge, 0.0f);
|
||||
igText("%s", l_alert.message);
|
||||
igPopFont();
|
||||
|
||||
|
||||
@@ -20,29 +20,48 @@
|
||||
|
||||
#include "interface/overlay.h"
|
||||
#include "cimgui.h"
|
||||
#include "cimplot.h"
|
||||
|
||||
#include "../main.h"
|
||||
#include "../overlays.h"
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/stringutils.h"
|
||||
#include "common/time.h"
|
||||
#include "overlay_utils.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
struct GraphState
|
||||
{
|
||||
bool show;
|
||||
struct ll * graphs;
|
||||
bool show;
|
||||
struct ll * graphs;
|
||||
ImPlotContext * plotContext;
|
||||
ImPlotSpec * plotSpec;
|
||||
};
|
||||
|
||||
static struct GraphState gs = {0};
|
||||
|
||||
enum OverlayGraphType
|
||||
{
|
||||
OVERLAY_GRAPH_LINE,
|
||||
OVERLAY_GRAPH_FRAME_TIMING,
|
||||
};
|
||||
|
||||
#define TIMING_STATISTIC_COUNT 3
|
||||
|
||||
struct OverlayGraph
|
||||
{
|
||||
char * name;
|
||||
RingBuffer buffer;
|
||||
bool enabled;
|
||||
float min;
|
||||
float max;
|
||||
GraphFormatFn formatFn;
|
||||
char * name;
|
||||
RingBuffer buffer;
|
||||
bool enabled;
|
||||
bool compact;
|
||||
enum OverlayGraphType type;
|
||||
float min;
|
||||
float max;
|
||||
float yScale[TIMING_STATISTIC_COUNT];
|
||||
GraphFormatFn formatFn;
|
||||
};
|
||||
|
||||
|
||||
@@ -81,6 +100,23 @@ static void graphs_earlyInit(void)
|
||||
|
||||
static bool graphs_init(void ** udata, const void * params)
|
||||
{
|
||||
gs.plotContext = ImPlot_CreateContext();
|
||||
if (!gs.plotContext)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create ImPlot context");
|
||||
return false;
|
||||
}
|
||||
|
||||
ImPlot_SetImGuiContext(igGetCurrentContext());
|
||||
gs.plotSpec = ImPlotSpec_ImPlotSpec();
|
||||
if (!gs.plotSpec)
|
||||
{
|
||||
DEBUG_ERROR("Failed to create ImPlot specification");
|
||||
ImPlot_DestroyContext(gs.plotContext);
|
||||
gs.plotContext = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
app_overlayConfigRegister("Performance Metrics", configCallback, NULL);
|
||||
app_registerKeybind(KEY_T, showTimingKeybind, NULL,
|
||||
"Show frame timing information");
|
||||
@@ -97,6 +133,11 @@ static void graphs_free(void * udata)
|
||||
}
|
||||
ll_free(gs.graphs);
|
||||
gs.graphs = NULL;
|
||||
|
||||
ImPlotSpec_destroy(gs.plotSpec);
|
||||
gs.plotSpec = NULL;
|
||||
ImPlot_DestroyContext(gs.plotContext);
|
||||
gs.plotContext = NULL;
|
||||
}
|
||||
|
||||
struct BufferMetrics
|
||||
@@ -116,9 +157,10 @@ static bool rbCalcMetrics(int index, void * value_, void * udata_)
|
||||
|
||||
if (index == 0)
|
||||
{
|
||||
udata->min = *value;
|
||||
udata->max = *value;
|
||||
udata->sum = *value;
|
||||
udata->min = *value;
|
||||
udata->max = *value;
|
||||
udata->sum = *value;
|
||||
udata->last = *value;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -128,11 +170,253 @@ static bool rbCalcMetrics(int index, void * value_, void * udata_)
|
||||
if (udata->max < *value)
|
||||
udata->max = *value;
|
||||
|
||||
udata->sum += *value;
|
||||
udata->sum += *value;
|
||||
udata->last = *value;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define TIMING_PLOT_BUCKETS 100
|
||||
#define TIMING_PLOT_WINDOW_NS 20000000000ULL
|
||||
#define TIMING_PLOT_BUCKET_NS \
|
||||
(TIMING_PLOT_WINDOW_NS / TIMING_PLOT_BUCKETS)
|
||||
#define TIMING_STAGE_COUNT 5
|
||||
|
||||
struct TimingPlotData
|
||||
{
|
||||
uint64_t windowStart;
|
||||
uint64_t windowEnd;
|
||||
float stageMin[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS];
|
||||
float stageMax[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS];
|
||||
float stageSum[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS];
|
||||
unsigned count[TIMING_PLOT_BUCKETS];
|
||||
};
|
||||
|
||||
static float roundTimingScale(float value)
|
||||
{
|
||||
value = max(value, 1.0f);
|
||||
const float magnitude = powf(10.0f, floorf(log10f(value)));
|
||||
const float normalized = value / magnitude;
|
||||
const float rounded = normalized <= 1.0f ? 1.0f :
|
||||
normalized <= 1.5f ? 1.5f :
|
||||
normalized <= 2.0f ? 2.0f :
|
||||
normalized <= 3.0f ? 3.0f :
|
||||
normalized <= 5.0f ? 5.0f :
|
||||
normalized <= 7.5f ? 7.5f : 10.0f;
|
||||
return rounded * magnitude;
|
||||
}
|
||||
|
||||
static float graphScale(struct OverlayGraph * graph, int scaleId,
|
||||
float peak)
|
||||
{
|
||||
float scale = graph->yScale[scaleId];
|
||||
if (scale == 0.0f || peak > scale * 0.95f || peak < scale * 0.45f)
|
||||
scale = roundTimingScale(peak * 1.2f);
|
||||
|
||||
graph->yScale[scaleId] = scale;
|
||||
return scale;
|
||||
}
|
||||
|
||||
static float graphRowWeight(const struct OverlayGraph * graph)
|
||||
{
|
||||
if (graph->type == OVERLAY_GRAPH_FRAME_TIMING)
|
||||
return 3.0f;
|
||||
return graph->compact ? 0.5f : 1.0f;
|
||||
}
|
||||
|
||||
static bool accumulateTimingSample(int index, void * value_, void * udata_)
|
||||
{
|
||||
struct TimingPlotData * data = udata_;
|
||||
const OverlayFrameTiming * timing = value_;
|
||||
if (timing->timestamp < data->windowStart)
|
||||
return true;
|
||||
if (timing->timestamp >= data->windowEnd)
|
||||
return false;
|
||||
|
||||
const uint64_t offset = timing->timestamp - data->windowStart;
|
||||
const int bucket = min(offset / TIMING_PLOT_BUCKET_NS,
|
||||
TIMING_PLOT_BUCKETS - 1);
|
||||
const float values[TIMING_STAGE_COUNT] = {
|
||||
timing->capture,
|
||||
timing->postProcess,
|
||||
timing->copy,
|
||||
timing->import,
|
||||
timing->render,
|
||||
};
|
||||
for (int i = 0; i < TIMING_STAGE_COUNT; ++i)
|
||||
{
|
||||
if (!data->count[bucket])
|
||||
data->stageMin[i][bucket] = data->stageMax[i][bucket] = values[i];
|
||||
else
|
||||
{
|
||||
data->stageMin[i][bucket] = min(data->stageMin[i][bucket], values[i]);
|
||||
data->stageMax[i][bucket] = max(data->stageMax[i][bucket], values[i]);
|
||||
}
|
||||
data->stageSum[i][bucket] += values[i];
|
||||
}
|
||||
++data->count[bucket];
|
||||
return true;
|
||||
}
|
||||
|
||||
static ImPlotFlags graphFlags(bool interactive, bool legend)
|
||||
{
|
||||
ImPlotFlags flags = legend ? ImPlotFlags_None : ImPlotFlags_NoLegend;
|
||||
if (!interactive)
|
||||
flags |= ImPlotFlags_NoInputs | ImPlotFlags_NoMenus |
|
||||
ImPlotFlags_NoBoxSelect;
|
||||
return flags;
|
||||
}
|
||||
|
||||
static void renderLineGraph(struct OverlayGraph * graph, bool interactive,
|
||||
ImVec2 size)
|
||||
{
|
||||
struct BufferMetrics metrics = {};
|
||||
ringbuffer_forEach(graph->buffer, rbCalcMetrics, &metrics, false);
|
||||
|
||||
const int count = ringbuffer_getCount(graph->buffer);
|
||||
if (metrics.sum > 0.0f && count > 0)
|
||||
{
|
||||
metrics.avg = metrics.sum / count;
|
||||
metrics.freq = 1000.0f / metrics.avg;
|
||||
}
|
||||
|
||||
const char * description;
|
||||
if (graph->formatFn)
|
||||
description = graph->formatFn(graph->name,
|
||||
metrics.min, metrics.max, metrics.avg, metrics.freq, metrics.last);
|
||||
else
|
||||
{
|
||||
static char text[128];
|
||||
snprintf(text, sizeof(text),
|
||||
"%s: min %.2f, max %.2f, avg %.2f ms / %.2f Hz",
|
||||
graph->name, metrics.min, metrics.max, metrics.avg, metrics.freq);
|
||||
description = text;
|
||||
}
|
||||
|
||||
char title[256];
|
||||
snprintf(title, sizeof(title), "%s###graph_%p", description, (void *)graph);
|
||||
if (!ImPlot_BeginPlot(title, size, graphFlags(interactive, false)))
|
||||
return;
|
||||
|
||||
ImPlot_SetupAxes(NULL, "ms",
|
||||
ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_None);
|
||||
const float valueMax = graphScale(graph, 0,
|
||||
count > 0 ? metrics.max : graph->max);
|
||||
ImPlot_SetupAxesLimits(0.0, max(1, count - 1), graph->min, valueMax,
|
||||
ImPlotCond_Always);
|
||||
|
||||
gs.plotSpec->Offset = ringbuffer_getStart(graph->buffer);
|
||||
gs.plotSpec->Stride = sizeof(float);
|
||||
gs.plotSpec->Flags = ImPlotLineFlags_None;
|
||||
ImPlot_PlotLine_FloatPtrInt(graph->name,
|
||||
ringbuffer_getValues(graph->buffer), count, 1.0, 0.0, *gs.plotSpec);
|
||||
ImPlot_EndPlot();
|
||||
}
|
||||
|
||||
static void renderTimingStatistic(struct OverlayGraph * graph,
|
||||
const char * statistic, int statisticId,
|
||||
const float values[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS],
|
||||
bool interactive, ImVec2 size)
|
||||
{
|
||||
float cumulative[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS] = {};
|
||||
float zero[TIMING_PLOT_BUCKETS] = {};
|
||||
float xValues[TIMING_PLOT_BUCKETS];
|
||||
float peak = 0.0f;
|
||||
for (int bucket = 0; bucket < TIMING_PLOT_BUCKETS; ++bucket)
|
||||
{
|
||||
xValues[bucket] = bucket;
|
||||
for (int stage = 0; stage < TIMING_STAGE_COUNT; ++stage)
|
||||
cumulative[stage][bucket] = values[stage][bucket] +
|
||||
(stage ? cumulative[stage - 1][bucket] : 0.0f);
|
||||
peak = max(peak, cumulative[TIMING_STAGE_COUNT - 1][bucket]);
|
||||
}
|
||||
const float valueMax = graphScale(graph, statisticId, peak);
|
||||
|
||||
char title[160];
|
||||
snprintf(title, sizeof(title), "%s %s###graph_%p_%d",
|
||||
graph->name, statistic, (void *)graph, statisticId);
|
||||
if (!ImPlot_BeginPlot(title, size, graphFlags(interactive, true)))
|
||||
return;
|
||||
|
||||
ImPlot_SetupAxes(NULL, "ms",
|
||||
ImPlotAxisFlags_NoDecorations,
|
||||
ImPlotAxisFlags_None);
|
||||
ImPlot_SetupAxesLimits(-0.5, TIMING_PLOT_BUCKETS - 0.5, 0.0, valueMax,
|
||||
ImPlotCond_Always);
|
||||
ImPlot_SetupLegend(ImPlotLocation_South,
|
||||
ImPlotLegendFlags_Outside | ImPlotLegendFlags_Horizontal);
|
||||
|
||||
const char * labels[TIMING_STAGE_COUNT] = {
|
||||
"Capture",
|
||||
"Post",
|
||||
"Copy",
|
||||
"Import",
|
||||
"Render",
|
||||
};
|
||||
|
||||
gs.plotSpec->Offset = 0;
|
||||
gs.plotSpec->Stride = sizeof(float);
|
||||
for (int stage = 0; stage < TIMING_STAGE_COUNT; ++stage)
|
||||
{
|
||||
const ImVec4 color =
|
||||
ImPlot_GetColormapColor(stage, ImPlotColormap_Deep);
|
||||
gs.plotSpec->FillColor = color;
|
||||
gs.plotSpec->FillAlpha = 0.35f;
|
||||
gs.plotSpec->LineWeight = 1.0f;
|
||||
gs.plotSpec->Flags = ImPlotShadedFlags_None;
|
||||
ImPlot_PlotShaded_FloatPtrFloatPtrFloatPtr(labels[stage], xValues,
|
||||
stage ? cumulative[stage - 1] : zero, cumulative[stage],
|
||||
TIMING_PLOT_BUCKETS, *gs.plotSpec);
|
||||
|
||||
gs.plotSpec->LineColor = color;
|
||||
gs.plotSpec->LineWeight = 1.0f;
|
||||
gs.plotSpec->FillAlpha = 1.0f;
|
||||
gs.plotSpec->Flags = ImPlotLineFlags_None;
|
||||
ImPlot_PlotLine_FloatPtrInt(labels[stage], cumulative[stage],
|
||||
TIMING_PLOT_BUCKETS, 1.0, 0.0, *gs.plotSpec);
|
||||
}
|
||||
|
||||
gs.plotSpec->LineColor = (ImVec4) {0.0f, 0.0f, 0.0f, -1.0f};
|
||||
gs.plotSpec->FillColor = (ImVec4) {0.0f, 0.0f, 0.0f, -1.0f};
|
||||
gs.plotSpec->LineWeight = 1.0f;
|
||||
ImPlot_EndPlot();
|
||||
}
|
||||
|
||||
static void renderTimingGraph(struct OverlayGraph * graph, bool interactive,
|
||||
ImVec2 size)
|
||||
{
|
||||
struct TimingPlotData data = {};
|
||||
data.windowEnd = nanotime() / TIMING_PLOT_BUCKET_NS * TIMING_PLOT_BUCKET_NS;
|
||||
data.windowStart = data.windowEnd > TIMING_PLOT_WINDOW_NS ?
|
||||
data.windowEnd - TIMING_PLOT_WINDOW_NS : 0;
|
||||
ringbuffer_forEach(graph->buffer, accumulateTimingSample, &data, false);
|
||||
|
||||
float minimum[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS] = {};
|
||||
float maximum[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS] = {};
|
||||
float average[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS] = {};
|
||||
for (int bucket = 0; bucket < TIMING_PLOT_BUCKETS; ++bucket)
|
||||
{
|
||||
if (!data.count[bucket])
|
||||
continue;
|
||||
|
||||
for (int stage = 0; stage < TIMING_STAGE_COUNT; ++stage)
|
||||
{
|
||||
minimum[stage][bucket] = data.stageMin[stage][bucket];
|
||||
maximum[stage][bucket] = data.stageMax[stage][bucket];
|
||||
average[stage][bucket] =
|
||||
data.stageSum[stage][bucket] / data.count[bucket];
|
||||
}
|
||||
}
|
||||
|
||||
const float spacing = igGetStyle()->ItemSpacing.y;
|
||||
const float height = (size.y - spacing * 2.0f) / 3.0f;
|
||||
renderTimingStatistic(graph, "MINIMUM", 0, minimum,
|
||||
interactive, (ImVec2) {size.x, height});
|
||||
renderTimingStatistic(graph, "MAXIMUM", 1, maximum,
|
||||
interactive, (ImVec2) {size.x, height});
|
||||
renderTimingStatistic(graph, "AVERAGE", 2, average,
|
||||
interactive, (ImVec2) {size.x, height});
|
||||
}
|
||||
|
||||
static int graphs_render(void * udata, bool interactive,
|
||||
struct Rect * windowRects, int maxRects)
|
||||
{
|
||||
@@ -142,12 +426,16 @@ static int graphs_render(void * udata, bool interactive,
|
||||
float fontSize = igGetFontSize();
|
||||
|
||||
GraphHandle graph;
|
||||
int graphCount = 0;
|
||||
int graphCount = 0;
|
||||
float graphRows = 0.0f;
|
||||
ll_lock(gs.graphs);
|
||||
ll_forEachNL(gs.graphs, item, graph)
|
||||
{
|
||||
if (graph->enabled)
|
||||
{
|
||||
++graphCount;
|
||||
graphRows += graphRowWeight(graph);
|
||||
}
|
||||
}
|
||||
|
||||
if (!graphCount)
|
||||
@@ -162,7 +450,7 @@ static int graphs_render(void * udata, bool interactive,
|
||||
igSetNextWindowSize(
|
||||
(ImVec2){
|
||||
28.0f * fontSize,
|
||||
7.0f * fontSize * graphCount
|
||||
7.0f * fontSize * graphRows
|
||||
},
|
||||
ImGuiCond_FirstUseEver);
|
||||
|
||||
@@ -172,51 +460,27 @@ static int graphs_render(void * udata, bool interactive,
|
||||
|
||||
igBegin("Performance Metrics", NULL, flags);
|
||||
|
||||
ImVec2 winSize;
|
||||
igGetContentRegionAvail(&winSize);
|
||||
const float height = (winSize.y / graphCount)
|
||||
- igGetStyle()->ItemSpacing.y;
|
||||
ImVec2 winSize = igGetContentRegionAvail();
|
||||
const float spacing = igGetStyle()->ItemSpacing.y;
|
||||
const float rowHeight =
|
||||
(winSize.y - spacing * (graphCount - 1)) / graphRows;
|
||||
|
||||
ll_forEachNL(gs.graphs, item, graph)
|
||||
for (int pass = 0; pass < 2; ++pass)
|
||||
{
|
||||
if (!graph->enabled)
|
||||
continue;
|
||||
|
||||
struct BufferMetrics metrics = {};
|
||||
ringbuffer_forEach(graph->buffer, rbCalcMetrics, &metrics, false);
|
||||
|
||||
if (metrics.sum > 0.0f)
|
||||
ll_forEachNL(gs.graphs, item, graph)
|
||||
{
|
||||
metrics.avg = metrics.sum / ringbuffer_getCount(graph->buffer);
|
||||
metrics.freq = 1000.0f / metrics.avg;
|
||||
}
|
||||
if (!graph->enabled || graph->compact != (pass == 1))
|
||||
continue;
|
||||
|
||||
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;
|
||||
igPushID_Ptr(graph);
|
||||
const float height = rowHeight * graphRowWeight(graph);
|
||||
if (graph->type == OVERLAY_GRAPH_FRAME_TIMING)
|
||||
renderTimingGraph(graph, interactive, (ImVec2) {winSize.x, height});
|
||||
else
|
||||
renderLineGraph(graph, interactive, (ImVec2) {winSize.x, height});
|
||||
igPopID();
|
||||
}
|
||||
|
||||
igPushID_Ptr(graph);
|
||||
igPlotLines_FloatPtr(
|
||||
"##graph",
|
||||
(float *)ringbuffer_getValues(graph->buffer),
|
||||
ringbuffer_getLength(graph->buffer),
|
||||
ringbuffer_getStart (graph->buffer),
|
||||
title,
|
||||
graph->min,
|
||||
graph->max,
|
||||
(ImVec2){ winSize.x, height },
|
||||
sizeof(float));
|
||||
igPopID();
|
||||
};
|
||||
}
|
||||
ll_unlock(gs.graphs);
|
||||
|
||||
overlayGetImGuiRect(windowRects);
|
||||
@@ -259,13 +523,26 @@ GraphHandle overlayGraph_register(const char * name, RingBuffer buffer,
|
||||
|
||||
graph->buffer = buffer;
|
||||
graph->enabled = true;
|
||||
graph->compact = false;
|
||||
graph->type = OVERLAY_GRAPH_LINE;
|
||||
graph->min = min;
|
||||
graph->max = max;
|
||||
for (int i = 0; i < TIMING_STATISTIC_COUNT; ++i)
|
||||
graph->yScale[i] = 0.0f;
|
||||
graph->formatFn = formatFn;
|
||||
ll_push(gs.graphs, graph);
|
||||
return graph;
|
||||
}
|
||||
|
||||
GraphHandle overlayGraph_registerFrameTiming(const char * name,
|
||||
RingBuffer buffer)
|
||||
{
|
||||
GraphHandle graph = overlayGraph_register(name, buffer, 0.0f, 0.0f, NULL);
|
||||
if (graph)
|
||||
graph->type = OVERLAY_GRAPH_FRAME_TIMING;
|
||||
return graph;
|
||||
}
|
||||
|
||||
void overlayGraph_unregister(GraphHandle handle)
|
||||
{
|
||||
if (!gs.graphs || !handle)
|
||||
@@ -279,6 +556,12 @@ void overlayGraph_unregister(GraphHandle handle)
|
||||
app_invalidateWindow(false);
|
||||
}
|
||||
|
||||
void overlayGraph_setCompact(GraphHandle handle, bool compact)
|
||||
{
|
||||
if (handle)
|
||||
handle->compact = compact;
|
||||
}
|
||||
|
||||
void overlayGraph_iterate(void (*callback)(GraphHandle handle, const char * name,
|
||||
bool * enabled, void * udata), void * udata)
|
||||
{
|
||||
|
||||
@@ -125,7 +125,7 @@ static int msg_render(void * udata, bool interactive, struct Rect * windowRects,
|
||||
continue;
|
||||
}
|
||||
|
||||
igCalcTextSize(&textSize, line, NULL, false, 0.0);
|
||||
textSize = igCalcTextSize(line, NULL, false, 0.0);
|
||||
igSetCursorPosX((igGetWindowWidth() * 0.5f) - (textSize.x * 0.5f));
|
||||
igText("%s", stringlist_at(msg->lines, i));
|
||||
}
|
||||
@@ -135,7 +135,7 @@ static int msg_render(void * udata, bool interactive, struct Rect * windowRects,
|
||||
bool destroy = false;
|
||||
if (msg->confirm)
|
||||
{
|
||||
igCalcTextSize(&textSize, "Yes", NULL, false, 0.0);
|
||||
textSize = igCalcTextSize("Yes", NULL, false, 0.0);
|
||||
ImGuiStyle * style = igGetStyle();
|
||||
textSize.x += (style->FramePadding.x * 2.0f) * 8.0f;
|
||||
textSize.y += (style->FramePadding.y * 2.0f) * 1.5f;
|
||||
@@ -156,7 +156,7 @@ static int msg_render(void * udata, bool interactive, struct Rect * windowRects,
|
||||
}
|
||||
else
|
||||
{
|
||||
igCalcTextSize(&textSize, "OK", NULL, false, 0.0);
|
||||
textSize = igCalcTextSize("OK", NULL, false, 0.0);
|
||||
ImGuiStyle * style = igGetStyle();
|
||||
textSize.x += (style->FramePadding.x * 2.0f) * 8.0f;
|
||||
textSize.y += (style->FramePadding.y * 2.0f) * 1.5f;
|
||||
|
||||
@@ -122,7 +122,7 @@ static void renderText(ImDrawList * list, int x, int y, ImU32 color,
|
||||
if (textHeight == 0.0f)
|
||||
{
|
||||
const char * tmp = "W";
|
||||
igCalcTextSize(&size, tmp, tmp + 1, false, 0.0f);
|
||||
size = igCalcTextSize(tmp, tmp + 1, false, 0.0f);
|
||||
textHeight = size.y;
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ static void renderText(ImDrawList * list, int x, int y, ImU32 color,
|
||||
const char * text = stringlist_at(lines, topAlign ? i : count - i - 1);
|
||||
const int len = strlen(text);
|
||||
|
||||
igCalcTextSize(&size, text, text + len, false, 0.0f);
|
||||
size = igCalcTextSize(text, text + len, false, 0.0f);
|
||||
ImDrawList_AddText_Vec2(
|
||||
list,
|
||||
(ImVec2){
|
||||
@@ -195,7 +195,7 @@ static int splash_render(void * udata, bool interactive, struct Rect * windowRec
|
||||
|
||||
ImDrawList_AddImage(
|
||||
list,
|
||||
(ImTextureID)l_logo.tex,
|
||||
(ImTextureRef) { ._TexID = (ImTextureID)(uintptr_t)l_logo.tex },
|
||||
(ImVec2){
|
||||
logoRect.x,
|
||||
logoRect.y
|
||||
|
||||
@@ -101,7 +101,7 @@ static int status_render(void * udata, bool interactive, struct Rect * windowRec
|
||||
|
||||
ImDrawList_AddImage(
|
||||
igGetBackgroundDrawList_Nil(),
|
||||
(ImTextureID)img->tex,
|
||||
(ImTextureRef) { ._TexID = (ImTextureID)(uintptr_t)img->tex },
|
||||
(ImVec2){
|
||||
xPos,
|
||||
marginY
|
||||
|
||||
@@ -41,11 +41,8 @@
|
||||
|
||||
void overlayGetImGuiRect(struct Rect * rect)
|
||||
{
|
||||
ImVec2 size;
|
||||
ImVec2 pos;
|
||||
|
||||
igGetWindowPos(&pos);
|
||||
igGetWindowSize(&size);
|
||||
ImVec2 pos = igGetWindowPos();
|
||||
ImVec2 size = igGetWindowSize();
|
||||
|
||||
rect->x = pos.x;
|
||||
rect->y = pos.y;
|
||||
@@ -60,9 +57,8 @@ ImVec2 * overlayGetScreenSize(void)
|
||||
|
||||
static void overlayAddUnderline(ImU32 color)
|
||||
{
|
||||
ImVec2 min, max;
|
||||
igGetItemRectMin(&min);
|
||||
igGetItemRectMax(&max);
|
||||
ImVec2 min = igGetItemRectMin();
|
||||
ImVec2 max = igGetItemRectMax();
|
||||
min.y = max.y;
|
||||
ImDrawList_AddLine(igGetWindowDrawList(), min, max, color, 1.0f);
|
||||
}
|
||||
|
||||
@@ -44,9 +44,23 @@ extern struct LG_OverlayOps LGOverlayStatus;
|
||||
|
||||
void overlayAlert_show(LG_MsgAlert type, const char * fmt, va_list args);
|
||||
|
||||
typedef struct OverlayFrameTiming
|
||||
{
|
||||
uint64_t timestamp;
|
||||
float capture;
|
||||
float postProcess;
|
||||
float copy;
|
||||
float import;
|
||||
float render;
|
||||
}
|
||||
OverlayFrameTiming;
|
||||
|
||||
GraphHandle overlayGraph_register(const char * name, RingBuffer buffer,
|
||||
float min, float max, GraphFormatFn formatFn);
|
||||
GraphHandle overlayGraph_registerFrameTiming(const char * name,
|
||||
RingBuffer buffer);
|
||||
void overlayGraph_unregister(GraphHandle handle);
|
||||
void overlayGraph_setCompact(GraphHandle handle, bool compact);
|
||||
void overlayGraph_iterate(void (*callback)(GraphHandle handle, const char * name,
|
||||
bool * enabled, void * udata), void * udata);
|
||||
void overlayGraph_invalidate(GraphHandle handle);
|
||||
|
||||
@@ -610,7 +610,7 @@ bool util_buildUIFontAtlas(
|
||||
goto done;
|
||||
}
|
||||
|
||||
result = ImFontAtlas_Build(atlas);
|
||||
result = true;
|
||||
|
||||
done:
|
||||
LG_UNLOCK(UIFontLock);
|
||||
|
||||
Reference in New Issue
Block a user