/** * Looking Glass * Copyright © 2017-2026 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 #include "common/debug.h" #include "common/ll.h" #include "main.h" #include "overlays.h" struct ll * l_renderQueue = NULL; static bool l_showSpice; static bool l_surfaceFormatValid; static bool l_rendererSupportsNativeHDR; static LG_RendererFormat l_surfaceFormat; static void updateSurfaceFormat(void) { if (!g_state.ds->setHDRImageDescription) return; LG_RendererFormat format = {}; if (l_surfaceFormatValid) format = l_surfaceFormat; if (l_showSpice) { // The SPICE display is always 8-bit SDR, regardless of the last format // received from the Looking Glass host. format.hdr = false; format.hdrPQ = false; format.hdrMetadata = false; g_state.ds->setHDRImageDescription(&format); atomic_store(&g_state.hdrDescFailed, false); return; } if (!l_surfaceFormatValid) return; if (format.hdr && !l_rendererSupportsNativeHDR) { format.hdr = false; format.hdrPQ = false; format.hdrMetadata = false; g_state.ds->setHDRImageDescription(&format); DEBUG_WARN("Renderer surface cannot represent the native HDR encoding; " "using software HDR mapping"); atomic_store(&g_state.hdrDescFailed, true); } else if (!g_state.ds->setHDRImageDescription(&format)) { DEBUG_WARN("Display server failed to apply HDR image description"); atomic_store(&g_state.hdrDescFailed, true); } else atomic_store(&g_state.hdrDescFailed, false); } void renderQueue_init(void) { l_renderQueue = ll_new(); l_showSpice = false; l_surfaceFormatValid = false; l_rendererSupportsNativeHDR = false; memset(&l_surfaceFormat, 0, sizeof(l_surfaceFormat)); } void renderQueue_free(void) { if (!l_renderQueue) return; renderQueue_clear(); ll_free(l_renderQueue); } void renderQueue_clear(void) { RenderCommand * cmd; while(ll_shift(l_renderQueue, (void **)&cmd)) { if (cmd->op == SPICE_OP_DRAW_BITMAP) free(cmd->spiceDrawBitmap.data); free(cmd); } } void renderQueue_spiceConfigure(int width, int height) { RenderCommand * cmd = malloc(sizeof(*cmd)); cmd->op = SPICE_OP_CONFIGURE; cmd->spiceConfigure.width = width; cmd->spiceConfigure.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->spiceFillRect.x = x; cmd->spiceFillRect.y = y; cmd->spiceFillRect.width = width; cmd->spiceFillRect.height = height; cmd->spiceFillRect.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, bool topDown) { if (width <= 0 || height <= 0 || stride <= 0) { if (width < 0 || height < 0 || stride < 0) DEBUG_ERROR("Invalid SPICE bitmap dimensions: %dx%d, stride: %d", width, height, stride); return; } if (!data) { DEBUG_ERROR("SPICE bitmap data is NULL"); return; } if ((size_t)height > SIZE_MAX / (size_t)stride) { DEBUG_ERROR("SPICE bitmap size overflows: height: %d, stride: %d", height, stride); return; } const size_t size = (size_t)height * (size_t)stride; RenderCommand * cmd = malloc(sizeof(*cmd)); if (!cmd) { DEBUG_ERROR("Failed to allocate SPICE bitmap command"); return; } uint8_t * copy = malloc(size); if (!copy) { DEBUG_ERROR("Failed to allocate %zu bytes for SPICE bitmap", size); free(cmd); return; } memcpy(copy, data, size); cmd->op = SPICE_OP_DRAW_BITMAP; cmd->spiceDrawBitmap.x = x; cmd->spiceDrawBitmap.y = y; cmd->spiceDrawBitmap.width = width; cmd->spiceDrawBitmap.height = height; cmd->spiceDrawBitmap.stride = stride; cmd->spiceDrawBitmap.data = copy; cmd->spiceDrawBitmap.topDown = topDown; if (!ll_push(l_renderQueue, cmd)) { free(copy); free(cmd); return; } app_invalidateWindow(true); } void renderQueue_spiceShow(bool show) { RenderCommand * cmd = malloc(sizeof(*cmd)); cmd->op = SPICE_OP_SHOW; cmd->spiceShow.show = show; ll_push(l_renderQueue, cmd); app_invalidateWindow(true); } void renderQueue_surfaceFormat(const LG_RendererFormat format, bool rendererSupportsNativeHDR) { RenderCommand * cmd = malloc(sizeof(*cmd)); cmd->op = SURFACE_OP_FORMAT; cmd->surfaceFormat.format = format; cmd->surfaceFormat.rendererSupportsNativeHDR = rendererSupportsNativeHDR; ll_push(l_renderQueue, cmd); app_invalidateWindow(true); } void renderQueue_cursorState(bool visible, int x, int y, int hx, int hy) { RenderCommand * cmd = malloc(sizeof(*cmd)); cmd->op = CURSOR_OP_STATE; cmd->cursorState.visible = visible; cmd->cursorState.x = x; cmd->cursorState.y = y; cmd->cursorState.hx = hx; cmd->cursorState.hy = hy; ll_push(l_renderQueue, cmd); } void renderQueue_cursorImage(bool monochrome, int width, int height, int pitch, uint8_t * data) { RenderCommand * cmd = malloc(sizeof(*cmd)); cmd->op = CURSOR_OP_IMAGE; cmd->cursorImage.monochrome = monochrome; cmd->cursorImage.width = width; cmd->cursorImage.height = height; cmd->cursorImage.pitch = pitch; cmd->cursorImage.data = data; ll_push(l_renderQueue, cmd); } void renderQueue_process(void) { RenderCommand * cmd; while(ll_shift(l_renderQueue, (void **)&cmd)) { switch(cmd->op) { case SPICE_OP_CONFIGURE: RENDERER(spiceConfigure, cmd->spiceConfigure.width, cmd->spiceConfigure.height); break; case SPICE_OP_DRAW_FILL: RENDERER(spiceDrawFill, cmd->spiceFillRect.x , cmd->spiceFillRect.y, cmd->spiceFillRect.width, cmd->spiceFillRect.height, cmd->spiceFillRect.color); break; case SPICE_OP_DRAW_BITMAP: RENDERER(spiceDrawBitmap, cmd->spiceDrawBitmap.x , cmd->spiceDrawBitmap.y, cmd->spiceDrawBitmap.width , cmd->spiceDrawBitmap.height, cmd->spiceDrawBitmap.stride, cmd->spiceDrawBitmap.data, cmd->spiceDrawBitmap.topDown); free(cmd->spiceDrawBitmap.data); break; case SPICE_OP_SHOW: l_showSpice = cmd->spiceShow.show; RENDERER(spiceShow, cmd->spiceShow.show); updateSurfaceFormat(); if (cmd->spiceShow.show) overlaySplash_show(false); break; case SURFACE_OP_FORMAT: l_surfaceFormat = cmd->surfaceFormat.format; l_surfaceFormatValid = true; l_rendererSupportsNativeHDR = cmd->surfaceFormat.rendererSupportsNativeHDR; updateSurfaceFormat(); break; case CURSOR_OP_STATE: RENDERER(onMouseEvent, cmd->cursorState.visible, cmd->cursorState.x, cmd->cursorState.y, cmd->cursorState.hx, cmd->cursorState.hy); break; case CURSOR_OP_IMAGE: RENDERER(onMouseShape, cmd->cursorImage.monochrome ? LG_CURSOR_MONOCHROME : LG_CURSOR_COLOR, cmd->cursorImage.width, cmd->cursorImage.height, cmd->cursorImage.pitch, cmd->cursorImage.data); free(cmd->cursorImage.data); } free(cmd); } }