[client] add ImPlot and rework latency graphs

This commit is contained in:
Geoffrey McRae
2026-08-03 14:18:22 +10:00
parent 7533c855c2
commit 4f39270736
28 changed files with 539 additions and 172 deletions

5
.gitmodules vendored
View File

@@ -5,7 +5,7 @@
path = repos/PureSpice
url = https://github.com/gnif/PureSpice
[submodule "repos/cimgui"]
path = repos/cimgui
path = repos/gui/cimgui
url = https://github.com/cimgui/cimgui.git
[submodule "repos/wayland-protocols"]
path = repos/wayland-protocols
@@ -13,3 +13,6 @@
[submodule "repos/nanosvg"]
path = repos/nanosvg
url = https://github.com/memononen/nanosvg.git
[submodule "repos/cimplot"]
path = repos/gui/cimplot
url = https://github.com/cimgui/cimplot.git

View File

@@ -181,18 +181,11 @@ if(ENABLE_AUDIO)
list(APPEND SOURCES src/audio.c)
endif()
# Force cimgui to build as a static library.
set(IMGUI_STATIC "yes" CACHE STRING "Build as a static library")
# Support Unicode codepoints outside the basic multilingual plane.
set(IMGUI_WCHAR32 "yes")
add_definitions("-DCIMGUI_USE_OPENGL2=1")
add_definitions("-DCIMGUI_USE_OPENGL3=1")
add_subdirectory("${PROJECT_TOP}/resources" "${CMAKE_BINARY_DIR}/resources")
add_subdirectory("${PROJECT_TOP}/common" "${CMAKE_BINARY_DIR}/common" )
add_subdirectory("${PROJECT_TOP}/repos/LGMP/lgmp" "${CMAKE_BINARY_DIR}/LGMP" )
add_subdirectory("${PROJECT_TOP}/repos/PureSpice" "${CMAKE_BINARY_DIR}/PureSpice")
add_subdirectory("${PROJECT_TOP}/repos/cimgui" "${CMAKE_BINARY_DIR}/cimgui" EXCLUDE_FROM_ALL)
add_subdirectory("${PROJECT_TOP}/repos/gui" "${CMAKE_BINARY_DIR}/gui" EXCLUDE_FROM_ALL)
add_subdirectory(displayservers)
add_subdirectory(renderers)
@@ -235,7 +228,7 @@ target_link_libraries(looking-glass-client
renderers
transports
audiodevs
cimgui
lg_gui
)
if(ENABLE_TESTS)

View File

@@ -88,6 +88,7 @@ bool waylandPresentationInit(void)
wlWm.photonTimings = ringbuffer_new(256, sizeof(float));
wlWm.photonGraph = app_registerGraph("PHOTON", wlWm.photonTimings,
0.0f, 30.0f, NULL);
app_setGraphCompact(wlWm.photonGraph, true);
wp_presentation_add_listener(wlWm.presentation, &presentationListener, NULL);
}
return true;

View File

@@ -145,6 +145,8 @@ GraphHandle app_registerGraph(const char * name, RingBuffer buffer,
float min, float max, GraphFormatFn formatFn);
void app_unregisterGraph(GraphHandle handle);
void app_invalidateGraph(GraphHandle handle);
void app_setGraphCompact(GraphHandle handle, bool compact);
void app_setFrameImportTime(uint64_t time);
void app_overlayConfigRegister(const char * title,
void (*callback)(void * udata, int * id), void * udata);

View File

