mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-02 05:12:02 +00:00
[client] transport: introduce pluggable frame transports
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled
Move shared-memory ownership, LGMP session handling, queue access, and DMA setup behind a transport interface. The LGMP backend preserves the existing zero-copy frame and DMA paths while owning its lgmp:* options. Expose the initialized EGL context through a versioned renderer interop record for future accelerated decode backends. Add an LGMP-independent, deterministic test transport for graphics-pipeline validation.
This commit is contained in:
@@ -33,6 +33,10 @@ add_feature_info(ENABLE_OPENGL ENABLE_OPENGL "Legacy OpenGL renderer.")
|
|||||||
option(ENABLE_EGL "Enable the EGL renderer" ON)
|
option(ENABLE_EGL "Enable the EGL renderer" ON)
|
||||||
add_feature_info(ENABLE_EGL ENABLE_EGL "EGL renderer.")
|
add_feature_info(ENABLE_EGL ENABLE_EGL "EGL renderer.")
|
||||||
|
|
||||||
|
option(ENABLE_TEST_TRANSPORT "Enable the test transport" OFF)
|
||||||
|
add_feature_info(ENABLE_TEST_TRANSPORT ENABLE_TEST_TRANSPORT
|
||||||
|
"Synthetic frames for graphics-pipeline validation.")
|
||||||
|
|
||||||
option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON)
|
option(ENABLE_BACKTRACE "Enable backtrace support on crash" ON)
|
||||||
add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.")
|
add_feature_info(ENABLE_BACKTRACE ENABLE_BACKTRACE "Backtrace support.")
|
||||||
|
|
||||||
@@ -134,6 +138,7 @@ set(SOURCES
|
|||||||
src/overlay_utils.c
|
src/overlay_utils.c
|
||||||
src/render_queue.c
|
src/render_queue.c
|
||||||
src/evdev.c
|
src/evdev.c
|
||||||
|
src/transport.c
|
||||||
|
|
||||||
src/overlay/splash.c
|
src/overlay/splash.c
|
||||||
src/overlay/alert.c
|
src/overlay/alert.c
|
||||||
@@ -158,6 +163,7 @@ add_subdirectory("${PROJECT_TOP}/repos/cimgui" "${CMAKE_BINARY_DIR}/cimgui" E
|
|||||||
|
|
||||||
add_subdirectory(displayservers)
|
add_subdirectory(displayservers)
|
||||||
add_subdirectory(renderers)
|
add_subdirectory(renderers)
|
||||||
|
add_subdirectory(transports)
|
||||||
|
|
||||||
configure_file("${PROJECT_TOP}/resources/looking-glass-client.desktop.in" "${CMAKE_BINARY_DIR}/resources/looking-glass-client.desktop" @ONLY)
|
configure_file("${PROJECT_TOP}/resources/looking-glass-client.desktop.in" "${CMAKE_BINARY_DIR}/resources/looking-glass-client.desktop" @ONLY)
|
||||||
add_executable(looking-glass-client ${SOURCES})
|
add_executable(looking-glass-client ${SOURCES})
|
||||||
@@ -191,9 +197,9 @@ target_link_libraries(looking-glass-client
|
|||||||
lg_resources
|
lg_resources
|
||||||
lg_common
|
lg_common
|
||||||
displayservers
|
displayservers
|
||||||
lgmp
|
|
||||||
purespice
|
purespice
|
||||||
renderers
|
renderers
|
||||||
|
transports
|
||||||
cimgui
|
cimgui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
#include "common/KVMFR.h"
|
#include "common/types.h"
|
||||||
#include "common/framebuffer.h"
|
#include "common/framebuffer.h"
|
||||||
|
|
||||||
#define IS_LG_RENDERER_VALID(x) \
|
#define IS_LG_RENDERER_VALID(x) \
|
||||||
@@ -88,7 +88,7 @@ typedef struct LG_RendererFormat
|
|||||||
unsigned int bpp; // bits per pixel (zero if compressed)
|
unsigned int bpp; // bits per pixel (zero if compressed)
|
||||||
LG_RendererRotate rotate; // guest rotation
|
LG_RendererRotate rotate; // guest rotation
|
||||||
|
|
||||||
// HDR static metadata (from KVMFRFrame, valid when hdrMetadata is true)
|
// HDR static metadata, valid when hdrMetadata is true
|
||||||
uint16_t hdrDisplayPrimary[3][2];
|
uint16_t hdrDisplayPrimary[3][2];
|
||||||
uint16_t hdrWhitePoint[2];
|
uint16_t hdrWhitePoint[2];
|
||||||
uint32_t hdrMaxDisplayLuminance;
|
uint32_t hdrMaxDisplayLuminance;
|
||||||
@@ -119,6 +119,31 @@ LG_RendererCursor;
|
|||||||
|
|
||||||
typedef struct LG_Renderer LG_Renderer;
|
typedef struct LG_Renderer LG_Renderer;
|
||||||
|
|
||||||
|
typedef enum LG_RendererInteropType
|
||||||
|
{
|
||||||
|
LG_RENDERER_INTEROP_NONE,
|
||||||
|
LG_RENDERER_INTEROP_EGL,
|
||||||
|
}
|
||||||
|
LG_RendererInteropType;
|
||||||
|
|
||||||
|
#define LG_RENDERER_INTEROP_VERSION 1
|
||||||
|
|
||||||
|
typedef struct LG_RendererInterop
|
||||||
|
{
|
||||||
|
uint32_t version;
|
||||||
|
uint32_t size;
|
||||||
|
LG_RendererInteropType type;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
EGLDisplay display;
|
||||||
|
EGLConfig config;
|
||||||
|
EGLContext shareContext;
|
||||||
|
bool dmaBufImport;
|
||||||
|
}
|
||||||
|
egl;
|
||||||
|
}
|
||||||
|
LG_RendererInterop;
|
||||||
|
|
||||||
typedef struct LG_RendererOps
|
typedef struct LG_RendererOps
|
||||||
{
|
{
|
||||||
/* returns the friendly name of the renderer */
|
/* returns the friendly name of the renderer */
|
||||||
@@ -145,6 +170,11 @@ typedef struct LG_RendererOps
|
|||||||
* Context: renderThread */
|
* Context: renderThread */
|
||||||
bool (*supports)(LG_Renderer * renderer, LG_RendererSupport support);
|
bool (*supports)(LG_Renderer * renderer, LG_RendererSupport support);
|
||||||
|
|
||||||
|
/* optionally exposes the initialized renderer device context to a transport.
|
||||||
|
* The transport must create its own shared context and release it before the
|
||||||
|
* renderer is deinitialized. Context: lg_run */
|
||||||
|
bool (*getInterop)(LG_Renderer * renderer, LG_RendererInterop * interop);
|
||||||
|
|
||||||
/* called when the renderer is to reset it's state
|
/* called when the renderer is to reset it's state
|
||||||
* Context: lg_run & frameThread */
|
* Context: lg_run & frameThread */
|
||||||
void (*onRestart)(LG_Renderer * renderer);
|
void (*onRestart)(LG_Renderer * renderer);
|
||||||
@@ -163,7 +193,7 @@ typedef struct LG_RendererOps
|
|||||||
/* optional display calibration update for hardware cursor composition
|
/* optional display calibration update for hardware cursor composition
|
||||||
* Context: cursorThread */
|
* Context: cursorThread */
|
||||||
void (*onMouseColorTransform)(LG_Renderer * renderer,
|
void (*onMouseColorTransform)(LG_Renderer * renderer,
|
||||||
const KVMFRColorTransform * transform);
|
const LGColorTransform * transform);
|
||||||
|
|
||||||
/* updates the cursor-specific SDR white level reported by IddCx
|
/* updates the cursor-specific SDR white level reported by IddCx
|
||||||
* Context: cursorThread */
|
* Context: cursorThread */
|
||||||
|
|||||||
222
client/include/interface/transport.h
Normal file
222
client/include/interface/transport.h
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _H_LG_CLIENT_TRANSPORT_
|
||||||
|
#define _H_LG_CLIENT_TRANSPORT_
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "common/framebuffer.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
#define LG_TRANSPORT_MAX_DAMAGE_RECTS LG_MAX_FRAME_DAMAGE_RECTS
|
||||||
|
|
||||||
|
typedef struct LG_Transport LG_Transport;
|
||||||
|
typedef struct LG_RendererInterop LG_RendererInterop;
|
||||||
|
|
||||||
|
typedef enum LG_TransportStatus
|
||||||
|
{
|
||||||
|
LG_TRANSPORT_OK,
|
||||||
|
LG_TRANSPORT_TIMEOUT,
|
||||||
|
LG_TRANSPORT_UNAVAILABLE,
|
||||||
|
LG_TRANSPORT_INVALID_VERSION,
|
||||||
|
LG_TRANSPORT_DISCONNECTED,
|
||||||
|
LG_TRANSPORT_END,
|
||||||
|
LG_TRANSPORT_ERROR,
|
||||||
|
}
|
||||||
|
LG_TransportStatus;
|
||||||
|
|
||||||
|
typedef enum LG_TransportGuestOS
|
||||||
|
{
|
||||||
|
LG_TRANSPORT_OS_LINUX,
|
||||||
|
LG_TRANSPORT_OS_BSD,
|
||||||
|
LG_TRANSPORT_OS_OSX,
|
||||||
|
LG_TRANSPORT_OS_WINDOWS,
|
||||||
|
LG_TRANSPORT_OS_OTHER,
|
||||||
|
}
|
||||||
|
LG_TransportGuestOS;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
LG_TRANSPORT_FEATURE_SET_CURSOR_POS = 0x1,
|
||||||
|
LG_TRANSPORT_FEATURE_WINDOW_SIZE = 0x2,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef uint32_t LG_TransportFeatureFlags;
|
||||||
|
|
||||||
|
typedef struct LG_TransportSession
|
||||||
|
{
|
||||||
|
char version[32];
|
||||||
|
LG_TransportFeatureFlags features;
|
||||||
|
|
||||||
|
bool uuidValid;
|
||||||
|
uint8_t uuid[16];
|
||||||
|
|
||||||
|
LG_TransportGuestOS os;
|
||||||
|
char osName[64];
|
||||||
|
char capture[32];
|
||||||
|
char cpuModel[256];
|
||||||
|
uint8_t cpus;
|
||||||
|
uint8_t cores;
|
||||||
|
uint8_t sockets;
|
||||||
|
|
||||||
|
uint32_t remoteVersion;
|
||||||
|
}
|
||||||
|
LG_TransportSession;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
LG_TRANSPORT_FRAME_BLOCK_SCREENSAVER = 0x1,
|
||||||
|
LG_TRANSPORT_FRAME_REQUEST_ACTIVATION = 0x2,
|
||||||
|
LG_TRANSPORT_FRAME_TRUNCATED = 0x4,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef uint32_t LG_TransportFrameFlags;
|
||||||
|
|
||||||
|
typedef struct LG_TransportFrameFormat
|
||||||
|
{
|
||||||
|
uint32_t version;
|
||||||
|
FrameType type;
|
||||||
|
uint32_t screenWidth;
|
||||||
|
uint32_t screenHeight;
|
||||||
|
uint32_t dataWidth;
|
||||||
|
uint32_t dataHeight;
|
||||||
|
uint32_t frameWidth;
|
||||||
|
uint32_t frameHeight;
|
||||||
|
FrameRotation rotation;
|
||||||
|
uint32_t stride;
|
||||||
|
uint32_t pitch;
|
||||||
|
|
||||||
|
bool hdr;
|
||||||
|
bool hdrPQ;
|
||||||
|
bool hdrMetadata;
|
||||||
|
uint16_t hdrDisplayPrimary[3][2];
|
||||||
|
uint16_t hdrWhitePoint[2];
|
||||||
|
uint32_t hdrMaxDisplayLuminance;
|
||||||
|
uint32_t hdrMinDisplayLuminance;
|
||||||
|
uint32_t hdrMaxContentLightLevel;
|
||||||
|
uint32_t hdrMaxFrameAverageLightLevel;
|
||||||
|
uint32_t sdrWhiteLevel;
|
||||||
|
}
|
||||||
|
LG_TransportFrameFormat;
|
||||||
|
|
||||||
|
typedef struct LG_TransportFrame
|
||||||
|
{
|
||||||
|
uint64_t serial;
|
||||||
|
uint64_t timestamp;
|
||||||
|
LG_TransportFrameFlags flags;
|
||||||
|
// Backend-owned immutable metadata, valid until releaseFrame.
|
||||||
|
const LG_TransportFrameFormat * format;
|
||||||
|
|
||||||
|
const FrameBuffer * framebuffer;
|
||||||
|
int dmaFD;
|
||||||
|
const FrameDamageRect * damageRects;
|
||||||
|
uint32_t damageRectsCount;
|
||||||
|
}
|
||||||
|
LG_TransportFrame;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
LG_TRANSPORT_POINTER_POSITION = 0x1,
|
||||||
|
LG_TRANSPORT_POINTER_VISIBLE = 0x2,
|
||||||
|
LG_TRANSPORT_POINTER_SHAPE = 0x4,
|
||||||
|
LG_TRANSPORT_POINTER_COLOR_TRANSFORM = 0x8,
|
||||||
|
LG_TRANSPORT_POINTER_VISIBLE_VALID = 0x10,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef uint32_t LG_TransportPointerFlags;
|
||||||
|
|
||||||
|
typedef struct LG_TransportPointer
|
||||||
|
{
|
||||||
|
LG_TransportPointerFlags flags;
|
||||||
|
int16_t x;
|
||||||
|
int16_t y;
|
||||||
|
CursorType type;
|
||||||
|
int8_t hx;
|
||||||
|
int8_t hy;
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t pitch;
|
||||||
|
uint32_t sdrWhiteLevel;
|
||||||
|
const uint8_t * shape;
|
||||||
|
const LGColorTransform * colorTransform;
|
||||||
|
}
|
||||||
|
LG_TransportPointer;
|
||||||
|
|
||||||
|
typedef enum LG_TransportControlType
|
||||||
|
{
|
||||||
|
LG_TRANSPORT_CONTROL_SET_CURSOR_POS,
|
||||||
|
LG_TRANSPORT_CONTROL_WINDOW_SIZE,
|
||||||
|
}
|
||||||
|
LG_TransportControlType;
|
||||||
|
|
||||||
|
typedef struct LG_TransportControl
|
||||||
|
{
|
||||||
|
LG_TransportControlType type;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct { int32_t x, y; } cursorPos;
|
||||||
|
struct { uint32_t width, height; } windowSize;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
LG_TransportControl;
|
||||||
|
|
||||||
|
typedef uint64_t LG_TransportControlToken;
|
||||||
|
|
||||||
|
typedef struct LG_TransportOps
|
||||||
|
{
|
||||||
|
const char * name;
|
||||||
|
void (*setup)(void);
|
||||||
|
bool (*create)(LG_Transport ** transport);
|
||||||
|
void (*destroy)(LG_Transport ** transport);
|
||||||
|
|
||||||
|
LG_TransportStatus (*connect)(LG_Transport * transport,
|
||||||
|
LG_TransportSession * session);
|
||||||
|
void (*disconnect)(LG_Transport * transport);
|
||||||
|
bool (*sessionValid)(LG_Transport * transport);
|
||||||
|
bool (*supportsDMA)(LG_Transport * transport);
|
||||||
|
bool (*attachRenderer)(LG_Transport * transport,
|
||||||
|
const LG_RendererInterop * interop);
|
||||||
|
void (*detachRenderer)(LG_Transport * transport);
|
||||||
|
|
||||||
|
LG_TransportStatus (*nextFrame)(LG_Transport * transport, bool useDMA,
|
||||||
|
LG_TransportFrame * frame);
|
||||||
|
void (*releaseFrame)(LG_Transport * transport, LG_TransportFrame * frame);
|
||||||
|
|
||||||
|
LG_TransportStatus (*nextPointer)(LG_Transport * transport,
|
||||||
|
LG_TransportPointer * pointer);
|
||||||
|
void (*releasePointer)(LG_Transport * transport,
|
||||||
|
LG_TransportPointer * pointer);
|
||||||
|
|
||||||
|
LG_TransportStatus (*sendControl)(LG_Transport * transport,
|
||||||
|
const LG_TransportControl * control, LG_TransportControlToken * token);
|
||||||
|
LG_TransportStatus (*controlStatus)(LG_Transport * transport,
|
||||||
|
LG_TransportControlToken token);
|
||||||
|
}
|
||||||
|
LG_TransportOps;
|
||||||
|
|
||||||
|
void lgTransport_setup(void);
|
||||||
|
bool lgTransport_isValid(const char * name);
|
||||||
|
bool lgTransport_create(const char * name, LG_Transport ** transport,
|
||||||
|
const LG_TransportOps ** ops);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -101,8 +101,8 @@ struct EGL_Cursor
|
|||||||
_Atomic(float) mapHDRContentPeak;
|
_Atomic(float) mapHDRContentPeak;
|
||||||
|
|
||||||
bool colorTransformUpdate;
|
bool colorTransformUpdate;
|
||||||
KVMFRColorTransform pendingColorTransform;
|
LGColorTransform pendingColorTransform;
|
||||||
KVMFRColorTransform activeColorTransform;
|
LGColorTransform activeColorTransform;
|
||||||
GLuint colorLUT;
|
GLuint colorLUT;
|
||||||
|
|
||||||
struct CursorTex norm;
|
struct CursorTex norm;
|
||||||
@@ -239,7 +239,7 @@ bool egl_cursorInit(EGL_Cursor ** cursor)
|
|||||||
atomic_init(&(*cursor)->scale , 1.0f );
|
atomic_init(&(*cursor)->scale , 1.0f );
|
||||||
atomic_init(&(*cursor)->sourceTransfer , CURSOR_TRANSFER_SRGB);
|
atomic_init(&(*cursor)->sourceTransfer , CURSOR_TRANSFER_SRGB);
|
||||||
atomic_init(&(*cursor)->sdrWhiteLevel ,
|
atomic_init(&(*cursor)->sdrWhiteLevel ,
|
||||||
KVMFR_SDR_WHITE_LEVEL_DEFAULT);
|
LG_SDR_WHITE_LEVEL_DEFAULT);
|
||||||
atomic_init(&(*cursor)->mapHDRtoSDR , false);
|
atomic_init(&(*cursor)->mapHDRtoSDR , false);
|
||||||
atomic_init(&(*cursor)->mapHDRGain , 1.0f );
|
atomic_init(&(*cursor)->mapHDRGain , 1.0f );
|
||||||
atomic_init(&(*cursor)->mapHDRContentPeak, 1.0f );
|
atomic_init(&(*cursor)->mapHDRContentPeak, 1.0f );
|
||||||
@@ -301,7 +301,7 @@ bool egl_cursorSetShape(EGL_Cursor * cursor, const LG_RendererCursor type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void egl_cursorSetColorTransform(EGL_Cursor * cursor,
|
void egl_cursorSetColorTransform(EGL_Cursor * cursor,
|
||||||
const KVMFRColorTransform * transform)
|
const LGColorTransform * transform)
|
||||||
{
|
{
|
||||||
LG_LOCK(cursor->lock);
|
LG_LOCK(cursor->lock);
|
||||||
cursor->pendingColorTransform = *transform;
|
cursor->pendingColorTransform = *transform;
|
||||||
@@ -351,7 +351,7 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
|
|||||||
cursor->activeColorTransform = cursor->pendingColorTransform;
|
cursor->activeColorTransform = cursor->pendingColorTransform;
|
||||||
cursor->colorTransformUpdate = false;
|
cursor->colorTransformUpdate = false;
|
||||||
|
|
||||||
if (cursor->activeColorTransform.flags & KVMFR_COLOR_TRANSFORM_LUT)
|
if (cursor->activeColorTransform.flags & LG_COLOR_TRANSFORM_LUT)
|
||||||
{
|
{
|
||||||
if (!cursor->colorLUT)
|
if (!cursor->colorLUT)
|
||||||
glGenTextures(1, &cursor->colorLUT);
|
glGenTextures(1, &cursor->colorLUT);
|
||||||
@@ -568,5 +568,5 @@ void egl_cursorSetHDRState(EGL_Cursor * cursor, bool sourceHDR,
|
|||||||
void egl_cursorSetSDRWhiteLevel(EGL_Cursor * cursor, float sdrWhiteLevel)
|
void egl_cursorSetSDRWhiteLevel(EGL_Cursor * cursor, float sdrWhiteLevel)
|
||||||
{
|
{
|
||||||
atomic_store(&cursor->sdrWhiteLevel, sdrWhiteLevel > 0.0f ?
|
atomic_store(&cursor->sdrWhiteLevel, sdrWhiteLevel > 0.0f ?
|
||||||
sdrWhiteLevel : KVMFR_SDR_WHITE_LEVEL_DEFAULT);
|
sdrWhiteLevel : LG_SDR_WHITE_LEVEL_DEFAULT);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ bool egl_cursorSetShape(
|
|||||||
const uint8_t * data);
|
const uint8_t * data);
|
||||||
|
|
||||||
void egl_cursorSetColorTransform(EGL_Cursor * cursor,
|
void egl_cursorSetColorTransform(EGL_Cursor * cursor,
|
||||||
const KVMFRColorTransform * transform);
|
const LGColorTransform * transform);
|
||||||
|
|
||||||
void egl_cursorSetSize(EGL_Cursor * cursor, const float x, const float y);
|
void egl_cursorSetSize(EGL_Cursor * cursor, const float x, const float y);
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
#include "damage.h"
|
#include "damage.h"
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/KVMFR.h"
|
|
||||||
#include "common/locking.h"
|
#include "common/locking.h"
|
||||||
|
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
@@ -89,7 +88,7 @@ bool egl_damageInit(EGL_Damage ** damage)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!egl_desktopRectsInit(&(*damage)->mesh, KVMFR_MAX_DAMAGE_RECTS))
|
if (!egl_desktopRectsInit(&(*damage)->mesh, LG_MAX_FRAME_DAMAGE_RECTS))
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Failed to initialize the mesh");
|
DEBUG_ERROR("Failed to initialize the mesh");
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -21,14 +21,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include "common/KVMFR.h"
|
#include "common/types.h"
|
||||||
#include "interface/renderer.h"
|
#include "interface/renderer.h"
|
||||||
#include "desktop_rects.h"
|
#include "desktop_rects.h"
|
||||||
|
|
||||||
struct DesktopDamage
|
struct DesktopDamage
|
||||||
{
|
{
|
||||||
int count;
|
int count;
|
||||||
FrameDamageRect rects[KVMFR_MAX_DAMAGE_RECTS];
|
FrameDamageRect rects[LG_MAX_FRAME_DAMAGE_RECTS];
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct EGL_Damage EGL_Damage;
|
typedef struct EGL_Damage EGL_Damage;
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
#include "desktop_rects.h"
|
#include "desktop_rects.h"
|
||||||
#include "state.h"
|
#include "state.h"
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/KVMFR.h"
|
|
||||||
#include "common/locking.h"
|
#include "common/locking.h"
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
#include "common/util.h"
|
#include "common/util.h"
|
||||||
#include "common/array.h"
|
#include "common/array.h"
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/KVMFR.h"
|
|
||||||
#include "common/option.h"
|
#include "common/option.h"
|
||||||
#include "common/sysinfo.h"
|
#include "common/sysinfo.h"
|
||||||
#include "common/rects.h"
|
#include "common/rects.h"
|
||||||
@@ -57,7 +56,7 @@
|
|||||||
#define MAX_BUFFER_AGE 3
|
#define MAX_BUFFER_AGE 3
|
||||||
#define DESKTOP_DAMAGE_COUNT 4
|
#define DESKTOP_DAMAGE_COUNT 4
|
||||||
#define DESKTOP_DAMAGE_MARGIN 1
|
#define DESKTOP_DAMAGE_MARGIN 1
|
||||||
#define MAX_ACCUMULATED_DAMAGE ((KVMFR_MAX_DAMAGE_RECTS + MAX_OVERLAY_RECTS + 2) * MAX_BUFFER_AGE)
|
#define MAX_ACCUMULATED_DAMAGE ((LG_MAX_FRAME_DAMAGE_RECTS + MAX_OVERLAY_RECTS + 2) * MAX_BUFFER_AGE)
|
||||||
#define IDX_AGO(counter, i, total) (((counter) + (total) - (i)) % (total))
|
#define IDX_AGO(counter, i, total) (((counter) + (total) - (i)) % (total))
|
||||||
|
|
||||||
struct Options
|
struct Options
|
||||||
@@ -361,6 +360,23 @@ static bool egl_supports(LG_Renderer * renderer, LG_RendererSupport flag)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool egl_getInterop(LG_Renderer * renderer,
|
||||||
|
LG_RendererInterop * interop)
|
||||||
|
{
|
||||||
|
struct Inst * this = UPCAST(struct Inst, renderer);
|
||||||
|
if (!this->display || !this->context || !this->configs ||
|
||||||
|
interop->size < sizeof(*interop))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
interop->version = LG_RENDERER_INTEROP_VERSION;
|
||||||
|
interop->type = LG_RENDERER_INTEROP_EGL;
|
||||||
|
interop->egl.display = this->display;
|
||||||
|
interop->egl.config = this->configs;
|
||||||
|
interop->egl.shareContext = this->context;
|
||||||
|
interop->egl.dmaBufImport = this->dmaSupport;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void egl_onRestart(LG_Renderer * renderer)
|
static void egl_onRestart(LG_Renderer * renderer)
|
||||||
{
|
{
|
||||||
struct Inst * this = UPCAST(struct Inst, renderer);
|
struct Inst * this = UPCAST(struct Inst, renderer);
|
||||||
@@ -598,7 +614,7 @@ static bool egl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cur
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void egl_onMouseColorTransform(LG_Renderer * renderer,
|
static void egl_onMouseColorTransform(LG_Renderer * renderer,
|
||||||
const KVMFRColorTransform * transform)
|
const LGColorTransform * transform)
|
||||||
{
|
{
|
||||||
struct Inst * this = UPCAST(struct Inst, renderer);
|
struct Inst * this = UPCAST(struct Inst, renderer);
|
||||||
egl_cursorSetColorTransform(this->cursor, transform);
|
egl_cursorSetColorTransform(this->cursor, transform);
|
||||||
@@ -753,7 +769,7 @@ static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int d
|
|||||||
if (unlikely(
|
if (unlikely(
|
||||||
damage->count == -1 ||
|
damage->count == -1 ||
|
||||||
damageRectsCount == 0 ||
|
damageRectsCount == 0 ||
|
||||||
damage->count + damageRectsCount >= KVMFR_MAX_DAMAGE_RECTS))
|
damage->count + damageRectsCount >= LG_MAX_FRAME_DAMAGE_RECTS))
|
||||||
{
|
{
|
||||||
damage->count = -1;
|
damage->count = -1;
|
||||||
}
|
}
|
||||||
@@ -1441,7 +1457,7 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
|||||||
egl_damageRender(this->damage, rotate, newFrame ? desktopDamage : NULL) |
|
egl_damageRender(this->damage, rotate, newFrame ? desktopDamage : NULL) |
|
||||||
invalidateWindow;
|
invalidateWindow;
|
||||||
|
|
||||||
struct Rect damage[KVMFR_MAX_DAMAGE_RECTS + MAX_OVERLAY_RECTS + 2];
|
struct Rect damage[LG_MAX_FRAME_DAMAGE_RECTS + MAX_OVERLAY_RECTS + 2];
|
||||||
int damageIdx = app_renderOverlay(damage, MAX_OVERLAY_RECTS);
|
int damageIdx = app_renderOverlay(damage, MAX_OVERLAY_RECTS);
|
||||||
if (unlikely(damageIdx != 0))
|
if (unlikely(damageIdx != 0))
|
||||||
{
|
{
|
||||||
@@ -1631,6 +1647,7 @@ struct LG_RendererOps LGR_EGL =
|
|||||||
.initialize = egl_initialize,
|
.initialize = egl_initialize,
|
||||||
.deinitialize = egl_deinitialize,
|
.deinitialize = egl_deinitialize,
|
||||||
.supports = egl_supports,
|
.supports = egl_supports,
|
||||||
|
.getInterop = egl_getInterop,
|
||||||
.onRestart = egl_onRestart,
|
.onRestart = egl_onRestart,
|
||||||
.onResize = egl_onResize,
|
.onResize = egl_onResize,
|
||||||
.onMouseShape = egl_onMouseShape,
|
.onMouseShape = egl_onMouseShape,
|
||||||
|
|||||||
@@ -22,13 +22,12 @@
|
|||||||
|
|
||||||
#include "texture_buffer.h"
|
#include "texture_buffer.h"
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/KVMFR.h"
|
|
||||||
#include "common/rects.h"
|
#include "common/rects.h"
|
||||||
|
|
||||||
struct TexDamage
|
struct TexDamage
|
||||||
{
|
{
|
||||||
int count;
|
int count;
|
||||||
FrameDamageRect rects[KVMFR_MAX_DAMAGE_RECTS];
|
FrameDamageRect rects[LG_MAX_FRAME_DAMAGE_RECTS];
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct TexFB
|
typedef struct TexFB
|
||||||
@@ -90,7 +89,7 @@ static bool egl_texFBUpdate(EGL_Texture * texture, const EGL_TexUpdate * update)
|
|||||||
|
|
||||||
struct TexDamage * damage = this->damage + parent->bufIndex;
|
struct TexDamage * damage = this->damage + parent->bufIndex;
|
||||||
bool damageAll = !update->rects || update->rectCount == 0 || damage->count < 0 ||
|
bool damageAll = !update->rects || update->rectCount == 0 || damage->count < 0 ||
|
||||||
damage->count + update->rectCount > KVMFR_MAX_DAMAGE_RECTS;
|
damage->count + update->rectCount > LG_MAX_FRAME_DAMAGE_RECTS;
|
||||||
bool complete;
|
bool complete;
|
||||||
|
|
||||||
if (damageAll)
|
if (damageAll)
|
||||||
@@ -168,7 +167,7 @@ static bool egl_texFBUpdate(EGL_Texture * texture, const EGL_TexUpdate * update)
|
|||||||
if (i == parent->bufIndex)
|
if (i == parent->bufIndex)
|
||||||
damage->count = 0;
|
damage->count = 0;
|
||||||
else if (update->rects && update->rectCount > 0 && damage->count >= 0 &&
|
else if (update->rects && update->rectCount > 0 && damage->count >= 0 &&
|
||||||
damage->count + update->rectCount <= KVMFR_MAX_DAMAGE_RECTS)
|
damage->count + update->rectCount <= LG_MAX_FRAME_DAMAGE_RECTS)
|
||||||
{
|
{
|
||||||
memcpy(damage->rects + damage->count, update->rects,
|
memcpy(damage->rects + damage->count, update->rects,
|
||||||
update->rectCount * sizeof(FrameDamageRect));
|
update->rectCount * sizeof(FrameDamageRect));
|
||||||
|
|||||||
@@ -1174,27 +1174,27 @@ void app_invalidateOverlay(bool renderTwice)
|
|||||||
|
|
||||||
bool app_guestIsLinux(void)
|
bool app_guestIsLinux(void)
|
||||||
{
|
{
|
||||||
return g_state.guestOS == KVMFR_OS_LINUX;
|
return g_state.guestOS == LG_TRANSPORT_OS_LINUX;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool app_guestIsWindows(void)
|
bool app_guestIsWindows(void)
|
||||||
{
|
{
|
||||||
return g_state.guestOS == KVMFR_OS_WINDOWS;
|
return g_state.guestOS == LG_TRANSPORT_OS_WINDOWS;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool app_guestIsOSX(void)
|
bool app_guestIsOSX(void)
|
||||||
{
|
{
|
||||||
return g_state.guestOS == KVMFR_OS_OSX;
|
return g_state.guestOS == LG_TRANSPORT_OS_OSX;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool app_guestIsBSD(void)
|
bool app_guestIsBSD(void)
|
||||||
{
|
{
|
||||||
return g_state.guestOS == KVMFR_OS_BSD;
|
return g_state.guestOS == LG_TRANSPORT_OS_BSD;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool app_guestIsOther(void)
|
bool app_guestIsOther(void)
|
||||||
{
|
{
|
||||||
return g_state.guestOS == KVMFR_OS_OTHER;
|
return g_state.guestOS == LG_TRANSPORT_OS_OTHER;
|
||||||
}
|
}
|
||||||
|
|
||||||
void app_stopVideo(bool stop)
|
void app_stopVideo(bool stop)
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ static StringList optScancodeValues (struct Option * opt);
|
|||||||
static bool optScancodeValidate (struct Option * opt, const char ** error);
|
static bool optScancodeValidate (struct Option * opt, const char ** error);
|
||||||
static char * optScancodeToString (struct Option * opt);
|
static char * optScancodeToString (struct Option * opt);
|
||||||
static bool optRotateValidate (struct Option * opt, const char ** error);
|
static bool optRotateValidate (struct Option * opt, const char ** error);
|
||||||
|
static bool optTransportValidate (struct Option * opt, const char ** error);
|
||||||
static bool optMicDefaultParse (struct Option * opt, const char * str);
|
static bool optMicDefaultParse (struct Option * opt, const char * str);
|
||||||
static StringList optMicDefaultValues (struct Option * opt);
|
static StringList optMicDefaultValues (struct Option * opt);
|
||||||
static char * optMicDefaultToString(struct Option * opt);
|
static char * optMicDefaultToString(struct Option * opt);
|
||||||
@@ -87,25 +88,12 @@ static struct Option options[] =
|
|||||||
.value.x_bool = false,
|
.value.x_bool = false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.module = "app",
|
.module = "app",
|
||||||
.name = "cursorPollInterval",
|
.name = "transport",
|
||||||
.description = "How often to check for a cursor update in microseconds",
|
.description = "Transport backend to use",
|
||||||
.type = OPTION_TYPE_INT,
|
.type = OPTION_TYPE_STRING,
|
||||||
.value.x_int = 1000
|
.value.x_string = "lgmp",
|
||||||
},
|
.validator = optTransportValidate,
|
||||||
{
|
|
||||||
.module = "app",
|
|
||||||
.name = "framePollInterval",
|
|
||||||
.description = "How often to check for a frame update in microseconds",
|
|
||||||
.type = OPTION_TYPE_INT,
|
|
||||||
.value.x_int = 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.module = "app",
|
|
||||||
.name = "allowDMA",
|
|
||||||
.description = "Allow direct DMA transfers if supported (see `README.md` in the `module` dir)",
|
|
||||||
.type = OPTION_TYPE_BOOL,
|
|
||||||
.value.x_bool = true
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// window options
|
// window options
|
||||||
@@ -686,9 +674,7 @@ bool config_load(int argc, char * argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setup the application params for the basic types
|
// setup the application params for the basic types
|
||||||
g_params.cursorPollInterval = option_get_int ("app" , "cursorPollInterval");
|
g_params.transport = option_get_string("app", "transport" );
|
||||||
g_params.framePollInterval = option_get_int ("app" , "framePollInterval" );
|
|
||||||
g_params.allowDMA = option_get_bool ("app" , "allowDMA" );
|
|
||||||
|
|
||||||
g_params.windowTitle = option_get_string("win", "title" );
|
g_params.windowTitle = option_get_string("win", "title" );
|
||||||
g_params.appId = option_get_string("win", "appId" );
|
g_params.appId = option_get_string("win", "appId" );
|
||||||
@@ -1025,6 +1011,15 @@ static bool optRotateValidate(struct Option * opt, const char ** error)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool optTransportValidate(struct Option * opt, const char ** error)
|
||||||
|
{
|
||||||
|
if (lgTransport_isValid(opt->value.x_string))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
*error = "Unknown transport (expected lgmp or test)";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static bool optMicDefaultParse(struct Option * opt, const char * str)
|
static bool optMicDefaultParse(struct Option * opt, const char * str)
|
||||||
{
|
{
|
||||||
if (!str)
|
if (!str)
|
||||||
|
|||||||
@@ -192,29 +192,30 @@ bool core_warpPointer(int x, int y, bool exiting)
|
|||||||
|
|
||||||
void core_onWindowSizeChanged(unsigned width, unsigned height)
|
void core_onWindowSizeChanged(unsigned width, unsigned height)
|
||||||
{
|
{
|
||||||
if (!g_state.pointerQueue)
|
if (!g_state.transport ||
|
||||||
|
!(g_state.transportFeatures & LG_TRANSPORT_FEATURE_WINDOW_SIZE))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (g_state.srcSize.x == width && g_state.srcSize.y == height)
|
if (g_state.srcSize.x == width && g_state.srcSize.y == height)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const KVMFRWindowSize msg =
|
const LG_TransportControl control =
|
||||||
{
|
{
|
||||||
.msg.type = KVMFR_MESSAGE_WINDOWSIZE,
|
.type = LG_TRANSPORT_CONTROL_WINDOW_SIZE,
|
||||||
.w = width,
|
.windowSize = { .width = width, .height = height },
|
||||||
.h = height
|
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t serial;
|
LG_TransportControlToken token;
|
||||||
LGMP_STATUS status;
|
const LG_TransportStatus status = g_state.transportOps->sendControl(
|
||||||
if ((status = lgmpClientSendData(g_state.pointerQueue,
|
g_state.transport, &control, &token);
|
||||||
&msg, sizeof(msg), &serial)) != LGMP_OK)
|
if (status != LG_TRANSPORT_OK && status != LG_TRANSPORT_UNAVAILABLE)
|
||||||
DEBUG_WARN("Message send failed: %s", lgmpStatusString(status));
|
DEBUG_WARN("Window-size control failed with status %d", status);
|
||||||
}
|
}
|
||||||
|
|
||||||
void core_updatePositionInfo(void)
|
void core_updatePositionInfo(void)
|
||||||
{
|
{
|
||||||
if (g_params.setGuestRes && g_state.kvmfrFeatures & KVMFR_FEATURE_WINDOWSIZE)
|
if (g_params.setGuestRes &&
|
||||||
|
g_state.transportFeatures & LG_TRANSPORT_FEATURE_WINDOW_SIZE)
|
||||||
{
|
{
|
||||||
LGMsg msg =
|
LGMsg msg =
|
||||||
{
|
{
|
||||||
@@ -535,20 +536,19 @@ void core_handleMouseNormal(double ex, double ey)
|
|||||||
util_localCurToGuest(&guest);
|
util_localCurToGuest(&guest);
|
||||||
|
|
||||||
if (!g_state.stopVideo &&
|
if (!g_state.stopVideo &&
|
||||||
g_state.kvmfrFeatures & KVMFR_FEATURE_SETCURSORPOS)
|
g_state.transportFeatures & LG_TRANSPORT_FEATURE_SET_CURSOR_POS)
|
||||||
{
|
{
|
||||||
const KVMFRSetCursorPos msg = {
|
const LG_TransportControl control = {
|
||||||
.msg.type = KVMFR_MESSAGE_SETCURSORPOS,
|
.type = LG_TRANSPORT_CONTROL_SET_CURSOR_POS,
|
||||||
.x = round(guest.x),
|
.cursorPos = { .x = round(guest.x), .y = round(guest.y) },
|
||||||
.y = round(guest.y)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t setPosSerial;
|
LG_TransportControlToken token;
|
||||||
LGMP_STATUS status;
|
LG_TransportStatus status = g_state.transportOps->sendControl(
|
||||||
if ((status = lgmpClientSendData(g_state.pointerQueue,
|
g_state.transport, &control, &token);
|
||||||
&msg, sizeof(msg), &setPosSerial)) != LGMP_OK)
|
if (status != LG_TRANSPORT_OK)
|
||||||
{
|
{
|
||||||
DEBUG_WARN("Message send failed: %s", lgmpStatusString(status));
|
DEBUG_WARN("Cursor-position control failed with status %d", status);
|
||||||
goto fallback;
|
goto fallback;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -558,32 +558,24 @@ void core_handleMouseNormal(double ex, double ey)
|
|||||||
unsigned timeout = 200;
|
unsigned timeout = 200;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
LG_LOCK(g_state.pointerQueueLock);
|
status = g_state.transportOps->controlStatus(g_state.transport,
|
||||||
if (!g_state.pointerQueue)
|
token);
|
||||||
|
if (status == LG_TRANSPORT_DISCONNECTED)
|
||||||
{
|
{
|
||||||
/* the queue is nolonger valid, assume complete */
|
|
||||||
g_cursor.realigning = false;
|
g_cursor.realigning = false;
|
||||||
LG_UNLOCK(g_state.pointerQueueLock);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (status == LG_TRANSPORT_OK)
|
||||||
uint32_t hostSerial;
|
break;
|
||||||
if (lgmpClientGetSerial(g_state.pointerQueue, &hostSerial) != LGMP_OK)
|
if (status != LG_TRANSPORT_UNAVAILABLE)
|
||||||
{
|
{
|
||||||
g_cursor.realigning = false;
|
g_cursor.realigning = false;
|
||||||
LG_UNLOCK(g_state.pointerQueueLock);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LG_UNLOCK(g_state.pointerQueueLock);
|
|
||||||
|
|
||||||
if (hostSerial >= setPosSerial)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (--timeout == 0)
|
if (--timeout == 0)
|
||||||
{
|
{
|
||||||
DEBUG_ERROR(
|
DEBUG_ERROR("Cursor-position control was not acknowledged");
|
||||||
"pointerQueue serial not updating, expected %u but stuck at %u",
|
|
||||||
setPosSerial, hostSerial);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,8 +583,8 @@ void core_handleMouseNormal(double ex, double ey)
|
|||||||
}
|
}
|
||||||
while(app_isRunning());
|
while(app_isRunning());
|
||||||
|
|
||||||
g_cursor.guest.x = msg.x;
|
g_cursor.guest.x = control.cursorPos.x;
|
||||||
g_cursor.guest.y = msg.y;
|
g_cursor.guest.y = control.cursorPos.y;
|
||||||
g_cursor.realign = false;
|
g_cursor.realign = false;
|
||||||
g_cursor.realigning = false;
|
g_cursor.realigning = false;
|
||||||
g_cursor.redraw = true;
|
g_cursor.redraw = true;
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ static void bind_toggleKey(int sc, void * opaque)
|
|||||||
|
|
||||||
static void bind_setGuestRes(int sc, void * opaque)
|
static void bind_setGuestRes(int sc, void * opaque)
|
||||||
{
|
{
|
||||||
if (!(g_state.kvmfrFeatures & KVMFR_FEATURE_WINDOWSIZE))
|
if (!(g_state.transportFeatures & LG_TRANSPORT_FEATURE_WINDOW_SIZE))
|
||||||
{
|
{
|
||||||
app_alert(LG_ALERT_INFO, "The guest doesn't support this feature");
|
app_alert(LG_ALERT_INFO, "The guest doesn't support this feature");
|
||||||
return;
|
return;
|
||||||
|
|||||||
1001
client/src/main.c
1001
client/src/main.c
File diff suppressed because it is too large
Load Diff
@@ -28,16 +28,14 @@
|
|||||||
|
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "common/ivshmem.h"
|
|
||||||
#include "common/locking.h"
|
#include "common/locking.h"
|
||||||
#include "common/ringbuffer.h"
|
#include "common/ringbuffer.h"
|
||||||
#include "common/event.h"
|
#include "common/event.h"
|
||||||
#include "common/ll.h"
|
#include "common/ll.h"
|
||||||
|
|
||||||
#include <purespice.h>
|
#include <purespice.h>
|
||||||
#include <lgmp/client.h>
|
|
||||||
|
|
||||||
#include "cimgui.h"
|
#include "cimgui.h"
|
||||||
|
#include "interface/transport.h"
|
||||||
|
|
||||||
enum RunState
|
enum RunState
|
||||||
{
|
{
|
||||||
@@ -91,7 +89,7 @@ struct AppState
|
|||||||
|
|
||||||
uint8_t guestUUID[16];
|
uint8_t guestUUID[16];
|
||||||
bool guestUUIDValid;
|
bool guestUUIDValid;
|
||||||
KVMFROS guestOS;
|
LG_TransportGuestOS guestOS;
|
||||||
|
|
||||||
atomic_bool lgHostConnected;
|
atomic_bool lgHostConnected;
|
||||||
|
|
||||||
@@ -133,11 +131,9 @@ struct AppState
|
|||||||
size_t cbXfer;
|
size_t cbXfer;
|
||||||
struct ll * cbRequestList;
|
struct ll * cbRequestList;
|
||||||
|
|
||||||
struct IVSHMEM shm;
|
LG_Transport * transport;
|
||||||
PLGMPClient lgmp;
|
const LG_TransportOps * transportOps;
|
||||||
PLGMPClientQueue pointerQueue;
|
LG_TransportFeatureFlags transportFeatures;
|
||||||
LG_Lock pointerQueueLock;
|
|
||||||
KVMFRFeatureFlags kvmfrFeatures;
|
|
||||||
|
|
||||||
LGThread * cursorThread;
|
LGThread * cursorThread;
|
||||||
LGThread * frameThread;
|
LGThread * frameThread;
|
||||||
@@ -223,9 +219,7 @@ struct AppParams
|
|||||||
bool requestActivation;
|
bool requestActivation;
|
||||||
bool disableWaitingMessage;
|
bool disableWaitingMessage;
|
||||||
|
|
||||||
unsigned int cursorPollInterval;
|
const char * transport;
|
||||||
unsigned int framePollInterval;
|
|
||||||
bool allowDMA;
|
|
||||||
|
|
||||||
bool forceRenderer;
|
bool forceRenderer;
|
||||||
unsigned int forceRendererIndex;
|
unsigned int forceRendererIndex;
|
||||||
|
|||||||
59
client/src/transport.c
Normal file
59
client/src/transport.c
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* 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 "interface/transport.h"
|
||||||
|
#include "dynamic/transports.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
void lgTransport_setup(void)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < LG_TRANSPORT_COUNT; ++i)
|
||||||
|
if (LG_Transports[i]->setup)
|
||||||
|
LG_Transports[i]->setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lgTransport_isValid(const char * name)
|
||||||
|
{
|
||||||
|
if (!name)
|
||||||
|
return false;
|
||||||
|
for (unsigned i = 0; i < LG_TRANSPORT_COUNT; ++i)
|
||||||
|
if (strcmp(LG_Transports[i]->name, name) == 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lgTransport_create(const char * name, LG_Transport ** transport,
|
||||||
|
const LG_TransportOps ** ops)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < LG_TRANSPORT_COUNT; ++i)
|
||||||
|
{
|
||||||
|
if (strcmp(LG_Transports[i]->name, name) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!LG_Transports[i]->create(transport))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*ops = LG_Transports[i];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
44
client/transports/CMakeLists.txt
Normal file
44
client/transports/CMakeLists.txt
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(transports LANGUAGES C)
|
||||||
|
|
||||||
|
set(TRANSPORT_H "${CMAKE_BINARY_DIR}/include/dynamic/transports.h")
|
||||||
|
set(TRANSPORT_C "${CMAKE_BINARY_DIR}/src/transports.c")
|
||||||
|
|
||||||
|
file(WRITE ${TRANSPORT_H} "#include \"interface/transport.h\"\n\n")
|
||||||
|
file(APPEND ${TRANSPORT_H} "extern const LG_TransportOps * LG_Transports[];\n\n")
|
||||||
|
|
||||||
|
file(WRITE ${TRANSPORT_C} "#include \"interface/transport.h\"\n\n")
|
||||||
|
file(APPEND ${TRANSPORT_C} "#include <stddef.h>\n\n")
|
||||||
|
|
||||||
|
set(TRANSPORTS "_")
|
||||||
|
set(TRANSPORTS_LINK "_")
|
||||||
|
function(add_transport name)
|
||||||
|
set(TRANSPORTS "${TRANSPORTS};${name}" PARENT_SCOPE)
|
||||||
|
set(TRANSPORTS_LINK "${TRANSPORTS_LINK};transport_${name}" PARENT_SCOPE)
|
||||||
|
add_subdirectory(${name})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Add/remove transports here!
|
||||||
|
add_transport(LGMP)
|
||||||
|
if(ENABLE_TEST_TRANSPORT)
|
||||||
|
add_transport(Test)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(REMOVE_AT TRANSPORTS 0)
|
||||||
|
list(REMOVE_AT TRANSPORTS_LINK 0)
|
||||||
|
|
||||||
|
list(LENGTH TRANSPORTS TRANSPORT_COUNT)
|
||||||
|
file(APPEND ${TRANSPORT_H} "#define LG_TRANSPORT_COUNT ${TRANSPORT_COUNT}\n")
|
||||||
|
|
||||||
|
foreach(transport ${TRANSPORTS})
|
||||||
|
file(APPEND ${TRANSPORT_C} "extern const LG_TransportOps LGT_${transport};\n")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
file(APPEND ${TRANSPORT_C} "\nconst LG_TransportOps * LG_Transports[] =\n{\n")
|
||||||
|
foreach(transport ${TRANSPORTS})
|
||||||
|
file(APPEND ${TRANSPORT_C} " &LGT_${transport},\n")
|
||||||
|
endforeach()
|
||||||
|
file(APPEND ${TRANSPORT_C} " NULL\n};")
|
||||||
|
|
||||||
|
add_library(transports STATIC ${TRANSPORT_C})
|
||||||
|
target_link_libraries(transports ${TRANSPORTS_LINK})
|
||||||
11
client/transports/LGMP/CMakeLists.txt
Normal file
11
client/transports/LGMP/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(transport_LGMP LANGUAGES C)
|
||||||
|
|
||||||
|
add_library(transport_LGMP STATIC
|
||||||
|
lgmp.c
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(transport_LGMP
|
||||||
|
lg_common
|
||||||
|
lgmp
|
||||||
|
)
|
||||||
766
client/transports/LGMP/lgmp.c
Normal file
766
client/transports/LGMP/lgmp.c
Normal file
@@ -0,0 +1,766 @@
|
|||||||
|
/**
|
||||||
|
* 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 "interface/transport.h"
|
||||||
|
|
||||||
|
#include "common/KVMFR.h"
|
||||||
|
#include "common/LGMPConfig.h"
|
||||||
|
#include "common/debug.h"
|
||||||
|
#include "common/ivshmem.h"
|
||||||
|
#include "common/locking.h"
|
||||||
|
#include "common/option.h"
|
||||||
|
#include "common/stringutils.h"
|
||||||
|
|
||||||
|
#include <lgmp/client.h>
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
struct DMAFrameInfo
|
||||||
|
{
|
||||||
|
const KVMFRFrame * frame;
|
||||||
|
size_t dataSize;
|
||||||
|
int fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LG_Transport
|
||||||
|
{
|
||||||
|
struct IVSHMEM shm;
|
||||||
|
PLGMPClient client;
|
||||||
|
PLGMPClientQueue frameQueue;
|
||||||
|
PLGMPClientQueue pointerQueue;
|
||||||
|
LG_Lock pointerLock;
|
||||||
|
|
||||||
|
unsigned cursorPollInterval;
|
||||||
|
unsigned framePollInterval;
|
||||||
|
bool allowDMA;
|
||||||
|
bool connected;
|
||||||
|
bool framePending;
|
||||||
|
uint32_t frameSerial;
|
||||||
|
bool formatValid;
|
||||||
|
LG_TransportFrameFormat format;
|
||||||
|
|
||||||
|
struct DMAFrameInfo dma[LGMP_Q_FRAME_LEN];
|
||||||
|
uint8_t * pointerData;
|
||||||
|
size_t pointerDataSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool lgmp_deviceValidator(struct Option * opt, const char ** error)
|
||||||
|
{
|
||||||
|
if (strlen(opt->value.x_string) > 3 &&
|
||||||
|
memcmp(opt->value.x_string, "kvmfr", 5) != 0)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
if (stat(opt->value.x_string, &st) != 0)
|
||||||
|
{
|
||||||
|
*error = "Invalid path to the shared memory file";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static StringList lgmp_deviceValues(struct Option * option)
|
||||||
|
{
|
||||||
|
StringList values = stringlist_new(true);
|
||||||
|
DIR * dir = opendir("/sys/class/kvmfr");
|
||||||
|
if (!dir)
|
||||||
|
return values;
|
||||||
|
|
||||||
|
struct dirent * entry;
|
||||||
|
while ((entry = readdir(dir)))
|
||||||
|
{
|
||||||
|
if (entry->d_name[0] == '.')
|
||||||
|
continue;
|
||||||
|
char * name;
|
||||||
|
alloc_sprintf(&name, "/dev/%s", entry->d_name);
|
||||||
|
stringlist_push(values, name);
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lgmp_setup(void)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
const char * defaultDevice = stat("/dev/kvmfr0", &st) == 0 ?
|
||||||
|
"/dev/kvmfr0" : "/dev/shm/looking-glass";
|
||||||
|
|
||||||
|
static struct Option options[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.module = "lgmp",
|
||||||
|
.name = "shmDevice",
|
||||||
|
.shortopt = 'f',
|
||||||
|
.description = "Shared memory file or KVMFR device path",
|
||||||
|
.type = OPTION_TYPE_STRING,
|
||||||
|
.value.x_string = NULL,
|
||||||
|
.validator = lgmp_deviceValidator,
|
||||||
|
.getValues = lgmp_deviceValues,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.module = "lgmp",
|
||||||
|
.name = "allowDMA",
|
||||||
|
.description = "Allow direct DMA transfers when supported",
|
||||||
|
.type = OPTION_TYPE_BOOL,
|
||||||
|
.value.x_bool = true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.module = "lgmp",
|
||||||
|
.name = "framePollInterval",
|
||||||
|
.description = "Frame queue polling interval in microseconds",
|
||||||
|
.type = OPTION_TYPE_INT,
|
||||||
|
.value.x_int = 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.module = "lgmp",
|
||||||
|
.name = "cursorPollInterval",
|
||||||
|
.description = "Pointer queue polling interval in microseconds",
|
||||||
|
.type = OPTION_TYPE_INT,
|
||||||
|
.value.x_int = 1000,
|
||||||
|
},
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
|
options[0].value.x_string = (char *)defaultDevice;
|
||||||
|
option_register(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_create(LG_Transport ** result)
|
||||||
|
{
|
||||||
|
struct LG_Transport * this = calloc(1, sizeof(*this));
|
||||||
|
if (!this)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this->allowDMA = option_get_bool("lgmp", "allowDMA");
|
||||||
|
const int framePoll = option_get_int("lgmp", "framePollInterval");
|
||||||
|
const int cursorPoll = option_get_int("lgmp", "cursorPollInterval");
|
||||||
|
if (framePoll < 0 || cursorPoll < 0)
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("LGMP polling intervals cannot be negative");
|
||||||
|
free(this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this->framePollInterval = framePoll;
|
||||||
|
this->cursorPollInterval = cursorPoll;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
||||||
|
this->dma[i].fd = -1;
|
||||||
|
|
||||||
|
LG_LOCK_INIT(this->pointerLock);
|
||||||
|
|
||||||
|
if (!ivshmemOpenDev(&this->shm,
|
||||||
|
option_get_string("lgmp", "shmDevice")))
|
||||||
|
{
|
||||||
|
LG_LOCK_FREE(this->pointerLock);
|
||||||
|
free(this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LGMP_STATUS status = lgmpClientInit(this->shm.mem, this->shm.size,
|
||||||
|
&this->client);
|
||||||
|
if (status != LGMP_OK)
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("lgmpClientInit failed: %s", lgmpStatusString(status));
|
||||||
|
ivshmemClose(&this->shm);
|
||||||
|
LG_LOCK_FREE(this->pointerLock);
|
||||||
|
free(this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*result = this;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lgmp_closeQueues(struct LG_Transport * this)
|
||||||
|
{
|
||||||
|
if (this->framePending && this->frameQueue)
|
||||||
|
{
|
||||||
|
lgmpClientMessageDone(this->frameQueue);
|
||||||
|
this->framePending = false;
|
||||||
|
}
|
||||||
|
lgmpClientUnsubscribe(&this->frameQueue);
|
||||||
|
|
||||||
|
LG_LOCK(this->pointerLock);
|
||||||
|
lgmpClientUnsubscribe(&this->pointerQueue);
|
||||||
|
LG_UNLOCK(this->pointerLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lgmp_closeDMA(struct LG_Transport * this)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
||||||
|
{
|
||||||
|
if (this->dma[i].fd >= 0)
|
||||||
|
close(this->dma[i].fd);
|
||||||
|
this->dma[i].fd = -1;
|
||||||
|
this->dma[i].frame = NULL;
|
||||||
|
this->dma[i].dataSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lgmp_destroy(LG_Transport ** transport)
|
||||||
|
{
|
||||||
|
if (!transport || !*transport)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct LG_Transport * this = *transport;
|
||||||
|
lgmp_closeQueues(this);
|
||||||
|
lgmp_closeDMA(this);
|
||||||
|
free(this->pointerData);
|
||||||
|
lgmpClientFree(&this->client);
|
||||||
|
ivshmemClose(&this->shm);
|
||||||
|
LG_LOCK_FREE(this->pointerLock);
|
||||||
|
free(this);
|
||||||
|
*transport = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_parseSession(const uint8_t * data, uint32_t size,
|
||||||
|
LG_TransportSession * session)
|
||||||
|
{
|
||||||
|
if (size < sizeof(KVMFR))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const KVMFR * header = (const KVMFR *)data;
|
||||||
|
if (memcmp(header->magic, KVMFR_MAGIC, sizeof(header->magic)) != 0 ||
|
||||||
|
header->version != KVMFR_VERSION)
|
||||||
|
{
|
||||||
|
session->remoteVersion = header->version;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str_copy(session->version, sizeof(session->version), header->hostver,
|
||||||
|
sizeof(header->hostver));
|
||||||
|
if (header->features & KVMFR_FEATURE_SETCURSORPOS)
|
||||||
|
session->features |= LG_TRANSPORT_FEATURE_SET_CURSOR_POS;
|
||||||
|
if (header->features & KVMFR_FEATURE_WINDOWSIZE)
|
||||||
|
session->features |= LG_TRANSPORT_FEATURE_WINDOW_SIZE;
|
||||||
|
|
||||||
|
data += sizeof(*header);
|
||||||
|
size -= sizeof(*header);
|
||||||
|
while (size >= sizeof(KVMFRRecord))
|
||||||
|
{
|
||||||
|
const KVMFRRecord * record = (const KVMFRRecord *)data;
|
||||||
|
data += sizeof(*record);
|
||||||
|
size -= sizeof(*record);
|
||||||
|
if (record->size > size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (record->type)
|
||||||
|
{
|
||||||
|
case KVMFR_RECORD_VMINFO:
|
||||||
|
if (record->size >= sizeof(KVMFRRecord_VMInfo))
|
||||||
|
{
|
||||||
|
const KVMFRRecord_VMInfo * info =
|
||||||
|
(const KVMFRRecord_VMInfo *)data;
|
||||||
|
memcpy(session->uuid, info->uuid, sizeof(session->uuid));
|
||||||
|
for (unsigned i = 0; i < sizeof(session->uuid); ++i)
|
||||||
|
session->uuidValid |= session->uuid[i] != 0;
|
||||||
|
str_copy(session->capture, sizeof(session->capture), info->capture,
|
||||||
|
sizeof(info->capture));
|
||||||
|
session->cpus = info->cpus;
|
||||||
|
session->cores = info->cores;
|
||||||
|
session->sockets = info->sockets;
|
||||||
|
const size_t fixed = offsetof(KVMFRRecord_VMInfo, model);
|
||||||
|
str_copy(session->cpuModel, sizeof(session->cpuModel), info->model,
|
||||||
|
record->size - fixed);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KVMFR_RECORD_OSINFO:
|
||||||
|
if (record->size >= sizeof(KVMFRRecord_OSInfo))
|
||||||
|
{
|
||||||
|
const KVMFRRecord_OSInfo * info =
|
||||||
|
(const KVMFRRecord_OSInfo *)data;
|
||||||
|
session->os = info->os <= KVMFR_OS_OTHER ?
|
||||||
|
(LG_TransportGuestOS)info->os : LG_TRANSPORT_OS_OTHER;
|
||||||
|
const size_t fixed = offsetof(KVMFRRecord_OSInfo, name);
|
||||||
|
str_copy(session->osName, sizeof(session->osName), info->name,
|
||||||
|
record->size - fixed);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
data += record->size;
|
||||||
|
size -= record->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus lgmp_connect(LG_Transport * this,
|
||||||
|
LG_TransportSession * session)
|
||||||
|
{
|
||||||
|
memset(session, 0, sizeof(*session));
|
||||||
|
session->os = LG_TRANSPORT_OS_OTHER;
|
||||||
|
|
||||||
|
uint32_t size;
|
||||||
|
uint8_t * data;
|
||||||
|
uint32_t remoteVersion;
|
||||||
|
LGMP_STATUS status = lgmpClientSessionInit(this->client, &size, &data, NULL,
|
||||||
|
&remoteVersion);
|
||||||
|
session->remoteVersion = remoteVersion;
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case LGMP_OK:
|
||||||
|
if (!lgmp_parseSession(data, size, session))
|
||||||
|
return LG_TRANSPORT_INVALID_VERSION;
|
||||||
|
this->connected = true;
|
||||||
|
this->frameSerial = 0;
|
||||||
|
this->formatValid = false;
|
||||||
|
return LG_TRANSPORT_OK;
|
||||||
|
|
||||||
|
case LGMP_ERR_INVALID_VERSION:
|
||||||
|
return LG_TRANSPORT_INVALID_VERSION;
|
||||||
|
|
||||||
|
case LGMP_ERR_INVALID_SESSION:
|
||||||
|
case LGMP_ERR_INVALID_MAGIC:
|
||||||
|
return LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
|
||||||
|
default:
|
||||||
|
DEBUG_ERROR("lgmpClientSessionInit failed: %s", lgmpStatusString(status));
|
||||||
|
return LG_TRANSPORT_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lgmp_disconnect(LG_Transport * this)
|
||||||
|
{
|
||||||
|
lgmp_closeQueues(this);
|
||||||
|
lgmp_closeDMA(this);
|
||||||
|
this->connected = false;
|
||||||
|
this->frameSerial = 0;
|
||||||
|
this->formatValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_sessionValid(LG_Transport * this)
|
||||||
|
{
|
||||||
|
return this->connected && lgmpClientSessionValid(this->client);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_supportsDMA(LG_Transport * this)
|
||||||
|
{
|
||||||
|
return this->allowDMA && ivshmemHasDMA(&this->shm);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lgmp_attachRenderer(LG_Transport * this,
|
||||||
|
const LG_RendererInterop * interop)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lgmp_detachRenderer(LG_Transport * this)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus lgmp_subscribe(PLGMPClient client, uint32_t id,
|
||||||
|
PLGMPClientQueue * queue)
|
||||||
|
{
|
||||||
|
if (*queue)
|
||||||
|
return LG_TRANSPORT_OK;
|
||||||
|
|
||||||
|
LGMP_STATUS status = lgmpClientSubscribe(client, id, queue);
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case LGMP_OK: return LG_TRANSPORT_OK;
|
||||||
|
case LGMP_ERR_NO_SUCH_QUEUE: return LG_TRANSPORT_TIMEOUT;
|
||||||
|
case LGMP_ERR_INVALID_SESSION: return LG_TRANSPORT_DISCONNECTED;
|
||||||
|
default:
|
||||||
|
DEBUG_ERROR("lgmpClientSubscribe failed: %s", lgmpStatusString(status));
|
||||||
|
return LG_TRANSPORT_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus lgmp_process(PLGMPClientQueue queue,
|
||||||
|
unsigned interval, LGMPMessage * message)
|
||||||
|
{
|
||||||
|
LGMP_STATUS status = lgmpClientProcess(queue, message);
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case LGMP_OK:
|
||||||
|
return LG_TRANSPORT_OK;
|
||||||
|
case LGMP_ERR_QUEUE_EMPTY:
|
||||||
|
if (interval)
|
||||||
|
usleep(interval);
|
||||||
|
return LG_TRANSPORT_TIMEOUT;
|
||||||
|
case LGMP_ERR_INVALID_SESSION:
|
||||||
|
return LG_TRANSPORT_DISCONNECTED;
|
||||||
|
default:
|
||||||
|
DEBUG_ERROR("lgmpClientProcess failed: %s", lgmpStatusString(status));
|
||||||
|
return LG_TRANSPORT_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lgmp_getDMA(struct LG_Transport * this, const KVMFRFrame * frame,
|
||||||
|
size_t dataSize)
|
||||||
|
{
|
||||||
|
struct DMAFrameInfo * dma = NULL;
|
||||||
|
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
||||||
|
if (this->dma[i].frame == frame)
|
||||||
|
{
|
||||||
|
dma = &this->dma[i];
|
||||||
|
if (dma->dataSize < dataSize && dma->fd >= 0)
|
||||||
|
{
|
||||||
|
close(dma->fd);
|
||||||
|
dma->fd = -1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dma)
|
||||||
|
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
||||||
|
if (!this->dma[i].frame)
|
||||||
|
{
|
||||||
|
dma = &this->dma[i];
|
||||||
|
dma->frame = frame;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dma)
|
||||||
|
return -1;
|
||||||
|
if (dma->fd >= 0)
|
||||||
|
return dma->fd;
|
||||||
|
|
||||||
|
const uintptr_t position = (uintptr_t)frame - (uintptr_t)this->shm.mem;
|
||||||
|
const uintptr_t offset = frame->offset + sizeof(FrameBuffer);
|
||||||
|
dma->dataSize = dataSize;
|
||||||
|
dma->fd = ivshmemGetDMABuf(&this->shm, position + offset, dataSize);
|
||||||
|
return dma->fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA,
|
||||||
|
LG_TransportFrame * result)
|
||||||
|
{
|
||||||
|
if (!this->connected)
|
||||||
|
return LG_TRANSPORT_DISCONNECTED;
|
||||||
|
if (this->framePending)
|
||||||
|
return LG_TRANSPORT_ERROR;
|
||||||
|
|
||||||
|
LG_TransportStatus status = lgmp_subscribe(this->client, LGMP_Q_FRAME,
|
||||||
|
&this->frameQueue);
|
||||||
|
if (status != LG_TRANSPORT_OK)
|
||||||
|
{
|
||||||
|
if (status == LG_TRANSPORT_TIMEOUT)
|
||||||
|
usleep(1000);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
LGMPMessage message;
|
||||||
|
status = lgmp_process(this->frameQueue, this->framePollInterval, &message);
|
||||||
|
if (status != LG_TRANSPORT_OK)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
if (message.size < sizeof(KVMFRFrame))
|
||||||
|
{
|
||||||
|
lgmpClientMessageDone(this->frameQueue);
|
||||||
|
DEBUG_ERROR("LGMP frame payload is too small");
|
||||||
|
return LG_TRANSPORT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
const KVMFRFrame * frame = (const KVMFRFrame *)message.mem;
|
||||||
|
const size_t frameDataSize = (size_t)frame->dataHeight * frame->pitch;
|
||||||
|
if (frame->type <= FRAME_TYPE_INVALID || frame->type >= FRAME_TYPE_MAX ||
|
||||||
|
frame->offset > message.size - sizeof(FrameBuffer) ||
|
||||||
|
frameDataSize > message.size - frame->offset - sizeof(FrameBuffer))
|
||||||
|
{
|
||||||
|
lgmpClientMessageDone(this->frameQueue);
|
||||||
|
DEBUG_ERROR("LGMP frame payload contains invalid dimensions or offsets");
|
||||||
|
return LG_TRANSPORT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame->frameSerial == this->frameSerial && this->frameSerial)
|
||||||
|
{
|
||||||
|
lgmpClientMessageDone(this->frameQueue);
|
||||||
|
return LG_TRANSPORT_TIMEOUT;
|
||||||
|
}
|
||||||
|
this->frameSerial = frame->frameSerial;
|
||||||
|
|
||||||
|
memset(result, 0, sizeof(*result));
|
||||||
|
result->serial = frame->frameSerial;
|
||||||
|
if (frame->flags & FRAME_FLAG_BLOCK_SCREENSAVER)
|
||||||
|
result->flags |= LG_TRANSPORT_FRAME_BLOCK_SCREENSAVER;
|
||||||
|
if (frame->flags & FRAME_FLAG_REQUEST_ACTIVATION)
|
||||||
|
result->flags |= LG_TRANSPORT_FRAME_REQUEST_ACTIVATION;
|
||||||
|
if (frame->flags & FRAME_FLAG_TRUNCATED)
|
||||||
|
result->flags |= LG_TRANSPORT_FRAME_TRUNCATED;
|
||||||
|
|
||||||
|
LG_TransportFrameFormat * format = &this->format;
|
||||||
|
if (!this->formatValid || format->version != frame->formatVer)
|
||||||
|
{
|
||||||
|
memset(format, 0, sizeof(*format));
|
||||||
|
format->version = frame->formatVer;
|
||||||
|
format->type = frame->type;
|
||||||
|
format->screenWidth = frame->screenWidth;
|
||||||
|
format->screenHeight = frame->screenHeight;
|
||||||
|
format->dataWidth = frame->dataWidth;
|
||||||
|
format->dataHeight = frame->dataHeight;
|
||||||
|
format->frameWidth = frame->frameWidth;
|
||||||
|
format->frameHeight = frame->frameHeight;
|
||||||
|
format->rotation = frame->rotation;
|
||||||
|
format->stride = frame->stride;
|
||||||
|
format->pitch = frame->pitch;
|
||||||
|
format->hdr = frame->flags & FRAME_FLAG_HDR;
|
||||||
|
format->hdrPQ = frame->flags & FRAME_FLAG_HDR_PQ;
|
||||||
|
format->hdrMetadata = frame->flags & FRAME_FLAG_HDR_METADATA;
|
||||||
|
format->sdrWhiteLevel = frame->sdrWhiteLevel ? frame->sdrWhiteLevel :
|
||||||
|
KVMFR_SDR_WHITE_LEVEL_DEFAULT;
|
||||||
|
if (format->hdrMetadata)
|
||||||
|
{
|
||||||
|
memcpy(format->hdrDisplayPrimary, frame->hdrDisplayPrimary,
|
||||||
|
sizeof(format->hdrDisplayPrimary));
|
||||||
|
memcpy(format->hdrWhitePoint, frame->hdrWhitePoint,
|
||||||
|
sizeof(format->hdrWhitePoint));
|
||||||
|
format->hdrMaxDisplayLuminance = frame->hdrMaxDisplayLuminance;
|
||||||
|
format->hdrMinDisplayLuminance = frame->hdrMinDisplayLuminance;
|
||||||
|
format->hdrMaxContentLightLevel = frame->hdrMaxContentLightLevel;
|
||||||
|
format->hdrMaxFrameAverageLightLevel =
|
||||||
|
frame->hdrMaxFrameAverageLightLevel;
|
||||||
|
}
|
||||||
|
this->formatValid = true;
|
||||||
|
}
|
||||||
|
result->format = format;
|
||||||
|
|
||||||
|
result->framebuffer = (const FrameBuffer *)((const uint8_t *)frame +
|
||||||
|
frame->offset);
|
||||||
|
result->dmaFD = -1;
|
||||||
|
if (useDMA)
|
||||||
|
{
|
||||||
|
const size_t dataSize = (size_t)format->dataHeight * format->pitch;
|
||||||
|
result->dmaFD = lgmp_getDMA(this, frame, dataSize);
|
||||||
|
if (result->dmaFD < 0)
|
||||||
|
{
|
||||||
|
lgmpClientMessageDone(this->frameQueue);
|
||||||
|
DEBUG_ERROR("Failed to obtain a DMA buffer for the frame");
|
||||||
|
return LG_TRANSPORT_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame->damageRectsCount <= KVMFR_MAX_DAMAGE_RECTS)
|
||||||
|
{
|
||||||
|
result->damageRects = frame->damageRects;
|
||||||
|
result->damageRectsCount = frame->damageRectsCount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DEBUG_WARN("Invalid damage rectangles, forcing a full update");
|
||||||
|
|
||||||
|
this->framePending = true;
|
||||||
|
return LG_TRANSPORT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lgmp_releaseFrame(LG_Transport * this, LG_TransportFrame * frame)
|
||||||
|
{
|
||||||
|
if (this->framePending && this->frameQueue)
|
||||||
|
lgmpClientMessageDone(this->frameQueue);
|
||||||
|
this->framePending = false;
|
||||||
|
memset(frame, 0, sizeof(*frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus lgmp_nextPointer(LG_Transport * this,
|
||||||
|
LG_TransportPointer * result)
|
||||||
|
{
|
||||||
|
if (!this->connected)
|
||||||
|
return LG_TRANSPORT_DISCONNECTED;
|
||||||
|
|
||||||
|
uint32_t pointerFlags = 0;
|
||||||
|
size_t shapeSize = 0;
|
||||||
|
size_t transformSize = 0;
|
||||||
|
LG_LOCK(this->pointerLock);
|
||||||
|
LG_TransportStatus status = lgmp_subscribe(this->client, LGMP_Q_POINTER,
|
||||||
|
&this->pointerQueue);
|
||||||
|
LG_UNLOCK(this->pointerLock);
|
||||||
|
if (status == LG_TRANSPORT_OK)
|
||||||
|
{
|
||||||
|
LGMPMessage message;
|
||||||
|
status = lgmp_process(this->pointerQueue, this->cursorPollInterval,
|
||||||
|
&message);
|
||||||
|
if (status == LG_TRANSPORT_OK)
|
||||||
|
{
|
||||||
|
if (message.size < sizeof(KVMFRCursor))
|
||||||
|
{
|
||||||
|
lgmpClientMessageDone(this->pointerQueue);
|
||||||
|
status = LG_TRANSPORT_ERROR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const KVMFRCursor * cursor = (const KVMFRCursor *)message.mem;
|
||||||
|
shapeSize = message.udata & CURSOR_FLAG_SHAPE ?
|
||||||
|
(size_t)cursor->height * cursor->pitch : 0;
|
||||||
|
transformSize =
|
||||||
|
message.udata & CURSOR_FLAG_COLOR_TRANSFORM ?
|
||||||
|
sizeof(KVMFRColorTransform) : 0;
|
||||||
|
if (shapeSize > message.size - sizeof(*cursor) ||
|
||||||
|
transformSize > message.size - sizeof(*cursor) - shapeSize)
|
||||||
|
{
|
||||||
|
lgmpClientMessageDone(this->pointerQueue);
|
||||||
|
status = LG_TRANSPORT_ERROR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const size_t needed = sizeof(*cursor) + shapeSize + transformSize;
|
||||||
|
if (needed > this->pointerDataSize)
|
||||||
|
{
|
||||||
|
void * data = realloc(this->pointerData, needed);
|
||||||
|
if (!data)
|
||||||
|
status = LG_TRANSPORT_ERROR;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->pointerData = data;
|
||||||
|
this->pointerDataSize = needed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (status == LG_TRANSPORT_OK)
|
||||||
|
{
|
||||||
|
memcpy(this->pointerData, message.mem, needed);
|
||||||
|
pointerFlags = message.udata;
|
||||||
|
}
|
||||||
|
lgmpClientMessageDone(this->pointerQueue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (status != LG_TRANSPORT_OK)
|
||||||
|
{
|
||||||
|
if (status == LG_TRANSPORT_TIMEOUT)
|
||||||
|
usleep(1000);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const KVMFRCursor * cursor = (const KVMFRCursor *)this->pointerData;
|
||||||
|
memset(result, 0, sizeof(*result));
|
||||||
|
if (pointerFlags & CURSOR_FLAG_POSITION)
|
||||||
|
result->flags |= LG_TRANSPORT_POINTER_POSITION;
|
||||||
|
if (pointerFlags & CURSOR_FLAG_VISIBLE)
|
||||||
|
result->flags |= LG_TRANSPORT_POINTER_VISIBLE;
|
||||||
|
if (pointerFlags & CURSOR_FLAG_SHAPE)
|
||||||
|
result->flags |= LG_TRANSPORT_POINTER_SHAPE;
|
||||||
|
if (pointerFlags & CURSOR_FLAG_COLOR_TRANSFORM)
|
||||||
|
result->flags |= LG_TRANSPORT_POINTER_COLOR_TRANSFORM;
|
||||||
|
if (pointerFlags & CURSOR_FLAG_VISIBLE_VALID)
|
||||||
|
result->flags |= LG_TRANSPORT_POINTER_VISIBLE_VALID;
|
||||||
|
result->x = cursor->x;
|
||||||
|
result->y = cursor->y;
|
||||||
|
result->type = cursor->type;
|
||||||
|
result->hx = cursor->hx;
|
||||||
|
result->hy = cursor->hy;
|
||||||
|
result->width = cursor->width;
|
||||||
|
result->height = cursor->height;
|
||||||
|
result->pitch = cursor->pitch;
|
||||||
|
result->sdrWhiteLevel = cursor->sdrWhiteLevel;
|
||||||
|
result->shape = (const uint8_t *)(cursor + 1);
|
||||||
|
if (transformSize)
|
||||||
|
result->colorTransform = (const LGColorTransform *)(result->shape +
|
||||||
|
shapeSize);
|
||||||
|
return LG_TRANSPORT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lgmp_releasePointer(LG_Transport * this,
|
||||||
|
LG_TransportPointer * pointer)
|
||||||
|
{
|
||||||
|
memset(pointer, 0, sizeof(*pointer));
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus lgmp_sendControl(LG_Transport * this,
|
||||||
|
const LG_TransportControl * control, LG_TransportControlToken * token)
|
||||||
|
{
|
||||||
|
uint8_t buffer[sizeof(KVMFRWindowSize)];
|
||||||
|
uint32_t size;
|
||||||
|
switch (control->type)
|
||||||
|
{
|
||||||
|
case LG_TRANSPORT_CONTROL_SET_CURSOR_POS:
|
||||||
|
{
|
||||||
|
const KVMFRSetCursorPos message = {
|
||||||
|
.msg.type = KVMFR_MESSAGE_SETCURSORPOS,
|
||||||
|
.x = control->cursorPos.x,
|
||||||
|
.y = control->cursorPos.y,
|
||||||
|
};
|
||||||
|
memcpy(buffer, &message, sizeof(message));
|
||||||
|
size = sizeof(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LG_TRANSPORT_CONTROL_WINDOW_SIZE:
|
||||||
|
{
|
||||||
|
const KVMFRWindowSize message = {
|
||||||
|
.msg.type = KVMFR_MESSAGE_WINDOWSIZE,
|
||||||
|
.w = control->windowSize.width,
|
||||||
|
.h = control->windowSize.height,
|
||||||
|
};
|
||||||
|
memcpy(buffer, &message, sizeof(message));
|
||||||
|
size = sizeof(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return LG_TRANSPORT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
LG_LOCK(this->pointerLock);
|
||||||
|
if (!this->pointerQueue)
|
||||||
|
{
|
||||||
|
LG_UNLOCK(this->pointerLock);
|
||||||
|
return LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
uint32_t serial;
|
||||||
|
LGMP_STATUS status = lgmpClientSendData(this->pointerQueue, buffer, size,
|
||||||
|
&serial);
|
||||||
|
LG_UNLOCK(this->pointerLock);
|
||||||
|
if (status != LGMP_OK)
|
||||||
|
return status == LGMP_ERR_INVALID_SESSION ? LG_TRANSPORT_DISCONNECTED :
|
||||||
|
LG_TRANSPORT_ERROR;
|
||||||
|
*token = serial;
|
||||||
|
return LG_TRANSPORT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus lgmp_controlStatus(LG_Transport * this,
|
||||||
|
LG_TransportControlToken token)
|
||||||
|
{
|
||||||
|
LG_LOCK(this->pointerLock);
|
||||||
|
if (!this->pointerQueue)
|
||||||
|
{
|
||||||
|
LG_UNLOCK(this->pointerLock);
|
||||||
|
return LG_TRANSPORT_DISCONNECTED;
|
||||||
|
}
|
||||||
|
uint32_t serial;
|
||||||
|
LGMP_STATUS status = lgmpClientGetSerial(this->pointerQueue, &serial);
|
||||||
|
LG_UNLOCK(this->pointerLock);
|
||||||
|
if (status != LGMP_OK)
|
||||||
|
return status == LGMP_ERR_INVALID_SESSION ? LG_TRANSPORT_DISCONNECTED :
|
||||||
|
LG_TRANSPORT_ERROR;
|
||||||
|
return serial >= token ? LG_TRANSPORT_OK : LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LG_TransportOps LGT_LGMP =
|
||||||
|
{
|
||||||
|
.name = "lgmp",
|
||||||
|
.setup = lgmp_setup,
|
||||||
|
.create = lgmp_create,
|
||||||
|
.destroy = lgmp_destroy,
|
||||||
|
.connect = lgmp_connect,
|
||||||
|
.disconnect = lgmp_disconnect,
|
||||||
|
.sessionValid = lgmp_sessionValid,
|
||||||
|
.supportsDMA = lgmp_supportsDMA,
|
||||||
|
.attachRenderer = lgmp_attachRenderer,
|
||||||
|
.detachRenderer = lgmp_detachRenderer,
|
||||||
|
.nextFrame = lgmp_nextFrame,
|
||||||
|
.releaseFrame = lgmp_releaseFrame,
|
||||||
|
.nextPointer = lgmp_nextPointer,
|
||||||
|
.releasePointer = lgmp_releasePointer,
|
||||||
|
.sendControl = lgmp_sendControl,
|
||||||
|
.controlStatus = lgmp_controlStatus,
|
||||||
|
};
|
||||||
10
client/transports/Test/CMakeLists.txt
Normal file
10
client/transports/Test/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(transport_Test LANGUAGES C)
|
||||||
|
|
||||||
|
add_library(transport_Test STATIC
|
||||||
|
test.c
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(transport_Test
|
||||||
|
lg_common
|
||||||
|
)
|
||||||
607
client/transports/Test/test.c
Normal file
607
client/transports/Test/test.c
Normal file
@@ -0,0 +1,607 @@
|
|||||||
|
/**
|
||||||
|
* 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 "interface/transport.h"
|
||||||
|
|
||||||
|
#include "common/array.h"
|
||||||
|
#include "common/debug.h"
|
||||||
|
#include "common/option.h"
|
||||||
|
#include "common/time.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define TEST_BUFFER_COUNT 2
|
||||||
|
#define TEST_PQ_LUT_SIZE 4096
|
||||||
|
|
||||||
|
struct TestFormat
|
||||||
|
{
|
||||||
|
const char * name;
|
||||||
|
FrameType type;
|
||||||
|
unsigned bytesPerPixel;
|
||||||
|
bool hdr;
|
||||||
|
bool hdrPQ;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct TestFormat testFormats[] =
|
||||||
|
{
|
||||||
|
{ "bgra" , FRAME_TYPE_BGRA , 4, false, false },
|
||||||
|
{ "rgba" , FRAME_TYPE_RGBA , 4, false, false },
|
||||||
|
{ "bgr32" , FRAME_TYPE_BGR_32 , 4, false, false },
|
||||||
|
{ "rgb24" , FRAME_TYPE_RGB_24 , 3, false, false },
|
||||||
|
{ "rgba10" , FRAME_TYPE_RGBA10 , 4, true , true },
|
||||||
|
{ "rgba16f", FRAME_TYPE_RGBA16F, 8, true , false },
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TestBuffer
|
||||||
|
{
|
||||||
|
FrameBuffer * framebuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LG_Transport
|
||||||
|
{
|
||||||
|
unsigned width;
|
||||||
|
unsigned height;
|
||||||
|
unsigned frameRate;
|
||||||
|
unsigned frameCount;
|
||||||
|
bool realtime;
|
||||||
|
bool cycleFormats;
|
||||||
|
unsigned formatIndex;
|
||||||
|
|
||||||
|
bool connected;
|
||||||
|
bool framePending;
|
||||||
|
uint64_t serial;
|
||||||
|
uint64_t nextFrameTime;
|
||||||
|
unsigned bufferIndex;
|
||||||
|
struct TestBuffer buffers[TEST_BUFFER_COUNT];
|
||||||
|
FrameDamageRect damage[2];
|
||||||
|
LG_TransportFrameFormat format;
|
||||||
|
uint16_t pqLUT[TEST_PQ_LUT_SIZE + 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
static void test_setup(void)
|
||||||
|
{
|
||||||
|
static struct Option options[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.module = "test",
|
||||||
|
.name = "width",
|
||||||
|
.description = "Generated frame width",
|
||||||
|
.type = OPTION_TYPE_INT,
|
||||||
|
.value.x_int = 1920,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.module = "test",
|
||||||
|
.name = "height",
|
||||||
|
.description = "Generated frame height",
|
||||||
|
.type = OPTION_TYPE_INT,
|
||||||
|
.value.x_int = 1080,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.module = "test",
|
||||||
|
.name = "frameRate",
|
||||||
|
.description = "Generated frames per second",
|
||||||
|
.type = OPTION_TYPE_INT,
|
||||||
|
.value.x_int = 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.module = "test",
|
||||||
|
.name = "frameCount",
|
||||||
|
.description = "Stop after this many frames, or zero to run forever",
|
||||||
|
.type = OPTION_TYPE_INT,
|
||||||
|
.value.x_int = 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.module = "test",
|
||||||
|
.name = "realtime",
|
||||||
|
.description = "Pace generated frames in real time",
|
||||||
|
.type = OPTION_TYPE_BOOL,
|
||||||
|
.value.x_bool = true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.module = "test",
|
||||||
|
.name = "format",
|
||||||
|
.description = "Generated frame format: bgra, rgba, bgr32, rgb24, "
|
||||||
|
"rgba10, rgba16f, or cycle",
|
||||||
|
.type = OPTION_TYPE_STRING,
|
||||||
|
.value.x_string = "bgra",
|
||||||
|
},
|
||||||
|
{0}
|
||||||
|
};
|
||||||
|
|
||||||
|
option_register(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_findFormat(const char * name)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < ARRAY_LENGTH(testFormats); ++i)
|
||||||
|
if (strcmp(name, testFormats[i].name) == 0)
|
||||||
|
return (int)i;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float test_linearToPQ(float linear)
|
||||||
|
{
|
||||||
|
const float m1 = 2610.0f / 16384.0f;
|
||||||
|
const float m2 = 2523.0f / 32.0f;
|
||||||
|
const float c1 = 3424.0f / 4096.0f;
|
||||||
|
const float c2 = 2413.0f / 128.0f;
|
||||||
|
const float c3 = 2392.0f / 128.0f;
|
||||||
|
const float p = powf(linear, m1);
|
||||||
|
return powf((c1 + c2 * p) / (1.0f + c3 * p), m2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_initPQLUT(struct LG_Transport * this)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i <= TEST_PQ_LUT_SIZE; ++i)
|
||||||
|
{
|
||||||
|
/* scRGB uses 1.0 for 80 nits; PQ uses 1.0 for 10000 nits. */
|
||||||
|
const float scRGB = (float)i * 4.0f / TEST_PQ_LUT_SIZE;
|
||||||
|
const float pq = test_linearToPQ(scRGB / 125.0f);
|
||||||
|
this->pqLUT[i] = (uint16_t)(pq * 1023.0f + 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t test_scRGBToPQ(const struct LG_Transport * this, float scRGB)
|
||||||
|
{
|
||||||
|
const unsigned index = scRGB <= 0.0f ? 0 : scRGB >= 4.0f ?
|
||||||
|
TEST_PQ_LUT_SIZE :
|
||||||
|
(unsigned)(scRGB * (TEST_PQ_LUT_SIZE / 4.0f) + 0.5f);
|
||||||
|
return this->pqLUT[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_setHDRMetadata(LG_TransportFrameFormat * format)
|
||||||
|
{
|
||||||
|
if (format->hdrPQ)
|
||||||
|
{
|
||||||
|
format->hdrDisplayPrimary[0][0] = 35400;
|
||||||
|
format->hdrDisplayPrimary[0][1] = 14600;
|
||||||
|
format->hdrDisplayPrimary[1][0] = 8500;
|
||||||
|
format->hdrDisplayPrimary[1][1] = 39850;
|
||||||
|
format->hdrDisplayPrimary[2][0] = 6550;
|
||||||
|
format->hdrDisplayPrimary[2][1] = 2300;
|
||||||
|
format->hdrMaxDisplayLuminance = 1000;
|
||||||
|
format->hdrMinDisplayLuminance = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
format->hdrDisplayPrimary[0][0] = 13250;
|
||||||
|
format->hdrDisplayPrimary[0][1] = 34500;
|
||||||
|
format->hdrDisplayPrimary[1][0] = 7500;
|
||||||
|
format->hdrDisplayPrimary[1][1] = 30000;
|
||||||
|
format->hdrDisplayPrimary[2][0] = 34000;
|
||||||
|
format->hdrDisplayPrimary[2][1] = 16000;
|
||||||
|
format->hdrMaxDisplayLuminance = 80;
|
||||||
|
format->hdrMinDisplayLuminance = 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
format->hdrWhitePoint[0] = 15635;
|
||||||
|
format->hdrWhitePoint[1] = 16450;
|
||||||
|
format->hdrMaxContentLightLevel = 1000;
|
||||||
|
format->hdrMaxFrameAverageLightLevel = 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_setFormat(struct LG_Transport * this, unsigned index)
|
||||||
|
{
|
||||||
|
if (this->format.version && this->formatIndex == index)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const struct TestFormat * testFormat = &testFormats[index];
|
||||||
|
const uint32_t version = this->format.version + 1;
|
||||||
|
const bool packedBGR =
|
||||||
|
testFormat->type == FRAME_TYPE_BGR_32;
|
||||||
|
const unsigned pitch = packedBGR ?
|
||||||
|
ALIGN_PAD(this->width * 3, 4) :
|
||||||
|
this->width * testFormat->bytesPerPixel;
|
||||||
|
|
||||||
|
this->formatIndex = index;
|
||||||
|
this->format = (LG_TransportFrameFormat) {
|
||||||
|
.version = version,
|
||||||
|
.type = testFormat->type,
|
||||||
|
.screenWidth = this->width,
|
||||||
|
.screenHeight = this->height,
|
||||||
|
.dataWidth = packedBGR ? pitch / 4 : this->width,
|
||||||
|
.dataHeight = this->height,
|
||||||
|
.frameWidth = this->width,
|
||||||
|
.frameHeight = this->height,
|
||||||
|
.rotation = FRAME_ROT_0,
|
||||||
|
.stride = packedBGR ? pitch / 4 : this->width,
|
||||||
|
.pitch = pitch,
|
||||||
|
.hdr = testFormat->hdr,
|
||||||
|
.hdrPQ = testFormat->hdrPQ,
|
||||||
|
.hdrMetadata = testFormat->hdr,
|
||||||
|
.sdrWhiteLevel = LG_SDR_WHITE_LEVEL_DEFAULT,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this->format.hdrMetadata)
|
||||||
|
test_setHDRMetadata(&this->format);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_create(LG_Transport ** result)
|
||||||
|
{
|
||||||
|
struct LG_Transport * this = calloc(1, sizeof(*this));
|
||||||
|
if (!this)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const int width = option_get_int("test", "width");
|
||||||
|
const int height = option_get_int("test", "height");
|
||||||
|
const int frameRate = option_get_int("test", "frameRate");
|
||||||
|
const int frameCount = option_get_int("test", "frameCount");
|
||||||
|
const char * format = option_get_string("test", "format");
|
||||||
|
const bool cycleFormats = strcmp(format, "cycle") == 0;
|
||||||
|
const int formatIndex = cycleFormats ? 0 : test_findFormat(format);
|
||||||
|
const unsigned maxBytesPerPixel = formatIndex < 0 ? 0 :
|
||||||
|
cycleFormats ? 8 : testFormats[formatIndex].bytesPerPixel;
|
||||||
|
if (width < 1 || height < 1 || !maxBytesPerPixel ||
|
||||||
|
width > (int)(UINT32_MAX / maxBytesPerPixel) ||
|
||||||
|
frameRate < 1 || frameRate > 1000000000 || frameCount < 0 ||
|
||||||
|
(size_t)width > (SIZE_MAX - sizeof(FrameBuffer)) /
|
||||||
|
maxBytesPerPixel / (size_t)height)
|
||||||
|
{
|
||||||
|
DEBUG_ERROR(
|
||||||
|
"Invalid test transport dimensions, rate, frame count, or format");
|
||||||
|
free(this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->width = width;
|
||||||
|
this->height = height;
|
||||||
|
this->frameRate = frameRate;
|
||||||
|
this->frameCount = frameCount;
|
||||||
|
this->realtime = option_get_bool("test", "realtime");
|
||||||
|
this->cycleFormats = cycleFormats;
|
||||||
|
this->formatIndex = (unsigned)formatIndex;
|
||||||
|
test_initPQLUT(this);
|
||||||
|
|
||||||
|
const size_t dataSize =
|
||||||
|
(size_t)this->width * this->height * maxBytesPerPixel;
|
||||||
|
for (unsigned i = 0; i < TEST_BUFFER_COUNT; ++i)
|
||||||
|
{
|
||||||
|
this->buffers[i].framebuffer = malloc(sizeof(FrameBuffer) + dataSize);
|
||||||
|
if (!this->buffers[i].framebuffer)
|
||||||
|
{
|
||||||
|
for (unsigned j = 0; j < i; ++j)
|
||||||
|
free(this->buffers[j].framebuffer);
|
||||||
|
free(this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*result = this;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_destroy(LG_Transport ** transport)
|
||||||
|
{
|
||||||
|
if (!transport || !*transport)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct LG_Transport * this = *transport;
|
||||||
|
for (unsigned i = 0; i < TEST_BUFFER_COUNT; ++i)
|
||||||
|
free(this->buffers[i].framebuffer);
|
||||||
|
free(this);
|
||||||
|
*transport = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus test_connect(LG_Transport * this,
|
||||||
|
LG_TransportSession * session)
|
||||||
|
{
|
||||||
|
memset(session, 0, sizeof(*session));
|
||||||
|
memcpy(session->version, "test", 5);
|
||||||
|
session->os = LG_TRANSPORT_OS_OTHER;
|
||||||
|
memcpy(session->capture, "test", 5);
|
||||||
|
|
||||||
|
this->connected = true;
|
||||||
|
this->framePending = false;
|
||||||
|
this->serial = 0;
|
||||||
|
this->bufferIndex = 0;
|
||||||
|
this->nextFrameTime = nanotime();
|
||||||
|
this->format.version = 0;
|
||||||
|
test_setFormat(this, this->formatIndex);
|
||||||
|
return LG_TRANSPORT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_disconnect(LG_Transport * this)
|
||||||
|
{
|
||||||
|
this->connected = false;
|
||||||
|
this->framePending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_sessionValid(LG_Transport * this)
|
||||||
|
{
|
||||||
|
return this->connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_supportsDMA(LG_Transport * this)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_attachRenderer(LG_Transport * this,
|
||||||
|
const LG_RendererInterop * interop)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_detachRenderer(LG_Transport * this)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t test_floatToHalf(float value)
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
float value;
|
||||||
|
uint32_t bits;
|
||||||
|
}
|
||||||
|
input = { .value = value };
|
||||||
|
|
||||||
|
const uint16_t sign = (input.bits >> 16) & 0x8000;
|
||||||
|
int exponent = ((input.bits >> 23) & 0xff) - 127 + 15;
|
||||||
|
uint32_t mantissa = input.bits & 0x7fffff;
|
||||||
|
if (exponent <= 0)
|
||||||
|
return sign;
|
||||||
|
if (exponent >= 31)
|
||||||
|
return sign | 0x7c00;
|
||||||
|
|
||||||
|
mantissa += 0x1000;
|
||||||
|
if (mantissa & 0x800000)
|
||||||
|
{
|
||||||
|
mantissa = 0;
|
||||||
|
if (++exponent >= 31)
|
||||||
|
return sign | 0x7c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sign | ((uint16_t)exponent << 10) | (mantissa >> 13);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_getColor(const struct LG_Transport * this,
|
||||||
|
unsigned x, unsigned y, uint8_t * r, uint8_t * g, uint8_t * b)
|
||||||
|
{
|
||||||
|
const unsigned widthRange = this->width > 1 ? this->width - 1 : 1;
|
||||||
|
const unsigned heightRange = this->height > 1 ? this->height - 1 : 1;
|
||||||
|
*r = (uint64_t)x * 255 / widthRange;
|
||||||
|
*g = (uint64_t)y * 255 / heightRange;
|
||||||
|
*b = ((x / 32) ^ (y / 32)) & 1 ? 0x30 : 0x90;
|
||||||
|
|
||||||
|
unsigned boxSize = this->width < this->height ?
|
||||||
|
this->width : this->height;
|
||||||
|
if (boxSize > 64)
|
||||||
|
boxSize = 64;
|
||||||
|
const unsigned rangeX = this->width > boxSize ? this->width - boxSize : 1;
|
||||||
|
const unsigned rangeY = this->height > boxSize ?
|
||||||
|
this->height - boxSize : 1;
|
||||||
|
const unsigned boxX = (this->serial * 7) % rangeX;
|
||||||
|
const unsigned boxY = (this->serial * 5) % rangeY;
|
||||||
|
if (x < boxX || y < boxY || x >= boxX + boxSize || y >= boxY + boxSize)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const uint32_t color = this->serial * 2654435761U;
|
||||||
|
*r = color >> 16;
|
||||||
|
*g = color >> 8;
|
||||||
|
*b = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_generateFrame(struct LG_Transport * this, FrameBuffer * fb)
|
||||||
|
{
|
||||||
|
uint8_t * data = framebuffer_get_data(fb);
|
||||||
|
if (this->format.type == FRAME_TYPE_BGR_32)
|
||||||
|
memset(data, 0, (size_t)this->format.pitch * this->height);
|
||||||
|
|
||||||
|
for (unsigned y = 0; y < this->height; ++y)
|
||||||
|
for (unsigned x = 0; x < this->width; ++x)
|
||||||
|
{
|
||||||
|
uint8_t r, g, b;
|
||||||
|
test_getColor(this, x, y, &r, &g, &b);
|
||||||
|
const size_t pixel = (size_t)y * this->width + x;
|
||||||
|
switch (this->format.type)
|
||||||
|
{
|
||||||
|
case FRAME_TYPE_BGRA:
|
||||||
|
((uint32_t *)data)[pixel] =
|
||||||
|
0xff000000U | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FRAME_TYPE_BGR_32:
|
||||||
|
{
|
||||||
|
uint8_t * bgr = data + (size_t)y * this->format.pitch + x * 3;
|
||||||
|
bgr[0] = b;
|
||||||
|
bgr[1] = g;
|
||||||
|
bgr[2] = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FRAME_TYPE_RGBA:
|
||||||
|
((uint32_t *)data)[pixel] =
|
||||||
|
0xff000000U | ((uint32_t)b << 16) | ((uint32_t)g << 8) | r;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FRAME_TYPE_RGB_24:
|
||||||
|
data[pixel * 3 ] = r;
|
||||||
|
data[pixel * 3 + 1] = g;
|
||||||
|
data[pixel * 3 + 2] = b;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FRAME_TYPE_RGBA10:
|
||||||
|
{
|
||||||
|
const float r709 = (float)r * 4.0f / 255.0f;
|
||||||
|
const float g709 = (float)g * 4.0f / 255.0f;
|
||||||
|
const float b709 = (float)b * 4.0f / 255.0f;
|
||||||
|
const float r2020 =
|
||||||
|
r709 * 0.6274039f + g709 * 0.3292830f + b709 * 0.0433131f;
|
||||||
|
const float g2020 =
|
||||||
|
r709 * 0.0690973f + g709 * 0.9195404f + b709 * 0.0113623f;
|
||||||
|
const float b2020 =
|
||||||
|
r709 * 0.0163914f + g709 * 0.0880133f + b709 * 0.8955953f;
|
||||||
|
((uint32_t *)data)[pixel] =
|
||||||
|
((uint32_t)test_scRGBToPQ(this, r2020) ) |
|
||||||
|
((uint32_t)test_scRGBToPQ(this, g2020) << 10) |
|
||||||
|
((uint32_t)test_scRGBToPQ(this, b2020) << 20) |
|
||||||
|
(3U << 30);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FRAME_TYPE_RGBA16F:
|
||||||
|
{
|
||||||
|
uint16_t * rgba = (uint16_t *)data + pixel * 4;
|
||||||
|
rgba[0] = test_floatToHalf((float)r * 4.0f / 255.0f);
|
||||||
|
rgba[1] = test_floatToHalf((float)g * 4.0f / 255.0f);
|
||||||
|
rgba[2] = test_floatToHalf((float)b * 4.0f / 255.0f);
|
||||||
|
rgba[3] = test_floatToHalf(1.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
DEBUG_UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
framebuffer_set_write_ptr(fb, (size_t)this->format.pitch * this->height);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus test_nextFrame(LG_Transport * this, bool useDMA,
|
||||||
|
LG_TransportFrame * frame)
|
||||||
|
{
|
||||||
|
if (!this->connected)
|
||||||
|
return LG_TRANSPORT_DISCONNECTED;
|
||||||
|
if (this->framePending)
|
||||||
|
return LG_TRANSPORT_ERROR;
|
||||||
|
if (this->frameCount && this->serial >= this->frameCount)
|
||||||
|
return LG_TRANSPORT_END;
|
||||||
|
|
||||||
|
if (this->realtime)
|
||||||
|
{
|
||||||
|
const uint64_t now = nanotime();
|
||||||
|
if (now < this->nextFrameTime)
|
||||||
|
{
|
||||||
|
const uint64_t remaining = this->nextFrameTime - now;
|
||||||
|
usleep((remaining > 1000000 ? 1000000 : remaining) / 1000);
|
||||||
|
return LG_TRANSPORT_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool formatChanged = this->cycleFormats && test_setFormat(this,
|
||||||
|
this->serial % ARRAY_LENGTH(testFormats));
|
||||||
|
++this->serial;
|
||||||
|
struct TestBuffer * buffer = &this->buffers[this->bufferIndex];
|
||||||
|
this->bufferIndex = (this->bufferIndex + 1) % TEST_BUFFER_COUNT;
|
||||||
|
framebuffer_prepare(buffer->framebuffer);
|
||||||
|
test_generateFrame(this, buffer->framebuffer);
|
||||||
|
|
||||||
|
memset(frame, 0, sizeof(*frame));
|
||||||
|
frame->serial = this->serial;
|
||||||
|
frame->timestamp = this->realtime ? nanotime() :
|
||||||
|
this->serial * (1000000000ULL / this->frameRate);
|
||||||
|
frame->format = &this->format;
|
||||||
|
frame->framebuffer = buffer->framebuffer;
|
||||||
|
frame->dmaFD = -1;
|
||||||
|
|
||||||
|
if (this->serial == 1 || formatChanged)
|
||||||
|
frame->damageRectsCount = 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unsigned boxSize = this->width < this->height ?
|
||||||
|
this->width : this->height;
|
||||||
|
if (boxSize > 64)
|
||||||
|
boxSize = 64;
|
||||||
|
const unsigned rangeX = this->width > boxSize ?
|
||||||
|
this->width - boxSize : 1;
|
||||||
|
const unsigned rangeY = this->height > boxSize ?
|
||||||
|
this->height - boxSize : 1;
|
||||||
|
this->damage[0] = (FrameDamageRect) {
|
||||||
|
.x = ((this->serial - 1) * 7) % rangeX,
|
||||||
|
.y = ((this->serial - 1) * 5) % rangeY,
|
||||||
|
.width = boxSize,
|
||||||
|
.height = boxSize,
|
||||||
|
};
|
||||||
|
this->damage[1] = (FrameDamageRect) {
|
||||||
|
.x = (this->serial * 7) % rangeX,
|
||||||
|
.y = (this->serial * 5) % rangeY,
|
||||||
|
.width = boxSize,
|
||||||
|
.height = boxSize,
|
||||||
|
};
|
||||||
|
frame->damageRects = this->damage;
|
||||||
|
frame->damageRectsCount = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->framePending = true;
|
||||||
|
this->nextFrameTime += 1000000000ULL / this->frameRate;
|
||||||
|
return LG_TRANSPORT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_releaseFrame(LG_Transport * this, LG_TransportFrame * frame)
|
||||||
|
{
|
||||||
|
this->framePending = false;
|
||||||
|
memset(frame, 0, sizeof(*frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus test_nextPointer(LG_Transport * this,
|
||||||
|
LG_TransportPointer * pointer)
|
||||||
|
{
|
||||||
|
if (!this->connected)
|
||||||
|
return LG_TRANSPORT_DISCONNECTED;
|
||||||
|
usleep(1000);
|
||||||
|
return LG_TRANSPORT_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_releasePointer(LG_Transport * this,
|
||||||
|
LG_TransportPointer * pointer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus test_sendControl(LG_Transport * this,
|
||||||
|
const LG_TransportControl * control, LG_TransportControlToken * token)
|
||||||
|
{
|
||||||
|
if (!this->connected)
|
||||||
|
return LG_TRANSPORT_DISCONNECTED;
|
||||||
|
return LG_TRANSPORT_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_TransportStatus test_controlStatus(LG_Transport * this,
|
||||||
|
LG_TransportControlToken token)
|
||||||
|
{
|
||||||
|
return this->connected ? LG_TRANSPORT_UNAVAILABLE :
|
||||||
|
LG_TRANSPORT_DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LG_TransportOps LGT_Test =
|
||||||
|
{
|
||||||
|
.name = "test",
|
||||||
|
.setup = test_setup,
|
||||||
|
.create = test_create,
|
||||||
|
.destroy = test_destroy,
|
||||||
|
.connect = test_connect,
|
||||||
|
.disconnect = test_disconnect,
|
||||||
|
.sessionValid = test_sessionValid,
|
||||||
|
.supportsDMA = test_supportsDMA,
|
||||||
|
.attachRenderer = test_attachRenderer,
|
||||||
|
.detachRenderer = test_detachRenderer,
|
||||||
|
.nextFrame = test_nextFrame,
|
||||||
|
.releaseFrame = test_releaseFrame,
|
||||||
|
.nextPointer = test_nextPointer,
|
||||||
|
.releasePointer = test_releasePointer,
|
||||||
|
.sendControl = test_sendControl,
|
||||||
|
.controlStatus = test_controlStatus,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user