[client] add SVG loading support and use icons for status display

This brings nanosvg into the project for SVG loading and rendering.
Unfortunatly we can not at this time use a submodule for this project
until https://github.com/memononen/nanosvg/pull/214 is merged.
This commit is contained in:
Geoffrey McRae
2022-05-26 04:08:22 +10:00
parent 8aa36144dc
commit 8974ae4fb5
19 changed files with 4947 additions and 92 deletions

View File

@@ -110,7 +110,10 @@ add_custom_command(
include_directories(
${PROJECT_TOP}
${PROJECT_SOURCE_DIR}/include
${CMAKE_BINARY_DIR}
${CMAKE_BINARY_DIR}/include
${PROJECT_TOP}/repos/nanosvg/src
)
link_libraries(
@@ -142,12 +145,13 @@ set(SOURCES
src/overlay/help.c
src/overlay/config.c
src/overlay/msg.c
src/overlay/record.c
src/overlay/status.c
)
# Force cimgui to build as a static library.
set(IMGUI_STATIC "yes" CACHE STRING "Build as a static library")
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")
@@ -163,6 +167,7 @@ target_compile_definitions(looking-glass-client PRIVATE CIMGUI_DEFINE_ENUMS_AND_
target_link_libraries(looking-glass-client
${EXE_FLAGS}
PkgConfig::FONTCONFIG
lg_resources
lg_common
displayservers
lgmp

View File

@@ -1,42 +0,0 @@
function(make_object out_var)
set(result)
set(result_h)
foreach(in_f ${ARGN})
file(RELATIVE_PATH out_f ${CMAKE_CURRENT_SOURCE_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}")
set(out_h "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.h")
set(out_f "${CMAKE_CURRENT_BINARY_DIR}/${out_f}.o")
string(REGEX REPLACE "[/.-]" "_" sym_in "${in_f}")
add_custom_command(OUTPUT ${out_f}
COMMAND ${CMAKE_LINKER} -r -b binary -o ${out_f} "${in_f}"
COMMAND ${CMAKE_OBJCOPY} --rename-section .data=.rodata,CONTENTS,ALLOC,LOAD,READONLY,DATA ${out_f} ${out_f}
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_start=b_${sym_in} ${out_f} ${out_f}
COMMAND ${CMAKE_OBJCOPY} --redefine-sym _binary_${sym_in}_end=b_${sym_in}_end ${out_f} ${out_f}
COMMAND ${CMAKE_OBJCOPY} --strip-symbol _binary_${sym_in}_size ${out_f} ${out_f}
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${in_f}"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Creating object from ${in_f}"
VERBATIM
)
file(WRITE ${out_h} "extern const char b_${sym_in}[];\n")
file(APPEND ${out_h} "extern const char b_${sym_in}_end[];\n")
file(APPEND ${out_h} "#define b_${sym_in}_size (b_${sym_in}_end - b_${sym_in})\n")
get_filename_component(h_dir ${out_h} DIRECTORY)
list(APPEND result_h ${h_dir})
list(APPEND result ${out_f})
endforeach()
list(REMOVE_DUPLICATES result_h)
set(${out_var}_OBJS "${result}" PARENT_SCOPE)
set(${out_var}_INCS "${result_h}" PARENT_SCOPE)
endfunction()
function(make_defines in_file out_file)
add_custom_command(OUTPUT ${out_file}
COMMAND grep "^#define" "${in_file}" > "${out_file}"
DEPENDS ${in_file}
COMMENT "Creating #defines from ${in_file}"
)
endfunction()

View File

@@ -172,6 +172,17 @@ typedef struct LG_RendererOps
const bool newFrame, const bool invalidateWindow,
void (*preSwap)(void * udata), void * udata);
/* called to create a texture from the specified 32-bit RGB image data. This
* method is for use with Dear ImGui
* Context: renderThread */
void * (*createTexture)(LG_Renderer * renderer,
int width, int height, uint8_t * data);
/* called to free a texture previously created by createTexture. This method
* is for use with Dear ImGui
* Context: renderThread */
void (*freeTexture)(LG_Renderer * renderer, void * texture);
/* setup the spice display */
void (*spiceConfigure)(LG_Renderer * renderer, int width, int height);

View File

@@ -27,9 +27,23 @@
typedef struct ImVec2 ImVec2;
typedef struct
{
void * tex;
int width;
int height;
}
OverlayImage;
void overlayGetImGuiRect(struct Rect * rect);
ImVec2 * overlayGetScreenSize(void);
void overlayTextURL(const char * url, const char * text);
void overlayTextMaybeURL(const char * text, bool wrapped);
// create a texture from a SVG and scale it to fit the supplied width & height
bool overlayLoadSVG(const char * data, unsigned int size, OverlayImage * image,
int width, int height);
void overlayFreeImage(OverlayImage * image);
#endif

View File

@@ -1210,6 +1210,41 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
return true;
}
static void * egl_createTexture(LG_Renderer * renderer,
int width, int height, uint8_t * data)
{
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA,
width,
height,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
data);
glBindTexture(GL_TEXTURE_2D, 0);
return (void*)(intptr_t)tex;
}
static void egl_freeTexture(LG_Renderer * renderer, void * texture)
{
GLuint tex = (GLuint)(intptr_t)texture;
glDeleteTextures(1, &tex);
}
static void egl_spiceConfigure(LG_Renderer * renderer, int width, int height)
{
struct Inst * this = UPCAST(struct Inst, renderer);
@@ -1257,6 +1292,8 @@ struct LG_RendererOps LGR_EGL =
.renderStartup = egl_renderStartup,
.needsRender = egl_needsRender,
.render = egl_render,
.createTexture = egl_createTexture,
.freeTexture = egl_freeTexture,
.spiceConfigure = egl_spiceConfigure,
.spiceDrawFill = egl_spiceDrawFill,

View File

@@ -663,6 +663,41 @@ static void renderWait(struct Inst * this)
glDisable(GL_BLEND);
}
static void * opengl_createTexture(LG_Renderer * renderer,
int width, int height, uint8_t * data)
{
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA,
width,
height,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
data);
glBindTexture(GL_TEXTURE_2D, 0);
return (void*)(intptr_t)tex;
}
static void opengl_freeTexture(LG_Renderer * renderer, void * texture)
{
GLuint tex = (GLuint)(intptr_t)texture;
glDeleteTextures(1, &tex);
}
static void opengl_spiceConfigure(LG_Renderer * renderer, int width, int height)
{
struct Inst * this = UPCAST(struct Inst, renderer);
@@ -797,6 +832,8 @@ const LG_RendererOps LGR_OpenGL =
.renderStartup = opengl_renderStartup,
.needsRender = opengl_needsRender,
.render = opengl_render,
.createTexture = opengl_createTexture,
.freeTexture = opengl_freeTexture,
.spiceConfigure = opengl_spiceConfigure,
.spiceDrawFill = opengl_spiceDrawFill,

View File

@@ -676,7 +676,7 @@ void app_msgBoxClose(MsgBoxHandle handle)
void app_showRecord(bool show)
{
overlayRecord_show(show);
overlayStatus_set(LG_USER_STATUS_RECORDING, show);
}
KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, const char * description)
@@ -1072,4 +1072,5 @@ void app_useSpiceDisplay(bool enable)
renderQueue_spiceShow(false);
purespice_disconnectChannel(PS_CHANNEL_DISPLAY);
}
overlayStatus_set(LG_USER_STATUS_SPICE, enable);
}