@@ -25,7 +25,7 @@
#include "common/types.h"
typedef struct ImVec2 ImVec2;
typedef struct ImVec2_c ImVec2;
typedef struct
{

View File

@@ -104,7 +104,6 @@ add_library(renderer_EGL STATIC
filter_downscale.c
${EGL_SHADER_OBJS}
"${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h"
${PROJECT_TOP}/repos/cimgui/imgui/backends/imgui_impl_opengl3.cpp
)
target_compile_definitions(renderer_EGL PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1 IMGUI_IMPL_OPENGL_ES3)
@@ -113,7 +112,7 @@ target_link_libraries(renderer_EGL
PkgConfig::RENDERER_EGL
lg_common
cimgui
lg_gui
)
if(RENDERER_EGL_OPT_FOUND)
target_link_libraries(renderer_EGL

View File

@@ -36,7 +36,7 @@
#include <GLES3/gl32.h>
#include "cimgui.h"
#include "generator/output/cimgui_impl.h"
#include "cimgui_impl.h"
#include <math.h>
#include <stdlib.h>
@@ -124,9 +124,6 @@ struct Inst
int overlayHistoryCount[DESKTOP_DAMAGE_COUNT];
unsigned int overlayHistoryIdx;
RingBuffer importTimings;
GraphHandle importGraph;
bool showSpice;
int spiceWidth, spiceHeight;
@@ -292,10 +289,6 @@ static bool egl_create(LG_Renderer ** renderer, const LG_RendererParams params,
LG_LOCK_INIT(this->desktopDamageLock);
this->desktopDamage[0].count = -1;
this->importTimings = ringbuffer_new(256, sizeof(float));
this->importGraph = app_registerGraph("IMPORT", this->importTimings,
0.0f, 5.0f, NULL);
*needsOpenGL = false;
return true;
}
@@ -317,8 +310,6 @@ static void egl_deinitialize(LG_Renderer * renderer)
egl_stateInvalidate();
}
ringbuffer_free(&this->importTimings);
egl_desktopFree(&this->desktop);
egl_cursorFree (&this->cursor);
egl_damageFree (&this->damage);
@@ -592,10 +583,8 @@ static void egl_onResize(LG_Renderer * renderer, const int width, const int heig
static bool egl_onFontUpdate(LG_Renderer * renderer)
{
ImGui_ImplOpenGL3_DestroyFontsTexture();
const bool result = ImGui_ImplOpenGL3_CreateFontsTexture();
egl_stateInvalidate();
return result;
return true;
}
static bool egl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cursor,
@@ -759,14 +748,14 @@ static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int d
struct Inst * this = UPCAST(struct Inst, renderer);
egl_stateCheckShared();
uint64_t start = nanotime();
const uint64_t start = nanotime();
if (unlikely(!egl_desktopUpdate(
this->desktop, frame, dmaFd, damageRects, damageRectsCount)))
{
DEBUG_INFO("Failed to to update the desktop");
return false;
}
ringbuffer_push(this->importTimings, &(float){ (nanotime() - start) * 1e-6f });
app_setFrameImportTime(nanotime() - start);
INTERLOCKED_SECTION(this->desktopDamageLock, {
struct DesktopDamage * damage = this->desktopDamage + this->desktopDamageIdx;

View File

@@ -496,10 +496,6 @@ static void configUI(void * opaque, int * id)
bool doMove = false;
ImVec2 window, pos;
igGetWindowPos(&window);
igGetMousePos(&pos);
EGL_Filter ** filters = vector_data(&this->filters);
size_t count = vector_size(&this->filters);
for (size_t i = 0; i < count; ++i)

View File

@@ -8,7 +8,6 @@ pkg_check_modules(RENDERER_OPENGL REQUIRED IMPORTED_TARGET
add_library(renderer_OpenGL STATIC
opengl.c
${PROJECT_TOP}/repos/cimgui/imgui/backends/imgui_impl_opengl2.cpp
)
target_compile_definitions(renderer_OpenGL PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1)
@@ -17,7 +16,7 @@ target_link_libraries(renderer_OpenGL
PkgConfig::RENDERER_OPENGL
lg_common
cimgui
lg_gui
)
target_include_directories(renderer_OpenGL

View File

@@ -29,7 +29,7 @@
#include <GL/gl.h>
#include "cimgui.h"
#include "generator/output/cimgui_impl.h"
#include "cimgui_impl.h"
#include "common/debug.h"
#include "common/option.h"
@@ -318,8 +318,7 @@ void opengl_onResize(LG_Renderer * renderer, const int width, const int height,
static bool opengl_onFontUpdate(LG_Renderer * renderer)
{
ImGui_ImplOpenGL2_DestroyFontsTexture();
return ImGui_ImplOpenGL2_CreateFontsTexture();
return true;
}
bool opengl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cursor,

View File

@@ -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);

View File

@@ -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

View File

@@ -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 };
@@ -1335,19 +1374,10 @@ static int lg_run(void)
// 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.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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -20,28 +20,47 @@
#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;
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;
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
@@ -119,6 +160,7 @@ static bool rbCalcMetrics(int index, void * value_, void * udata_)
udata->min = *value;
udata->max = *value;
udata->sum = *value;
udata->last = *value;
return true;
}
@@ -133,6 +175,248 @@ static bool rbCalcMetrics(int index, void * value_, void * udata_)
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)
{
@@ -143,11 +427,15 @@ static int graphs_render(void * udata, bool interactive,
GraphHandle graph;
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;
for (int pass = 0; pass < 2; ++pass)
{
ll_forEachNL(gs.graphs, item, graph)
{
if (!graph->enabled)
if (!graph->enabled || graph->compact != (pass == 1))
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;
}
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));
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();
};
}
}
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)
{

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -610,7 +610,7 @@ bool util_buildUIFontAtlas(
goto done;
}
result = ImFontAtlas_Build(atlas);
result = true;
done:
LG_UNLOCK(UIFontLock);

View File

@@ -5,7 +5,7 @@ add_executable(font-tests
../src/font.c
)
target_compile_definitions(font-tests PRIVATE
FONT_TEST_FILE="${PROJECT_TOP}/repos/cimgui/imgui/misc/fonts/DroidSans.ttf"
FONT_TEST_FILE="${PROJECT_TOP}/repos/gui/cimgui/imgui/misc/fonts/DroidSans.ttf"
)
target_include_directories(font-tests PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../include"

View File

@@ -1,8 +1,10 @@
if (EXISTS "${PROJECT_TOP}/.git" AND (
(NOT EXISTS "${PROJECT_TOP}/repos/cimgui/.git") OR
(NOT EXISTS "${PROJECT_TOP}/repos/gui/cimgui/.git") OR
(NOT EXISTS "${PROJECT_TOP}/repos/gui/cimplot/.git") OR
(NOT EXISTS "${PROJECT_TOP}/repos/LGMP/.git") OR
(NOT EXISTS "${PROJECT_TOP}/repos/PureSpice/.git") OR
(NOT EXISTS "${PROJECT_TOP}/repos/cimgui/imgui/.git") OR
(NOT EXISTS "${PROJECT_TOP}/repos/gui/cimgui/imgui/.git") OR
(NOT EXISTS "${PROJECT_TOP}/repos/gui/cimplot/implot/.git") OR
(NOT EXISTS "${PROJECT_TOP}/repos/wayland-protocols/.git") OR
(NOT EXISTS "${PROJECT_TOP}/repos/nanosvg/.git")
))

Submodule repos/cimgui deleted from d6b4ecda71

57
repos/gui/CMakeLists.txt Normal file
View File

@@ -0,0 +1,57 @@
cmake_minimum_required(VERSION 3.10)
project(lg_gui LANGUAGES CXX)
add_library(lg_gui STATIC
cimgui/cimgui.cpp
cimgui/imgui/imgui.cpp
cimgui/imgui/imgui_draw.cpp
cimgui/imgui/imgui_tables.cpp
cimgui/imgui/imgui_widgets.cpp
cimplot/cimplot.cpp
cimplot/implot/implot.cpp
cimplot/implot/implot_items.cpp
)
target_compile_definitions(lg_gui PUBLIC
IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1
IMGUI_USE_WCHAR32
IMGUI_USER_CONFIG="../cimconfig.h"
)
if(ENABLE_OPENGL)
target_sources(lg_gui PRIVATE
cimgui/imgui/backends/imgui_impl_opengl2.cpp
)
target_compile_definitions(lg_gui PUBLIC CIMGUI_USE_OPENGL2=1)
endif()
if(ENABLE_EGL)
set(IMGUI_OPENGL3_BACKEND
cimgui/imgui/backends/imgui_impl_opengl3.cpp
)
target_sources(lg_gui PRIVATE ${IMGUI_OPENGL3_BACKEND})
set_source_files_properties(${IMGUI_OPENGL3_BACKEND} PROPERTIES
COMPILE_DEFINITIONS IMGUI_IMPL_OPENGL_ES3
)
target_compile_definitions(lg_gui PUBLIC CIMGUI_USE_OPENGL3=1)
endif()
if(WIN32)
target_compile_definitions(lg_gui PUBLIC
IMGUI_IMPL_API=extern\t"C"\t__declspec\(dllexport\)
)
else()
target_compile_definitions(lg_gui PUBLIC IMGUI_IMPL_API=extern\t"C"\t)
endif()
target_include_directories(lg_gui PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/cimgui"
"${CMAKE_CURRENT_SOURCE_DIR}/cimgui/imgui"
"${CMAKE_CURRENT_SOURCE_DIR}/cimplot"
"${CMAKE_CURRENT_SOURCE_DIR}/cimplot/implot"
)
set_target_properties(lg_gui PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED ON
)

1
repos/gui/cimgui Submodule

Submodule repos/gui/cimgui added at 4e8fcc1367

1
repos/gui/cimplot Submodule

Submodule repos/gui/cimplot added at 8213880419