From 4f3927073695e35cb2119a02682ffcd7ba66bb8c Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Mon, 3 Aug 2026 14:18:22 +1000 Subject: [PATCH] [client] add ImPlot and rework latency graphs --- .gitmodules | 5 +- client/CMakeLists.txt | 11 +- client/displayservers/Wayland/presentation.c | 1 + client/include/app.h | 2 + client/include/overlay_utils.h | 2 +- client/renderers/EGL/CMakeLists.txt | 3 +- client/renderers/EGL/egl.c | 19 +- client/renderers/EGL/postprocess.c | 4 - client/renderers/OpenGL/CMakeLists.txt | 3 +- client/renderers/OpenGL/opengl.c | 5 +- client/src/app.c | 10 + client/src/font.c | 2 +- client/src/main.c | 125 +++--- client/src/main.h | 16 +- client/src/overlay/alert.c | 2 +- client/src/overlay/graphs.c | 391 ++++++++++++++++--- client/src/overlay/msg.c | 6 +- client/src/overlay/splash.c | 6 +- client/src/overlay/status.c | 2 +- client/src/overlay_utils.c | 12 +- client/src/overlays.h | 14 + client/src/util.c | 2 +- client/tests/CMakeLists.txt | 2 +- cmake/CheckSubmodule.cmake | 6 +- repos/cimgui | 1 - repos/gui/CMakeLists.txt | 57 +++ repos/gui/cimgui | 1 + repos/gui/cimplot | 1 + 28 files changed, 539 insertions(+), 172 deletions(-) delete mode 160000 repos/cimgui create mode 100644 repos/gui/CMakeLists.txt create mode 160000 repos/gui/cimgui create mode 160000 repos/gui/cimplot diff --git a/.gitmodules b/.gitmodules index 839d3ae9..c1a7557e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 770ecd1f..f1557d5e 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -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) diff --git a/client/displayservers/Wayland/presentation.c b/client/displayservers/Wayland/presentation.c index ed7a00ea..4664260e 100644 --- a/client/displayservers/Wayland/presentation.c +++ b/client/displayservers/Wayland/presentation.c @@ -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; diff --git a/client/include/app.h b/client/include/app.h index 6360c980..2fbbaae3 100644 --- a/client/include/app.h +++ b/client/include/app.h @@ -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); diff --git a/client/include/overlay_utils.h b/client/include/overlay_utils.h index 3ad48110..0b5c5240 100644 --- a/client/include/overlay_utils.h +++ b/client/include/overlay_utils.h @@ -25,7 +25,7 @@ #include "common/types.h" -typedef struct ImVec2 ImVec2; +typedef struct ImVec2_c ImVec2; typedef struct { diff --git a/client/renderers/EGL/CMakeLists.txt b/client/renderers/EGL/CMakeLists.txt index 8b88346f..592474a1 100644 --- a/client/renderers/EGL/CMakeLists.txt +++ b/client/renderers/EGL/CMakeLists.txt @@ -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 diff --git a/client/renderers/EGL/egl.c b/client/renderers/EGL/egl.c index 86990098..4d25efee 100644 --- a/client/renderers/EGL/egl.c +++ b/client/renderers/EGL/egl.c @@ -36,7 +36,7 @@ #include #include "cimgui.h" -#include "generator/output/cimgui_impl.h" +#include "cimgui_impl.h" #include #include @@ -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; diff --git a/client/renderers/EGL/postprocess.c b/client/renderers/EGL/postprocess.c index 3951f37c..bc91ee5d 100644 --- a/client/renderers/EGL/postprocess.c +++ b/client/renderers/EGL/postprocess.c @@ -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) diff --git a/client/renderers/OpenGL/CMakeLists.txt b/client/renderers/OpenGL/CMakeLists.txt index d4768250..cf976ef5 100644 --- a/client/renderers/OpenGL/CMakeLists.txt +++ b/client/renderers/OpenGL/CMakeLists.txt @@ -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 diff --git a/client/renderers/OpenGL/opengl.c b/client/renderers/OpenGL/opengl.c index 201a80b5..a4a9506b 100644 --- a/client/renderers/OpenGL/opengl.c +++ b/client/renderers/OpenGL/opengl.c @@ -29,7 +29,7 @@ #include #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, diff --git a/client/src/app.c b/client/src/app.c index 584162a6..6d71855a 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -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); diff --git a/client/src/font.c b/client/src/font.c index 61cfa009..9b384f48 100644 --- a/client/src/font.c +++ b/client/src/font.c @@ -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 diff --git a/client/src/main.c b/client/src/main.c index 038ca81b..13ff07d4 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -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); diff --git a/client/src/main.h b/client/src/main.h index 0dd690ca..2e4d825f 100644 --- a/client/src/main.h +++ b/client/src/main.h @@ -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; diff --git a/client/src/overlay/alert.c b/client/src/overlay/alert.c index cbea995c..3a0dca08 100644 --- a/client/src/overlay/alert.c +++ b/client/src/overlay/alert.c @@ -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(); diff --git a/client/src/overlay/graphs.c b/client/src/overlay/graphs.c index 5c52b790..db77bde7 100644 --- a/client/src/overlay/graphs.c +++ b/client/src/overlay/graphs.c @@ -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 + 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) { diff --git a/client/src/overlay/msg.c b/client/src/overlay/msg.c index 228bcaa3..f5b14d1f 100644 --- a/client/src/overlay/msg.c +++ b/client/src/overlay/msg.c @@ -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; diff --git a/client/src/overlay/splash.c b/client/src/overlay/splash.c index 4ad793de..8a111497 100644 --- a/client/src/overlay/splash.c +++ b/client/src/overlay/splash.c @@ -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 diff --git a/client/src/overlay/status.c b/client/src/overlay/status.c index 1d384520..c9f83507 100644 --- a/client/src/overlay/status.c +++ b/client/src/overlay/status.c @@ -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 diff --git a/client/src/overlay_utils.c b/client/src/overlay_utils.c index 05fcbe9f..e491a0cd 100644 --- a/client/src/overlay_utils.c +++ b/client/src/overlay_utils.c @@ -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); } diff --git a/client/src/overlays.h b/client/src/overlays.h index ed050871..b131223e 100644 --- a/client/src/overlays.h +++ b/client/src/overlays.h @@ -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); diff --git a/client/src/util.c b/client/src/util.c index 4a9b4af7..832fa42d 100644 --- a/client/src/util.c +++ b/client/src/util.c @@ -610,7 +610,7 @@ bool util_buildUIFontAtlas( goto done; } - result = ImFontAtlas_Build(atlas); + result = true; done: LG_UNLOCK(UIFontLock); diff --git a/client/tests/CMakeLists.txt b/client/tests/CMakeLists.txt index 0cf21960..e80c9be4 100644 --- a/client/tests/CMakeLists.txt +++ b/client/tests/CMakeLists.txt @@ -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" diff --git a/cmake/CheckSubmodule.cmake b/cmake/CheckSubmodule.cmake index 93d0146c..a3c6ea7e 100644 --- a/cmake/CheckSubmodule.cmake +++ b/cmake/CheckSubmodule.cmake @@ -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") )) diff --git a/repos/cimgui b/repos/cimgui deleted file mode 160000 index d6b4ecda..00000000 --- a/repos/cimgui +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d6b4ecda718352bb4adcd30ec25784fd16538b73 diff --git a/repos/gui/CMakeLists.txt b/repos/gui/CMakeLists.txt new file mode 100644 index 00000000..bc0d73b6 --- /dev/null +++ b/repos/gui/CMakeLists.txt @@ -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 +) diff --git a/repos/gui/cimgui b/repos/gui/cimgui new file mode 160000 index 00000000..4e8fcc13 --- /dev/null +++ b/repos/gui/cimgui @@ -0,0 +1 @@ +Subproject commit 4e8fcc13674481a8c4c04abbfea90915e3896fe7 diff --git a/repos/gui/cimplot b/repos/gui/cimplot new file mode 160000 index 00000000..82138804 --- /dev/null +++ b/repos/gui/cimplot @@ -0,0 +1 @@ +Subproject commit 8213880419a5d72f2c898ef90a73df6c16507f27