From fdbdf6f167a39e2deed952787af576241ab0e15b Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Thu, 22 Jul 2021 17:27:30 +1000 Subject: [PATCH] [client] app: implement new overlay rendering framework This change set implements a framework for overlays to be registered that make use of ImGui. See `overlay/fps` for a simple implementation example. --- client/CMakeLists.txt | 3 + client/include/app.h | 10 +- client/include/interface/overlay.h | 5 +- client/renderers/EGL/egl.c | 18 ++- client/renderers/OpenGL/opengl.c | 2 +- client/src/app.c | 166 +++++++++------------------ client/src/main.c | 17 ++- client/src/main.h | 2 +- client/src/overlay/fps.c | 79 +++++++++++++ client/src/overlay/graphs.c | 175 +++++++++++++++++++++++++++++ client/src/overlays.h | 32 ++++++ 11 files changed, 385 insertions(+), 124 deletions(-) create mode 100644 client/src/overlay/fps.c create mode 100644 client/src/overlay/graphs.c create mode 100644 client/src/overlays.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 648988dd..ef83116c 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -120,6 +120,9 @@ set(SOURCES src/kb.c src/egl_dynprocs.c src/eglutil.c + + src/overlay/fps.c + src/overlay/graphs.c ) # Force cimgui to build as a static library. diff --git a/client/include/app.h b/client/include/app.h index ff72a261..ca84d576 100644 --- a/client/include/app.h +++ b/client/include/app.h @@ -27,6 +27,7 @@ #include "common/ringbuffer.h" #include "common/types.h" #include "interface/displayserver.h" +#include "interface/overlay.h" typedef enum LG_MsgAlert { @@ -79,10 +80,15 @@ void app_glSetSwapInterval(int interval); void app_glSwapBuffers(void); #endif -typedef struct ImGuiGraph * GraphHandle; +void app_registerOverlay(const struct LG_OverlayOps * ops, void * params); +int app_renderOverlay(struct Rect * rects, int maxRects); +void app_freeOverlays(void); + +struct OverlayGraph; +typedef struct OverlayGraph * GraphHandle; + GraphHandle app_registerGraph(const char * name, RingBuffer buffer); void app_unregisterGraph(GraphHandle handle); -bool app_renderImGui(void); void app_clipboardRelease(void); void app_clipboardNotifyTypes(const LG_ClipboardData types[], int count); diff --git a/client/include/interface/overlay.h b/client/include/interface/overlay.h index 6d64df0a..4f9bb682 100644 --- a/client/include/interface/overlay.h +++ b/client/include/interface/overlay.h @@ -22,6 +22,8 @@ #define _H_I_OVERLAY_ #include +#include + #include "common/types.h" enum LG_OverlayFlags @@ -57,7 +59,8 @@ struct LG_OverlayOps * mode. * * The caller provides `windowRects` to be populated by the callee and is sized - * according to the return value of `getWindowCount` + * according to the return value of `getWindowCount`. Note, `windowRects` may + * be NULL if the caller does not want this information. */ void (*render)(void * udata, bool interactive, struct Rect windowRects[]); diff --git a/client/renderers/EGL/egl.c b/client/renderers/EGL/egl.c index 7f713bf9..07b9748a 100644 --- a/client/renderers/EGL/egl.c +++ b/client/renderers/EGL/egl.c @@ -1007,15 +1007,23 @@ bool egl_render(void * opaque, LG_RendererRotate rotate, const bool newFrame) hasOverlay |= egl_help_render(this->help, this->screenScaleX, this->screenScaleY); hasOverlay |= egl_damage_render(this->damage, newFrame ? desktopDamage : NULL); - if (app_renderImGui()) + struct Rect damage[KVMFR_MAX_DAMAGE_RECTS + 2]; + int damageIdx = app_renderOverlay(damage, KVMFR_MAX_DAMAGE_RECTS); + + // if no overlay + if (damageIdx == -1) + { + damageIdx = 0; + } + else { ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_RenderDrawData(igGetDrawData()); - hasOverlay = true; - } - struct Rect damage[KVMFR_MAX_DAMAGE_RECTS + 2]; - int damageIdx = 0; + // if there were too many rects invalidate the entire window + if (damageIdx == 0) + hasOverlay = true; + } if (!hasOverlay && !this->hadOverlay) { diff --git a/client/renderers/OpenGL/opengl.c b/client/renderers/OpenGL/opengl.c index a0646acd..b2e224fd 100644 --- a/client/renderers/OpenGL/opengl.c +++ b/client/renderers/OpenGL/opengl.c @@ -686,7 +686,7 @@ bool opengl_render(void * opaque, LG_RendererRotate rotate, const bool newFrame) break; } - if (app_renderImGui()) + if (app_renderOverlay(NULL, 0) > -1) { ImGui_ImplOpenGL2_NewFrame(); ImGui_ImplOpenGL2_RenderDrawData(igGetDrawData()); diff --git a/client/src/app.c b/client/src/app.c index 0860a6bd..94f98cad 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -30,6 +30,8 @@ #include "common/debug.h" #include "common/stringutils.h" +#include "interface/overlay.h" +#include "overlays.h" #include "cimgui.h" @@ -612,143 +614,87 @@ void app_showHelp(bool show) free(help); } -struct ImGuiGraph -{ - const char * name; - RingBuffer buffer; - bool enabled; -}; - GraphHandle app_registerGraph(const char * name, RingBuffer buffer) { - struct ImGuiGraph * graph = malloc(sizeof(struct ImGuiGraph)); - graph->name = name; - graph->buffer = buffer; - graph->enabled = true; - ll_push(g_state.graphs, graph); - return graph; + return overlayGraph_register(name, buffer); } void app_unregisterGraph(GraphHandle handle) { - handle->enabled = false; + overlayGraph_unregister(handle); } -struct BufferMetrics +struct Overlay { - float min; - float max; - float sum; - float avg; - float freq; + const struct LG_OverlayOps * ops; + void * udata; + int windowCount; }; -static bool rbCalcMetrics(int index, void * value_, void * udata_) +void app_registerOverlay(const struct LG_OverlayOps * ops, void * params) { - float * value = value_; - struct BufferMetrics * udata = udata_; + ASSERT_LG_OVERLAY_VALID(ops); - if (index == 0) + void * udata; + if (!ops->init(&udata, params)) { - udata->min = *value; - udata->max = *value; - udata->sum = *value; - return true; + DEBUG_ERROR("Overlay `%s` failed to initialize", ops->name); + return; } - if (udata->min > *value) - udata->min = *value; - - if (udata->max < *value) - udata->max = *value; - - udata->sum += *value; - return true; + struct Overlay * overlay = malloc(sizeof(struct Overlay)); + overlay->ops = ops; + overlay->udata = udata; + ll_push(g_state.overlays, overlay); } -bool app_renderImGui(void) +int app_renderOverlay(struct Rect * rects, int maxRects) { - if (!g_state.showFPS && - !g_state.showTiming) - return false; + int windowCount = 0; + struct Overlay * overlay; - igNewFrame(); - - ImGuiStyle * style = igGetStyle(); - style->WindowBorderSize = 0.0f; - - if (g_state.showFPS) + // get the total window count + for (ll_reset(g_state.overlays); + ll_walk(g_state.overlays, (void **)&overlay); ) { - const ImVec2 pos = {0.0f, 0.0f}; - igSetNextWindowBgAlpha(0.6f); - igSetNextWindowPos(pos, 0, pos); - - igBegin( - "FPS", - NULL, - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar - ); - - igText("FPS:%4.2f UPS:%4.2f", - atomic_load_explicit(&g_state.fps, memory_order_relaxed), - atomic_load_explicit(&g_state.ups, memory_order_relaxed)); - - igEnd(); + overlay->windowCount = overlay->ops->getWindowCount(overlay->udata, false); + windowCount += overlay->windowCount; } - if (g_state.showTiming) + // return -1 if there are no windows to render + if (windowCount == 0) + return -1; + + if (windowCount > maxRects) { - const ImVec2 pos = {0.0f, 0.0f}; - igSetNextWindowBgAlpha(0.4f); - igSetNextWindowPos(pos, 0, pos); + rects = NULL; + windowCount = 0; + } - igBegin( - "Performance Metrics", - NULL, - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar - ); + // render the overlays + igNewFrame(); + for (ll_reset(g_state.overlays); + ll_walk(g_state.overlays, (void **)&overlay); ) + { + if (overlay->windowCount == 0) + continue; - GraphHandle graph; - for (ll_reset(g_state.graphs); ll_walk(g_state.graphs, (void **)&graph); ) - { - if (!graph->enabled) - continue; - - struct BufferMetrics metrics = {}; - ringbuffer_forEach(graph->buffer, rbCalcMetrics, &metrics); - - if (metrics.sum > 0.0f) - { - metrics.avg = metrics.sum / ringbuffer_getCount(graph->buffer); - metrics.freq = 1000.0f / metrics.avg; - } - - char title[64]; - const ImVec2 size = {400.0f, 100.0f}; - - 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); - - igPlotLinesFloatPtr( - "", - (float *)ringbuffer_getValues(graph->buffer), - ringbuffer_getLength(graph->buffer), - ringbuffer_getStart (graph->buffer), - title, - 0.0f, - 50.0f, - size, - sizeof(float)); - } - - igEnd(); + overlay->ops->render(overlay->udata, false, rects); + if (rects) + rects += overlay->windowCount; } igRender(); - return true; + + return windowCount; +} + +void app_freeOverlays(void) +{ + struct Overlay * overlay; + while(ll_shift(g_state.overlays, (void **)&overlay)) + { + overlay->ops->free(overlay->udata); + free(overlay); + } } diff --git a/client/src/main.c b/client/src/main.c index 0f4607d9..f2108cc7 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -55,6 +55,7 @@ #include "clipboard.h" #include "ll.h" #include "egl_dynprocs.h" +#include "overlays.h" // forwards static int cursorThread(void * unused); @@ -758,14 +759,15 @@ static int lg_run(void) ImFontAtlas_GetTexDataAsRGBA32(g_state.io->Fonts, &text_pixels, &text_w, &text_h, NULL); - g_state.graphs = ll_new(); + g_state.overlays = ll_new(); + app_registerOverlay(&LGOverlayFPS , NULL); + app_registerOverlay(&LGOverlayGraphs, NULL); // initialize metrics ringbuffers g_state.renderTimings = ringbuffer_new(256, sizeof(float)); g_state.frameTimings = ringbuffer_new(256, sizeof(float)); - - app_registerGraph("RENDER", g_state.renderTimings); - app_registerGraph("UPLOAD", g_state.frameTimings); + overlayGraph_register("RENDER", g_state.renderTimings); + overlayGraph_register("UPLOAD", g_state.frameTimings ); // search for the best displayserver ops to use for(int i = 0; i < LG_DISPLAYSERVER_COUNT; ++i) @@ -1091,6 +1093,13 @@ static void lg_shutdown(void) lgmpClientFree(&g_state.lgmp); + if (g_state.overlays) + { + app_freeOverlays(); + ll_free(g_state.overlays); + g_state.overlays = NULL; + } + if (e_frame) { lgFreeEvent(e_frame); diff --git a/client/src/main.h b/client/src/main.h index 914159d2..61443169 100644 --- a/client/src/main.h +++ b/client/src/main.h @@ -48,7 +48,7 @@ struct AppState enum RunState state; ImGuiIO * io; - struct ll * graphs; + struct ll * overlays; struct LG_DisplayServerOps * ds; bool dsInitialized; diff --git a/client/src/overlay/fps.c b/client/src/overlay/fps.c new file mode 100644 index 00000000..dce1085f --- /dev/null +++ b/client/src/overlay/fps.c @@ -0,0 +1,79 @@ +/** + * Looking Glass + * Copyright (C) 2017-2021 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "interface/overlay.h" +#include "cimgui.h" + +#include "../main.h" + +static bool fps_init(void ** udata, void * params) +{ + return true; +} + +static void fps_free(void * udata) +{ +} + +static int fps_getWindowCount(void * udata, bool interactive) +{ + return g_state.showFPS ? 1 : 0; +} + +static void fps_render(void * udata, bool interactive, struct Rect * windowRects) +{ + const ImVec2 pos = {0.0f, 0.0f}; + igSetNextWindowBgAlpha(0.6f); + igSetNextWindowPos(pos, 0, pos); + + igBegin( + "FPS", + NULL, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar + ); + + igText("FPS:%4.2f UPS:%4.2f", + atomic_load_explicit(&g_state.fps, memory_order_relaxed), + atomic_load_explicit(&g_state.ups, memory_order_relaxed)); + + if (windowRects) + { + ImVec2 pos, size; + igGetWindowPos(&pos); + igGetWindowSize(&size); + windowRects[0].x = pos.x; + windowRects[0].y = pos.y; + windowRects[0].w = size.x; + windowRects[0].h = size.y; + } + + igEnd(); +} + +struct LG_OverlayOps LGOverlayFPS = +{ + .name = "FPS", + .init = fps_init, + .free = fps_free, + .getWindowCount = fps_getWindowCount, + .render = fps_render +}; diff --git a/client/src/overlay/graphs.c b/client/src/overlay/graphs.c new file mode 100644 index 00000000..1d8e3793 --- /dev/null +++ b/client/src/overlay/graphs.c @@ -0,0 +1,175 @@ +/** + * Looking Glass + * Copyright (C) 2017-2021 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "interface/overlay.h" +#include "cimgui.h" + +#include "../main.h" + +#include "ll.h" +#include "common/debug.h" + +struct GraphState +{ + struct ll * graphs; +}; + +static struct GraphState gs = {0}; + +struct OverlayGraph +{ + const char * name; + RingBuffer buffer; + bool enabled; +}; + +static bool graphs_init(void ** udata, void * params) +{ + gs.graphs = ll_new(); + return true; +} + +static void graphs_free(void * udata) +{ + ll_free(gs.graphs); +} + +static int graphs_getWindowCount(void * udata, bool interactive) +{ + return g_state.showTiming ? 1 : 0; +} + +struct BufferMetrics +{ + float min; + float max; + float sum; + float avg; + float freq; +}; + +static bool rbCalcMetrics(int index, void * value_, void * udata_) +{ + float * value = value_; + struct BufferMetrics * udata = udata_; + + if (index == 0) + { + udata->min = *value; + udata->max = *value; + udata->sum = *value; + return true; + } + + if (udata->min > *value) + udata->min = *value; + + if (udata->max < *value) + udata->max = *value; + + udata->sum += *value; + return true; +} + +static void graphs_render(void * udata, bool interactive, struct Rect * windowRects) +{ + const ImVec2 pos = {0.0f, 0.0f}; + igSetNextWindowBgAlpha(0.4f); + igSetNextWindowPos(pos, 0, pos); + + igBegin( + "Performance Metrics", + NULL, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar + ); + + GraphHandle graph; + for (ll_reset(gs.graphs); ll_walk(gs.graphs, (void **)&graph); ) + { + if (!graph->enabled) + continue; + + struct BufferMetrics metrics = {}; + ringbuffer_forEach(graph->buffer, rbCalcMetrics, &metrics); + + if (metrics.sum > 0.0f) + { + metrics.avg = metrics.sum / ringbuffer_getCount(graph->buffer); + metrics.freq = 1000.0f / metrics.avg; + } + + char title[64]; + const ImVec2 size = {400.0f, 100.0f}; + + 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); + + igPlotLinesFloatPtr( + "", + (float *)ringbuffer_getValues(graph->buffer), + ringbuffer_getLength(graph->buffer), + ringbuffer_getStart (graph->buffer), + title, + 0.0f, + 50.0f, + size, + sizeof(float)); + }; + + if (windowRects) + { + ImVec2 pos, size; + igGetWindowPos(&pos); + igGetWindowSize(&size); + windowRects[0].x = pos.x; + windowRects[0].y = pos.y; + windowRects[0].w = size.x; + windowRects[0].h = size.y; + } + + igEnd(); +} + +struct LG_OverlayOps LGOverlayGraphs = +{ + .name = "Graphs", + .init = graphs_init, + .free = graphs_free, + .getWindowCount = graphs_getWindowCount, + .render = graphs_render +}; + +GraphHandle overlayGraph_register(const char * name, RingBuffer buffer) +{ + struct OverlayGraph * graph = malloc(sizeof(struct OverlayGraph)); + graph->name = name; + graph->buffer = buffer; + graph->enabled = true; + ll_push(gs.graphs, graph); + return graph; +} + +void overlayGraph_unregister(GraphHandle handle) +{ + handle->enabled = false; +} diff --git a/client/src/overlays.h b/client/src/overlays.h new file mode 100644 index 00000000..24b88c99 --- /dev/null +++ b/client/src/overlays.h @@ -0,0 +1,32 @@ +/** + * Looking Glass + * Copyright (C) 2017-2021 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_OVERLAYS_H +#define _H_OVERLAYS_H + +#include "interface/overlay.h" + +extern struct LG_OverlayOps LGOverlayFPS; +extern struct LG_OverlayOps LGOverlayGraphs; + +GraphHandle overlayGraph_register(const char * name, RingBuffer buffer); +void overlayGraph_unregister(); + +#endif