View File

@@ -1725,7 +1725,7 @@ int main(int argc, char * argv[])
app_registerOverlay(&LGOverlayGraphs, NULL);
app_registerOverlay(&LGOverlayHelp , NULL);
app_registerOverlay(&LGOverlayMsg , NULL);
app_registerOverlay(&LGOverlayRecord, NULL);
app_registerOverlay(&LGOverlayStatus, NULL);
// early renderer setup for option registration
for(unsigned int i = 0; i < LG_RENDERER_COUNT; ++i)

View File

@@ -1,87 +0,0 @@
/**
* Looking Glass
* Copyright © 2017-2022 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 "overlay_utils.h"
#include "common/stringutils.h"
#include "../main.h"
static bool recordShow = false;
static bool recordToggle = false;
static unsigned long long lastTick = 0;
static bool record_init(void ** udata, const void * params)
{
return true;
}
static void record_free(void * udata)
{}
static int record_render(void * udata, bool interactive, struct Rect * windowRects,
int maxRects)
{
if (!recordShow || !recordToggle)
return 0;
ImVec2 * screen = overlayGetScreenSize();
ImDrawList_AddCircleFilled(igGetBackgroundDrawList_Nil(),
(ImVec2) { screen->x - 20.0f, 20.0f },
5.0f, 0xFF0000FF, 0
);
*windowRects = (struct Rect) {
.x = screen->x - 26, .y = 14, .w = 12, .h = 12
};
return 1;
}
static bool record_tick(void * udata, unsigned long long tickCount)
{
if (tickCount - lastTick >= 25)
{
recordToggle = !recordToggle;
lastTick = tickCount;
return true;
}
return false;
}
struct LG_OverlayOps LGOverlayRecord =
{
.name = "record",
.init = record_init,
.free = record_free,
.render = record_render,
.tick = record_tick,
};
void overlayRecord_show(bool show)
{
if (show == recordShow)
return;
recordShow = show;
if (g_state.state != APP_STATE_SHUTDOWN)
app_invalidateOverlay(true);
}

134
client/src/overlay/status.c Normal file
View File

@@ -0,0 +1,134 @@
/**
* Looking Glass
* Copyright © 2017-2022 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 "../overlays.h"
#include "../main.h"
#include "overlay_utils.h"
#include "resources/status/recording.svg.h"
#include "resources/status/spice.svg.h"
//TODO: Make this user configurable?
#define ICON_SIZE 32
static bool l_state[LG_USER_STATUS_MAX] = { 0 };
static OverlayImage l_image[LG_USER_STATUS_MAX] = { 0 };
static bool l_recordToggle;
static bool status_init(void ** udata, const void * params)
{
overlayLoadSVG(b_status_recording_svg, b_status_recording_svg_size,
&l_image[LG_USER_STATUS_RECORDING], ICON_SIZE, ICON_SIZE);
overlayLoadSVG(b_status_spice_svg, b_status_spice_svg_size,
&l_image[LG_USER_STATUS_SPICE], ICON_SIZE, ICON_SIZE);
return true;
}
static void status_free(void * udata)
{
for(int i = 0; i < LG_USER_STATUS_MAX; ++i)
overlayFreeImage(&l_image[i]);
}
static int status_render(void * udata, bool interactive, struct Rect * windowRects,
int maxRects)
{
const int marginX = 10;
const int marginY = 10;
const int gapX = 5;
ImVec2 * screen = overlayGetScreenSize();
struct Rect rect = {
.x = screen->x - LG_USER_STATUS_MAX * (ICON_SIZE + gapX) - marginX,
.y = marginY,
.w = LG_USER_STATUS_MAX * (ICON_SIZE + gapX),
.h = ICON_SIZE
};
int xPos = screen->x - marginX;
for(int i = 0; i < LG_USER_STATUS_MAX; ++i)
{
OverlayImage * img = &l_image[i];
if (!l_state[i] || !img->tex)
continue;
// if the recording indicator is off, don't draw but reserve space
if (i == LG_USER_STATUS_RECORDING && !l_recordToggle)
goto next;
ImDrawList_AddImage(
igGetBackgroundDrawList_Nil(),
img->tex,
(ImVec2){
xPos,
marginY
},
(ImVec2){
xPos - ICON_SIZE,
img->height + marginY
},
(ImVec2){ 0, 0 },
(ImVec2){ 1, 1 },
0xFFFFFFFF);
next:
xPos -= ICON_SIZE + gapX;
}
*windowRects = rect;
return 1;
}
static bool status_tick(void * udata, unsigned long long tickCount)
{
static unsigned long long lastTick = 0;
if (tickCount - lastTick >= 25)
{
l_recordToggle = !l_recordToggle;
lastTick = tickCount;
return true;
}
return false;
}
struct LG_OverlayOps LGOverlayStatus =
{
.name = "status",
.init = status_init,
.free = status_free,
.render = status_render,
.tick = status_tick,
};
void overlayStatus_set(LGUserStatus status, bool value)
{
if (l_state[status] == value)
return;
l_state[status] = value;
app_invalidateOverlay(true);
};

View File

@@ -26,6 +26,11 @@
#include "cimgui.h"
#include "main.h"
#define NANOSVG_IMPLEMENTATION
#define NANOSVG_ALL_COLOR_KEYWORDS
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvgrast.h"
void overlayGetImGuiRect(struct Rect * rect)
{
ImVec2 size;
@@ -78,3 +83,93 @@ void overlayTextMaybeURL(const char * text, bool wrapped)
else
igText(text);
}
bool overlayLoadSVG(const char * data, unsigned int size, OverlayImage * image,
int width, int height)
{
image->tex = NULL;
//nsvgParse alters the data, we need to make a copy and null terminate it
char * svg = malloc(size + 1);
if (!svg)
{
DEBUG_ERROR("out of ram");
goto err;
}
memcpy(svg, data, size);
svg[size] = 0;
NSVGimage * nvi = nsvgParse(svg, "px", 96.0);
if (!nvi)
{
free(svg);
DEBUG_ERROR("nvsgParseFromData failed");
goto err;
}
free(svg);
NSVGrasterizer * rast = nsvgCreateRasterizer();
if (!rast)
{
DEBUG_ERROR("nsvgCreateRasterizer failed");
goto err_image;
}
double srcAspect = nvi->width / nvi->height;
double dstAspect = (double)width / (double)height;
float scale;
if (dstAspect > srcAspect)
{
image->width = (double)height * srcAspect;
image->height = height;
scale = (float)image->width / nvi->width;
}
else
{
image->width = width;
image->height = (double)width / srcAspect;
scale = (float)image->height / nvi->height;
}
uint8_t * img = malloc(image->width * image->height * 4);
if (!img)
{
DEBUG_ERROR("out of ram");
goto err_rast;
}
nsvgRasterize(rast, nvi,
0.0f, 0.0f,
scale,
img,
image->width,
image->height,
image->width * 4);
image->tex = RENDERER(createTexture, image->width, image->height, img);
free(img);
if (!image->tex)
{
DEBUG_ERROR("renderer failed to create the texture");
goto err_rast;
}
err_rast:
nsvgDeleteRasterizer(rast);
err_image:
nsvgDelete(nvi);
err:
return image->tex != NULL;
}
void overlayFreeImage(OverlayImage * image)
{
if (!image->tex)
return;
RENDERER(freeTexture, image->tex);
}

View File

@@ -39,7 +39,7 @@ extern struct LG_OverlayOps LGOverlayGraphs;
extern struct LG_OverlayOps LGOverlayHelp;
extern struct LG_OverlayOps LGOverlayConfig;
extern struct LG_OverlayOps LGOverlayMsg;
extern struct LG_OverlayOps LGOverlayRecord;
extern struct LG_OverlayOps LGOverlayStatus;
void overlayAlert_show(LG_MsgAlert type, const char * fmt, va_list args);
@@ -56,6 +56,14 @@ void overlayConfig_register(const char * title,
void overlayConfig_registerTab(const char * title,
void (*callback)(void * udata, int * id), void * udata);
void overlayRecord_show(bool show);
typedef enum LG_UserStatus
{
LG_USER_STATUS_SPICE,
LG_USER_STATUS_RECORDING,
LG_USER_STATUS_MAX
}
LGUserStatus;
void overlayStatus_set(LGUserStatus, bool value);
#endif