diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 1a6c47e3..183361f0 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -134,6 +134,7 @@ set(SOURCES src/egl_dynprocs.c src/eglutil.c src/overlay_utils.c + src/render_queue.c src/overlay/alert.c src/overlay/fps.c diff --git a/client/include/app.h b/client/include/app.h index 1d868461..6276aba2 100644 --- a/client/include/app.h +++ b/client/include/app.h @@ -179,5 +179,9 @@ bool app_guestIsOSX(void); bool app_guestIsBSD(void); bool app_guestIsOther(void); +/** + * Enable/disable the spice display + */ +void app_useSpiceDisplay(bool enable); #endif diff --git a/client/include/interface/renderer.h b/client/include/interface/renderer.h index 7fc11d65..75320095 100644 --- a/client/include/interface/renderer.h +++ b/client/include/interface/renderer.h @@ -27,17 +27,21 @@ #include "common/framebuffer.h" #define IS_LG_RENDERER_VALID(x) \ - ((x)->getName && \ - (x)->create && \ - (x)->initialize && \ - (x)->deinitialize && \ - (x)->onRestart && \ - (x)->onResize && \ - (x)->onMouseShape && \ - (x)->onMouseEvent && \ - (x)->renderStartup && \ - (x)->needsRender && \ - (x)->render) + ((x)->getName && \ + (x)->create && \ + (x)->initialize && \ + (x)->deinitialize && \ + (x)->onRestart && \ + (x)->onResize && \ + (x)->onMouseShape && \ + (x)->onMouseEvent && \ + (x)->renderStartup && \ + (x)->needsRender && \ + (x)->render && \ + (x)->spiceConfigure && \ + (x)->spiceDrawFill && \ + (x)->spiceDrawBitmap && \ + (x)->spiceShow) typedef struct LG_RendererParams { @@ -167,6 +171,20 @@ typedef struct LG_RendererOps bool (*render)(LG_Renderer * renderer, LG_RendererRotate rotate, const bool newFrame, const bool invalidateWindow, void (*preSwap)(void * udata), void * udata); + + /* setup the spice display */ + void (*spiceConfigure)(LG_Renderer * renderer, int width, int height); + + /* draw a filled rect on the spice display with the specified color */ + void (*spiceDrawFill)(LG_Renderer * renderer, int x, int y, int width, + int height, uint32_t color); + + /* draw an image on the spice display, data is RGBA32 */ + void (*spiceDrawBitmap)(LG_Renderer * renderer, int x, int y, int width, + int height, int stride, uint8_t * data); + + /* show the spice display */ + void (*spiceShow)(LG_Renderer * renderer, bool show); } LG_RendererOps; diff --git a/client/src/app.c b/client/src/app.c index e894309c..1b45c569 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -1008,3 +1008,23 @@ bool app_guestIsOther(void) { return g_state.guestOS == KVMFR_OS_OTHER; } + +void app_useSpiceDisplay(bool enable) +{ + if (!g_params.useSpice) + return; + + if (!purespice_hasChannel(PS_CHANNEL_DISPLAY)) + return; + + if (enable) + { + purespice_connectChannel(PS_CHANNEL_DISPLAY); + // do not call spiceShow as the surface create callback will do this + } + else + { + RENDERER(spiceShow, false); + purespice_disconnectChannel(PS_CHANNEL_DISPLAY); + } +} diff --git a/client/src/config.c b/client/src/config.c index 004bd2fa..9210f8ab 100644 --- a/client/src/config.c +++ b/client/src/config.c @@ -667,7 +667,7 @@ bool config_load(int argc, char * argv[]) g_params.minimizeOnFocusLoss = option_get_bool("win", "minimizeOnFocusLoss"); - if (option_get_bool("spice", "enable")) + if ((g_params.useSpice = option_get_bool("spice", "enable"))) { g_params.spiceHost = option_get_string("spice", "host"); g_params.spicePort = option_get_int ("spice", "port"); diff --git a/client/src/main.c b/client/src/main.c index 69a757f7..301139c7 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -62,6 +62,7 @@ #include "overlays.h" #include "overlay_utils.h" #include "util.h" +#include "render_queue.h" // forwards static int renderThread(void * unused); @@ -280,6 +281,9 @@ static int renderThread(void * unused) const uint64_t renderStart = nanotime(); LG_LOCK(g_state.lgrLock); + + renderQueue_process(); + if (!RENDERER(render, g_params.winRotate, newFrame, invalidate, preSwapCallback, (void *)&renderStart)) { @@ -595,6 +599,9 @@ int main_frameThread(void * unused) break; } + // disable the spice display as we have a frame from the LG host + app_useSpiceDisplay(false); + KVMFRFrame * frame = (KVMFRFrame *)msg.mem; // ignore any repeated frames, this happens when a new client connects to @@ -800,7 +807,9 @@ int main_frameThread(void * unused) } lgmpClientUnsubscribe(&queue); + RENDERER(onRestart); + app_useSpiceDisplay(true); if (g_state.useDMA) { @@ -809,7 +818,6 @@ int main_frameThread(void * unused) close(dmaInfo[i].fd); } - return 0; } @@ -862,6 +870,32 @@ void spiceReady(void) purespice_freeServerInfo(&info); } +static void spice_surfaceCreate(unsigned int surfaceId, PSSurfaceFormat format, + unsigned int width, unsigned int height) +{ + renderQueue_spiceConfigure(width, height); + if (g_state.lgr) + RENDERER(spiceShow, true); +} + +static void spice_surfaceDestroy(unsigned int surfaceId) +{ + if (g_state.lgr) + RENDERER(spiceShow, false); +} + +static void spice_drawFill(unsigned int surfaceId, int x, int y, int width, + int height, uint32_t color) +{ + renderQueue_spiceDrawFill(x, y, width, height, color); +} + +static void spice_drawBitmap(unsigned int surfaceId, PSBitmapFormat format, + bool topDown, int x, int y, int width, int height, int stride, void * data) +{ + renderQueue_spiceDrawBitmap(x, y, width, height, stride, data); +} + int spiceThread(void * arg) { if (g_params.useSpiceAudio) @@ -886,6 +920,14 @@ int spiceThread(void * arg) .release = cb_spiceRelease, .request = cb_spiceRequest }, + .display = + { + .enable = true, + .surfaceCreate = spice_surfaceCreate, + .surfaceDestroy = spice_surfaceDestroy, + .drawFill = spice_drawFill, + .drawBitmap = spice_drawBitmap + }, #if ENABLE_AUDIO .playback = { @@ -1117,6 +1159,9 @@ static int lg_run(void) return -1; } + //setup the render command queue + renderQueue_init(); + const PSInit psInit = { .log = @@ -1261,6 +1306,10 @@ static int lg_run(void) if (g_state.cbAvailable) g_state.cbRequestList = ll_new(); + // fallback to the spice display + if (g_params.useSpice && purespice_hasChannel(PS_CHANNEL_DISPLAY)) + purespice_connectChannel(PS_CHANNEL_DISPLAY); + LGMP_STATUS status; while(g_state.state == APP_STATE_RUNNING) @@ -1610,6 +1659,8 @@ static void lg_shutdown(void) ivshmemClose(&g_state.shm); + renderQueue_free(); + // free metrics ringbuffers ringbuffer_free(&g_state.renderTimings); ringbuffer_free(&g_state.uploadTimings); diff --git a/client/src/main.h b/client/src/main.h index ae05fc63..1bfcd40e 100644 --- a/client/src/main.h +++ b/client/src/main.h @@ -159,6 +159,7 @@ struct AppParams unsigned int w, h; int fpsMin; LG_RendererRotate winRotate; + bool useSpice; bool useSpiceInput; bool useSpiceClipboard; bool useSpiceAudio; diff --git a/client/src/render_queue.c b/client/src/render_queue.c new file mode 100644 index 00000000..8edf4d14 --- /dev/null +++ b/client/src/render_queue.c @@ -0,0 +1,121 @@ +/** + * 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 "render_queue.h" + +#include + +#include "common/ll.h" +#include "main.h" + +struct ll * l_renderQueue = NULL; + +void renderQueue_init(void) +{ + l_renderQueue = ll_new(); +} + +void renderQueue_clear(void) +{ + RenderCommand * cmd; + while(ll_shift(l_renderQueue, (void **)&cmd)) + { + if (cmd->op == SPICE_OP_DRAW_BITMAP) + free(cmd->drawBitmap.data); + free(cmd); + } +} + +void renderQueue_spiceConfigure(int width, int height) +{ + RenderCommand * cmd = malloc(sizeof(*cmd)); + cmd->op = SPICE_OP_CONFIGURE; + cmd->configure.width = width; + cmd->configure.height = height; + ll_push(l_renderQueue, cmd); + app_invalidateWindow(true); +} + +void renderQueue_spiceDrawFill(int x, int y, int width, int height, + uint32_t color) +{ + RenderCommand * cmd = malloc(sizeof(*cmd)); + cmd->op = SPICE_OP_DRAW_FILL; + cmd->fillRect.x = x; + cmd->fillRect.y = y; + cmd->fillRect.width = width; + cmd->fillRect.height = height; + cmd->fillRect.color = color; + ll_push(l_renderQueue, cmd); + app_invalidateWindow(true); +} + +void renderQueue_spiceDrawBitmap(int x, int y, int width, int height, int stride, + void * data) +{ + RenderCommand * cmd = malloc(sizeof(*cmd)); + cmd->op = SPICE_OP_DRAW_BITMAP; + cmd->drawBitmap.x = x; + cmd->drawBitmap.y = y; + cmd->drawBitmap.width = width; + cmd->drawBitmap.height = height; + cmd->drawBitmap.stride = stride; + cmd->drawBitmap.data = malloc(height * stride); + memcpy(cmd->drawBitmap.data, data, height * stride); + ll_push(l_renderQueue, cmd); + app_invalidateWindow(true); +} + +void renderQueue_free(void) +{ + renderQueue_clear(); + ll_free(l_renderQueue); +} + +void renderQueue_process(void) +{ + RenderCommand * cmd; + while(ll_shift(l_renderQueue, (void **)&cmd)) + { + switch(cmd->op) + { + case SPICE_OP_CONFIGURE: + RENDERER(spiceConfigure, + cmd->configure.width, cmd->configure.height); + break; + + case SPICE_OP_DRAW_FILL: + RENDERER(spiceDrawFill, + cmd->fillRect.x , cmd->fillRect.y, + cmd->fillRect.width, cmd->fillRect.height, + cmd->fillRect.color); + break; + + case SPICE_OP_DRAW_BITMAP: + RENDERER(spiceDrawBitmap, + cmd->drawBitmap.x , cmd->drawBitmap.y, + cmd->drawBitmap.width , cmd->drawBitmap.height, + cmd->drawBitmap.stride, cmd->drawBitmap.data); + free(cmd->drawBitmap.data); + break; + } + free(cmd); + } +} diff --git a/client/src/render_queue.h b/client/src/render_queue.h new file mode 100644 index 00000000..d86545f5 --- /dev/null +++ b/client/src/render_queue.h @@ -0,0 +1,72 @@ +/** + * 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 "common/ll.h" + +typedef struct +{ + enum + { + SPICE_OP_CONFIGURE, + SPICE_OP_DRAW_FILL, + SPICE_OP_DRAW_BITMAP + } + op; + + union + { + struct + { + int width, height; + } + configure; + + struct + { + int x, y; + int width, height; + uint32_t color; + } + fillRect; + + struct + { + int x , y; + int width, height; + int stride; + uint8_t * data; + } + drawBitmap; + }; +} +RenderCommand; + +void renderQueue_init(void); +void renderQueue_free(void); +void renderQueue_clear(void); +void renderQueue_process(void); + +void renderQueue_spiceConfigure(int width, int height); + +void renderQueue_spiceDrawFill(int x, int y, int width, int height, + uint32_t color); + +void renderQueue_spiceDrawBitmap(int x, int y, int width, int height, int stride, + void * data);