[idd] transport: abstract LGMP implementation

Introduce transport, frame, and control interfaces with an LGMP factory
backend.

Move LGMP and IVSHMEM implementation details under transport/lgmp and
expose direct frame-buffer memory through a neutral capability.
This commit is contained in:
Geoffrey McRae
2026-08-07 16:39:07 +10:00
parent 30a1383d5e
commit 6725133375
46 changed files with 1026 additions and 480 deletions

View File

@@ -0,0 +1,208 @@
/**
* 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 "transport/lgmp/CIVSHMEM.h"
#include <Windows.h>
#include <SetupAPI.h>
#include <algorithm>
#include <winioctl.h>
#include "CDebug.h"
#include "ivshmem/ivshmem.h"
CIVSHMEM::CIVSHMEM()
{
}
CIVSHMEM::~CIVSHMEM()
{
if (m_handle == INVALID_HANDLE_VALUE)
return;
Close();
CloseHandle(m_handle);
}
bool CIVSHMEM::Init()
{
// Init may be called more than once (the adapter init is retried at boot
// until IVSHMEM enumerates). Release any handle from a prior attempt so we
// do not leak it when re-enumerating.
if (m_handle != INVALID_HANDLE_VALUE)
{
Close();
CloseHandle(m_handle);
m_handle = INVALID_HANDLE_VALUE;
}
HDEVINFO devInfoSet;
SP_DEVINFO_DATA devInfoData;
SP_DEVICE_INTERFACE_DATA devInterfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA infData = nullptr;
devInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_IVSHMEM, nullptr, nullptr,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
devInfoData.cbSize = sizeof(devInfoData);
devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
m_devices.clear();
for (int i = 0; SetupDiEnumDeviceInfo(devInfoSet, i, &devInfoData); ++i)
{
DWORD bus, addr;
if (!SetupDiGetDeviceRegistryProperty(devInfoSet, &devInfoData, SPDRP_BUSNUMBER,
nullptr, (BYTE*)&bus, sizeof(bus), nullptr))
bus = 0xffff;
if (!SetupDiGetDeviceRegistryProperty(devInfoSet, &devInfoData, SPDRP_ADDRESS,
nullptr, (BYTE*)&addr, sizeof(addr), nullptr))
addr = 0xffff;
IVSHMEMData data;
data.busAddr = ((DWORD64)bus) << 32 | addr;
memcpy(&data.devInfoData, &devInfoData, sizeof(devInfoData));
m_devices.push_back(data);
}
HRESULT hr = GetLastError();
if (hr != ERROR_NO_MORE_ITEMS)
{
m_devices.clear();
SetupDiDestroyDeviceInfoList(devInfoSet);
DEBUG_ERROR_HR(hr, "Enumeration Failed");
return false;
}
std::sort(m_devices.begin(), m_devices.end(),
[](const IVSHMEMData & a, const IVSHMEMData & b) -> bool
{ return a.busAddr < b.busAddr; });
HKEY hkeyLG;
IVSHMEMData * device = nullptr;
DWORD shmDevice = 0;
if (RegOpenKeyA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Looking Glass", &hkeyLG) == ERROR_SUCCESS)
{
DWORD dataType;
DWORD dataSize = sizeof(shmDevice);
if (RegQueryValueExA(hkeyLG, "shmDevice", nullptr, &dataType, (BYTE*)&shmDevice, &dataSize) != ERROR_SUCCESS ||
dataType != REG_DWORD)
shmDevice = 0;
}
DWORD i = 0;
for (auto it = m_devices.begin(); it != m_devices.end(); ++it, ++i)
{
DWORD bus = it->busAddr >> 32;
DWORD addr = it->busAddr & 0xFFFFFFFF;
DEBUG_INFO("IVSHMEM %u%c on bus 0x%lx, device 0x%lx, function 0x%lx",
i, i == shmDevice ? '*' : ' ', bus, addr >> 16, addr & 0xFFFF);
if (i == shmDevice)
device = &(*it);
}
if (!device)
{
DEBUG_ERROR("Failed to match a shmDevice");
SetupDiDestroyDeviceInfoList(devInfoSet);
return false;
}
if (SetupDiEnumDeviceInterfaces(devInfoSet, &devInfoData, &GUID_DEVINTERFACE_IVSHMEM, 0, &devInterfaceData) == FALSE)
{
DEBUG_ERROR_HR(GetLastError(), "SetupDiEnumDeviceInterfaces");
SetupDiDestroyDeviceInfoList(devInfoSet);
return false;
}
DWORD reqSize = 0;
SetupDiGetDeviceInterfaceDetail(devInfoSet, &devInterfaceData, nullptr, 0, &reqSize, nullptr);
if (!reqSize)
{
DEBUG_ERROR_HR(GetLastError(), "SetupDiGetDeviceInterfaceDetail");
SetupDiDestroyDeviceInfoList(devInfoSet);
return false;
}
infData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(1, reqSize);
infData->cbSize = sizeof(PSP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(devInfoSet, &devInterfaceData, infData, reqSize, nullptr, nullptr))
{
DEBUG_ERROR_HR(GetLastError(), "SetupDiGetDeviceInterfaceDetail");
SetupDiDestroyDeviceInfoList(devInfoSet);
return false;
}
m_handle = CreateFile(infData->DevicePath, 0, 0, nullptr, OPEN_EXISTING, 0, 0);
if (m_handle == INVALID_HANDLE_VALUE)
{
DEBUG_ERROR_HR(GetLastError(), "CreateFile");
SetupDiDestroyDeviceInfoList(devInfoSet);
return false;
}
SetupDiDestroyDeviceInfoList(devInfoSet);
DEBUG_TRACE("IVSHMEM Initialized");
return true;
}
bool CIVSHMEM::Open()
{
IVSHMEM_SIZE size;
if (!DeviceIoControl(m_handle, IOCTL_IVSHMEM_REQUEST_SIZE, nullptr, 0, &size, sizeof(size), nullptr, nullptr))
{
DEBUG_ERROR_HR(GetLastError(), "Failed to request ivshmem size");
return false;
}
IVSHMEM_MMAP_CONFIG config = {};
IVSHMEM_MMAP map = {};
config.cacheMode = IVSHMEM_CACHE_WRITECOMBINED;
if (!DeviceIoControl(m_handle, IOCTL_IVSHMEM_REQUEST_MMAP, &config, sizeof(config), &map, sizeof(map), nullptr, nullptr))
{
DEBUG_ERROR_HR(GetLastError(), "Failed to request ivshmem mmap");
return false;
}
m_size = (size_t)size;
m_mem = map.ptr;
DEBUG_INFO("IVSHMEM opened, size: %zu MiB", m_size / 1048576);
return true;
}
void CIVSHMEM::Close()
{
if (m_mem == nullptr)
return;
if (!DeviceIoControl(m_handle, IOCTL_IVSHMEM_RELEASE_MMAP, nullptr, 0, nullptr, 0, nullptr, nullptr))
{
DEBUG_ERROR("Failed to release ivshmem mmap");
return;
}
m_size = 0;
m_mem = nullptr;
}

View File

@@ -0,0 +1,51 @@
/**
* 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
*/
#pragma once
#include <Windows.h>
#include <SetupAPI.h>
#include <vector>
class CIVSHMEM
{
private:
struct IVSHMEMData
{
SP_DEVINFO_DATA devInfoData;
DWORD64 busAddr;
};
std::vector<struct IVSHMEMData> m_devices;
HANDLE m_handle = INVALID_HANDLE_VALUE;
size_t m_size = 0;
void * m_mem = nullptr;
public:
CIVSHMEM();
~CIVSHMEM();
bool Init();
bool Open();
void Close();
size_t GetSize() const { return m_size; }
void * GetMem () const { return m_mem; }
};

View File

@@ -0,0 +1,301 @@
/**
* 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 "transport/lgmp/CLGMPControl.h"
#include "CDebug.h"
#include <string.h>
#include <utility>
static const uint32_t MAX_POINTER_SIZE =
(uint32_t)(sizeof(KVMFRCursor) + (512 * 512 * 4));
static const struct LGMPQueueConfig POINTER_QUEUE_CONFIG =
{
LGMP_Q_POINTER, //queueID
LGMP_Q_POINTER_LEN, //numMesages
1000 //subTimeout
};
CLGMPControl::~CLGMPControl()
{
DeInit();
}
bool CLGMPControl::Initialize()
{
if (m_pointerQueue)
return true;
LGMP_STATUS status;
if ((status = m_host.CreateQueue(
POINTER_QUEUE_CONFIG, &m_pointerQueue)) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostQueueCreate Failed (Pointer): %s",
lgmpStatusString(status));
return false;
}
for (int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
{
if ((status = m_host.Allocate(
MAX_POINTER_SIZE, &m_pointerMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer): %s",
lgmpStatusString(status));
return false;
}
memset(lgmpHostMemPtr(m_pointerMemory[i]), 0, MAX_POINTER_SIZE);
}
for (int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
{
if ((status = m_host.Allocate(
MAX_POINTER_SIZE, &m_pointerShapeMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Shapes): %s",
lgmpStatusString(status));
return false;
}
memset(lgmpHostMemPtr(m_pointerShapeMemory[i]), 0, MAX_POINTER_SIZE);
}
for (int i = 0; i < COLOR_TRANSFORM_BUFFERS; ++i)
{
if ((status = m_host.Allocate(
sizeof(KVMFRCursor) + sizeof(KVMFRColorTransform),
&m_pointerTransformMemory[i])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAlloc Failed (Pointer Transform): %s",
lgmpStatusString(status));
return false;
}
memset(lgmpHostMemPtr(m_pointerTransformMemory[i]), 0,
sizeof(KVMFRCursor) + sizeof(KVMFRColorTransform));
}
return true;
}
void CLGMPControl::DeInit()
{
for (int i = 0; i < LGMP_Q_POINTER_LEN; ++i)
lgmpHostMemFree(&m_pointerMemory[i]);
for (int i = 0; i < POINTER_SHAPE_BUFFERS; ++i)
lgmpHostMemFree(&m_pointerShapeMemory[i]);
for (int i = 0; i < COLOR_TRANSFORM_BUFFERS; ++i)
lgmpHostMemFree(&m_pointerTransformMemory[i]);
m_pointerQueue = nullptr;
m_pointerShape = nullptr;
m_pointerMemoryIndex = 0;
m_pointerShapeIndex = 0;
m_pointerTransformIndex = 0;
}
LGMP_STATUS CLGMPControl::ReadDataWithSource(void * data, size_t * size,
uint32_t * sourceClientID)
{
return lgmpHostReadDataWithSource(
m_pointerQueue, data, size, sourceClientID);
}
LGMP_STATUS CLGMPControl::AckData()
{
return lgmpHostAckData(m_pointerQueue);
}
bool CLGMPControl::HasNewSubscribers()
{
return lgmpHostQueueNewSubs(m_pointerQueue) != 0;
}
void CLGMPControl::SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info,
const BYTE * data, UINT sdrWhiteLevel)
{
PLGMPMemory mem;
if (info.CursorShapeInfo.CursorType == IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED)
{
mem = m_pointerMemory[m_pointerMemoryIndex];
if (++m_pointerMemoryIndex == LGMP_Q_POINTER_LEN)
m_pointerMemoryIndex = 0;
}
else
{
mem = m_pointerShapeMemory[m_pointerShapeIndex];
if (++m_pointerShapeIndex == POINTER_SHAPE_BUFFERS)
m_pointerShapeIndex = 0;
}
KVMFRCursor * cursor = (KVMFRCursor *)lgmpHostMemPtr(mem);
cursor->sdrWhiteLevel = sdrWhiteLevel ?
sdrWhiteLevel : KVMFR_SDR_WHITE_LEVEL_DEFAULT;
m_cursorVisible = info.IsCursorVisible;
uint32_t flags = CURSOR_FLAG_VISIBLE_VALID;
if (info.IsCursorVisible)
{
m_cursorX = info.X;
m_cursorY = info.Y;
cursor->x = (int16_t)info.X;
cursor->y = (int16_t)info.Y;
flags |= CURSOR_FLAG_POSITION | CURSOR_FLAG_VISIBLE;
}
if (info.CursorShapeInfo.CursorType != IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED)
{
memcpy(cursor + 1, data,
(size_t)info.CursorShapeInfo.Height * info.CursorShapeInfo.Pitch);
cursor->hx = (int8_t )info.CursorShapeInfo.XHot;
cursor->hy = (int8_t )info.CursorShapeInfo.YHot;
cursor->width = (uint32_t)info.CursorShapeInfo.Width;
cursor->height = (uint32_t)info.CursorShapeInfo.Height;
cursor->pitch = (uint32_t)info.CursorShapeInfo.Pitch;
switch (info.CursorShapeInfo.CursorType)
{
case IDDCX_CURSOR_SHAPE_TYPE_ALPHA:
cursor->type = CURSOR_TYPE_COLOR;
break;
case IDDCX_CURSOR_SHAPE_TYPE_MASKED_COLOR:
cursor->type = CURSOR_TYPE_MASKED_COLOR;
break;
}
flags |= CURSOR_FLAG_SHAPE;
m_pointerShape = mem;
}
LGMP_STATUS status;
while ((status = lgmpHostQueuePost(
m_pointerQueue, flags, mem)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_FULL)
{
Sleep(1);
continue;
}
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer): %s",
lgmpStatusString(status));
break;
}
}
void CLGMPControl::SetColorTransform(
std::shared_ptr<const D12ColorTransform> transform)
{
AcquireSRWLockExclusive(&m_colorTransformLock);
m_colorTransform = std::move(transform);
ReleaseSRWLockExclusive(&m_colorTransformLock);
SendColorTransform();
}
std::shared_ptr<const D12ColorTransform>
CLGMPControl::GetColorTransform() const
{
AcquireSRWLockShared(&m_colorTransformLock);
std::shared_ptr<const D12ColorTransform> transform = m_colorTransform;
ReleaseSRWLockShared(&m_colorTransformLock);
return transform;
}
void CLGMPControl::SendColorTransform()
{
if (!m_pointerQueue || !m_pointerTransformMemory[0])
return;
PLGMPMemory mem = m_pointerTransformMemory[m_pointerTransformIndex];
if (++m_pointerTransformIndex == COLOR_TRANSFORM_BUFFERS)
m_pointerTransformIndex = 0;
KVMFRCursor * cursor = (KVMFRCursor *)lgmpHostMemPtr(mem);
KVMFRColorTransform * output =
(KVMFRColorTransform *)(cursor + 1);
const auto transform = GetColorTransform();
output->flags = 0;
if (transform)
{
if (transform->matrixEnabled)
output->flags |= KVMFR_COLOR_TRANSFORM_MATRIX;
if (transform->lutEnabled)
output->flags |= KVMFR_COLOR_TRANSFORM_LUT;
memcpy(output->matrix, transform->matrix, sizeof(output->matrix));
output->scalar = transform->scalar;
memcpy(output->lut, transform->lut, sizeof(output->lut));
}
LGMP_STATUS status;
while ((status = lgmpHostQueuePost(m_pointerQueue,
CURSOR_FLAG_COLOR_TRANSFORM, mem)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_FULL)
{
Sleep(1);
continue;
}
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer Transform): %s",
lgmpStatusString(status));
break;
}
}
void CLGMPControl::ResendCursor()
{
PLGMPMemory mem = m_pointerShape;
if (!mem)
return;
KVMFRCursor* cursor = (KVMFRCursor*)lgmpHostMemPtr(mem);
cursor->x = (int16_t)m_cursorX;
cursor->y = (int16_t)m_cursorY;
const uint32_t flags =
CURSOR_FLAG_POSITION | CURSOR_FLAG_SHAPE | CURSOR_FLAG_VISIBLE_VALID |
(m_cursorVisible ? CURSOR_FLAG_VISIBLE : 0);
LGMP_STATUS status;
while ((status = lgmpHostQueuePost(
m_pointerQueue, flags, mem)) != LGMP_OK)
{
if (status == LGMP_ERR_QUEUE_FULL)
{
Sleep(1);
continue;
}
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer): %s",
lgmpStatusString(status));
break;
}
}
void CLGMPControl::ResendState()
{
ResendCursor();
SendColorTransform();
}

View File

@@ -0,0 +1,85 @@
/**
* 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
*/
#pragma once
#include "transport/lgmp/CLGMPHost.h"
#include "transport/IControlTransport.h"
#include "common/KVMFR.h"
#include <Windows.h>
#include <wdf.h>
#include <IddCx.h>
#include <memory>
class CLGMPTransport;
class CLGMPControl final : public IControlTransport
{
private:
friend class CLGMPTransport;
static constexpr int POINTER_SHAPE_BUFFERS = 3;
static constexpr int COLOR_TRANSFORM_BUFFERS = 3;
CLGMPHost& m_host;
PLGMPHostQueue m_pointerQueue = nullptr;
PLGMPMemory m_pointerMemory[LGMP_Q_POINTER_LEN] = {};
PLGMPMemory m_pointerShapeMemory[POINTER_SHAPE_BUFFERS] = {};
PLGMPMemory m_pointerTransformMemory[COLOR_TRANSFORM_BUFFERS] = {};
PLGMPMemory m_pointerShape = nullptr;
int m_pointerMemoryIndex = 0;
int m_pointerShapeIndex = 0;
int m_pointerTransformIndex = 0;
bool m_cursorVisible = false;
int m_cursorX = 0;
int m_cursorY = 0;
mutable SRWLOCK m_colorTransformLock = SRWLOCK_INIT;
std::shared_ptr<const D12ColorTransform> m_colorTransform;
void SendColorTransform();
void ResendCursor();
bool Initialize();
void DeInit();
LGMP_STATUS ReadDataWithSource(void * data, size_t * size,
uint32_t * sourceClientID);
LGMP_STATUS AckData();
bool HasNewSubscribers();
void ResendState();
public:
explicit CLGMPControl(CLGMPHost& host) :
m_host(host) {}
~CLGMPControl() override;
CLGMPControl(const CLGMPControl&) = delete;
CLGMPControl& operator=(const CLGMPControl&) = delete;
void SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info, const BYTE * data,
UINT sdrWhiteLevel) override;
void SetColorTransform(
std::shared_ptr<const D12ColorTransform> transform) override;
std::shared_ptr<const D12ColorTransform>
GetColorTransform() const override;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,215 @@
/**
* 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
*/
#pragma once
#include <Windows.h>
#include <atomic>
#include <stdint.h>
extern "C" {
#include "lgmp/host.h"
}
#include "common/KVMFR.h"
#include "transport/CFrameScheduler.h"
#include "transport/FrameMemoryLimits.h"
#include "transport/IFrameTransport.h"
class CIVSHMEM;
class CLGMPHost;
class CLGMPTransport;
struct LGMPBuffer;
class CLGMPFrameTransport final : public IFrameTransport
{
private:
friend class CLGMPTransport;
struct SubscriberSnapshot
{
uint32_t clientIDs [LGMP_MAX_CLIENTS] = {};
uint32_t ownerClientIDs[LGMP_MAX_CLIENTS] = {};
unsigned clientCount = 0;
unsigned ownerClientCount = 0;
LGMP_STATUS status = LGMP_OK;
};
private:
enum SharedFramePostResult
{
SHARED_FRAME_FAILED,
SHARED_FRAME_IDLE,
SHARED_FRAME_POSTED,
};
struct FrameDelivery
{
uint64_t sharedOwnerToken = 0;
unsigned ownerQueueMask = 0;
uint32_t sharedOwnerClientID = 0;
bool sharedOwnerPending = false;
bool sharedPending = false;
};
struct OwnerDelivery
{
uint64_t token = 0;
uint32_t clientID = 0;
unsigned frameIndex = 0;
bool active = false;
};
CLGMPHost& m_host;
CIVSHMEM& m_ivshmem;
PLGMPHostQueue m_frameQueue = nullptr;
PLGMPHostQueue m_frameOwnerQueue[LGMP_Q_FRAME_LEN] = {};
CFrameScheduler m_frameScheduler;
size_t m_alignSize = 0;
size_t m_frameMemoryOffset = 0;
size_t m_maxFrameSize = 0;
// LGMP publication precedes copy completion. Replay only completed frames;
// the deferred index tracks the newest frame still owed to the owner.
std::atomic<LONG> m_submittedFrameIndex = -1;
std::atomic<LONG> m_readyFrameIndex = -1;
LONG m_deferredOwnerFrameIndex = -1;
std::atomic<bool> m_frameInFlight[LGMP_Q_FRAME_BUFFER_LEN] = {};
bool m_frameCompleted[LGMP_Q_FRAME_BUFFER_LEN] = {};
SRWLOCK m_framePublishLock = SRWLOCK_INIT;
uint64_t m_framePublishSequence = 0;
uint64_t m_frameLastPublishSequence[LGMP_Q_FRAME_BUFFER_LEN] = {};
FrameDelivery m_frameDelivery[LGMP_Q_FRAME_BUFFER_LEN] = {};
OwnerDelivery m_ownerDelivery[LGMP_Q_FRAME_LEN] = {};
uint32_t m_formatVer = 0;
uint32_t m_frameSerial = 0;
PLGMPMemory m_frameMemory[LGMP_Q_FRAME_BUFFER_LEN] = {};
KVMFRFrame * m_frame [LGMP_Q_FRAME_BUFFER_LEN] = {};
LGMPBuffer * m_frameBuffer[LGMP_Q_FRAME_BUFFER_LEN] = {};
unsigned m_width = 0;
unsigned m_height = 0;
unsigned m_frameWidth = 0;
unsigned m_frameHeight = 0;
unsigned m_pitch = 0;
DXGI_FORMAT m_format = DXGI_FORMAT_UNKNOWN;
FrameType m_frameType = FRAME_TYPE_INVALID;
// Previous HDR metadata used to detect changes for formatVer bumps.
uint16_t m_lastHDRDisplayPrimary[3][2] = {};
uint16_t m_lastHDRWhitePoint[2] = {};
uint32_t m_lastHDRMaxDisplayLuminance = 0;
uint32_t m_lastHDRMinDisplayLuminance = 0;
uint32_t m_lastHDRMaxContentLightLevel = 0;
uint32_t m_lastHDRMaxFrameAverageLightLevel = 0;
uint32_t m_lastSDRWhiteLevel = 0;
bool m_lastHDRActive = false;
bool m_lastHDRMetadata = false;
void ProcessFrameDeliveries();
bool FrameBufferReferenced(unsigned frameIndex) const;
int FindAvailableFrameBuffer(bool allowReady) const;
int FindNewestCompletedFrame(unsigned excludeFrameIndex) const;
int FindAvailableOwnerQueue(unsigned preferredIndex) const;
unsigned CountOwnerDeliveries(uint32_t clientID) const;
bool HasMatchingOwnerDelivery(uint32_t clientID, unsigned frameIndex,
uint64_t token) const;
SharedFramePostResult PostSharedFrame(unsigned frameIndex,
uint32_t excludeClientID, uint64_t now);
bool PostSharedOwnerFrame(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule);
CLGMPFrameTransport(CLGMPHost& host, CIVSHMEM& ivshmem);
bool Initialize();
void SealMemoryLayout();
bool Setup(size_t alignSize);
void DeInit();
FrameMemoryLimits GetMemoryLimits() const;
SubscriberSnapshot SnapshotSubscribers() const;
void FinalizeSubscribers(
const SubscriberSnapshot& snapshot, uint64_t now);
bool UpdateSchedule(uint32_t sourceClientID,
const FrameScheduleUpdate& schedule, uint64_t now);
public:
~CLGMPFrameTransport() override;
CLGMPFrameTransport(const CLGMPFrameTransport&) = delete;
CLGMPFrameTransport& operator=(const CLGMPFrameTransport&) = delete;
size_t GetMaxFrameSize() const override { return m_maxFrameSize; }
bool FrameBufferAvailable(const CFrameScheduler::Schedule& schedule,
bool allowReadyReplacement = true) override;
bool HasPublishedFrame() const override
{
return m_readyFrameIndex.load(std::memory_order_acquire) >= 0;
}
void ProcessDeliveries() override;
bool GetPendingDeliveryTarget(
uint64_t now, uint64_t& target) override;
bool RetryPendingDelivery(uint64_t now, bool& retry) override;
PreparedFrameBuffer PrepareFrameBuffer(unsigned pitch,
const D12FrameFormat& srcFormat, const D12FrameFormat& dstFormat,
const RECT * dirtyRects, unsigned nbDirtyRects,
const CFrameScheduler::Schedule& schedule,
bool allowReadyReplacement = true) override;
bool PublishFrameBuffer(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule,
bool& deliveredToOwner) override;
bool RepublishFrameBuffer(
const CFrameScheduler::Schedule& schedule) override;
bool TryFrameSubmitted(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule) override;
void CommitFrameBuffer(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule, bool periodic,
bool deliveredToOwner) override;
void AbortFrameBuffer(unsigned frameIndex) override;
void FailFrameBuffer(unsigned frameIndex) override;
void CompleteFrameBuffer(
unsigned frameIndex, bool succeeded) override;
void SetFrameTiming(unsigned frameIndex, uint64_t captureTime,
uint64_t postProcessTime, uint64_t copyTime, uint64_t readyTime,
uint64_t holdTime, const CFrameScheduler::Schedule& schedule,
uint64_t completedAt) override;
void WriteFrameBuffer(unsigned frameIndex, void * src, size_t offset,
size_t len, bool setWritePos) const override;
void WriteFrameBufferRows(unsigned frameIndex, void * src,
size_t offset, size_t rowBytes, size_t pitch,
unsigned rows) const override;
void FinalizeFrameBuffer(unsigned frameIndex) const override;
void ObserveFrame(uint64_t now) override;
void ForceFrame() override;
bool GetPublishTarget(uint64_t now, uint64_t& target,
CFrameScheduler::Schedule& schedule, bool& periodic,
bool& republish) override;
void FrameMissed(const CFrameScheduler::Schedule& schedule,
uint64_t now, bool periodic) override;
void FrameSuperseded() override;
HANDLE GetFrameScheduleEvent() const override
{
return m_frameScheduler.GetWakeEvent();
}
void TryRecordFrameTiming(uint64_t duration) override;
};

View File

@@ -0,0 +1,165 @@
/**
* 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 "transport/lgmp/CLGMPHost.h"
#include "transport/lgmp/CIVSHMEM.h"
#include "platform/CPlatformInfo.h"
#include "CDebug.h"
#include "VersionInfo.h"
#include "common/KVMFR.h"
#include <stdlib.h>
#include <string.h>
#include <sstream>
#include <string>
CLGMPHost::~CLGMPHost()
{
DeInit();
}
bool CLGMPHost::Initialize(CIVSHMEM& ivshmem)
{
if (m_host)
return true;
std::stringstream ss;
{
KVMFR kvmfr = {};
memcpy_s(kvmfr.magic, sizeof(kvmfr.magic), KVMFR_MAGIC, sizeof(KVMFR_MAGIC) - 1);
kvmfr.version = KVMFR_VERSION;
kvmfr.features =
KVMFR_FEATURE_SETCURSORPOS |
KVMFR_FEATURE_WINDOWSIZE |
KVMFR_FEATURE_FRAME_SCHEDULE;
strncpy_s(kvmfr.hostver, LG_VERSION_STR, sizeof(kvmfr.hostver) - 1);
ss.write(reinterpret_cast<const char *>(&kvmfr), sizeof(kvmfr));
}
{
const std::string & model = CPlatformInfo::GetCPUModel();
KVMFRRecord_VMInfo * vmInfo = static_cast<KVMFRRecord_VMInfo *>(calloc(1, sizeof(*vmInfo)));
if (!vmInfo)
{
DEBUG_ERROR("Failed to allocate KVMFRRecord_VMInfo");
return false;
}
vmInfo->cpus = static_cast<uint8_t>(CPlatformInfo::GetProcCount ());
vmInfo->cores = static_cast<uint8_t>(CPlatformInfo::GetCoreCount ());
vmInfo->sockets = static_cast<uint8_t>(CPlatformInfo::GetSocketCount());
const uint8_t * uuid = CPlatformInfo::GetUUID();
memcpy_s (vmInfo->uuid, sizeof(vmInfo->uuid), uuid, 16);
strncpy_s(vmInfo->capture, "Looking Glass IDD Driver", sizeof(vmInfo->capture));
KVMFRRecord * record = static_cast<KVMFRRecord *>(calloc(1, sizeof(*record)));
if (!record)
{
DEBUG_ERROR("Failed to allocate KVMFRRecord");
return false;
}
record->type = KVMFR_RECORD_VMINFO;
record->size = sizeof(*vmInfo) + (uint32_t)model.length() + 1;
ss.write(reinterpret_cast<const char*>(record ), sizeof(*record));
ss.write(reinterpret_cast<const char*>(vmInfo ), sizeof(*vmInfo));
ss.write(reinterpret_cast<const char*>(model.c_str()), model.length() + 1);
}
{
KVMFRRecord_OSInfo * osInfo = static_cast<KVMFRRecord_OSInfo *>(calloc(1, sizeof(*osInfo)));
if (!osInfo)
{
DEBUG_ERROR("Failed to allocate KVMFRRecord_OSInfo");
return false;
}
osInfo->os = KVMFR_OS_WINDOWS;
const std::string & osName = CPlatformInfo::GetProductName();
KVMFRRecord* record = static_cast<KVMFRRecord*>(calloc(1, sizeof(*record)));
if (!record)
{
DEBUG_ERROR("Failed to allocate KVMFRRecord");
return false;
}
record->type = KVMFR_RECORD_OSINFO;
record->size = sizeof(*osInfo) + (uint32_t)osName.length() + 1;
ss.write(reinterpret_cast<const char*>(record), sizeof(*record));
ss.write(reinterpret_cast<const char*>(osInfo), sizeof(*osInfo));
ss.write(reinterpret_cast<const char*>(osName.c_str()), osName.length() + 1);
}
LGMP_STATUS status;
std::string udata = ss.str();
if ((status = lgmpHostInit(ivshmem.GetMem(), (uint32_t)ivshmem.GetSize(),
&m_host, (uint32_t)udata.size(), (uint8_t*)&udata[0])) != LGMP_OK)
{
DEBUG_ERROR("lgmpHostInit Failed: %s", lgmpStatusString(status));
return false;
}
return true;
}
void CLGMPHost::DeInit()
{
if (m_host)
lgmpHostFree(&m_host);
}
LGMP_STATUS CLGMPHost::Process()
{
AcquireSRWLockExclusive(&m_processLock);
const LGMP_STATUS status = lgmpHostProcess(m_host);
ReleaseSRWLockExclusive(&m_processLock);
return status;
}
LGMP_STATUS CLGMPHost::CreateQueue(
const struct LGMPQueueConfig& config, PLGMPHostQueue * queue)
{
return lgmpHostQueueNew(m_host, config, queue);
}
LGMP_STATUS CLGMPHost::Allocate(uint32_t size, PLGMPMemory * memory)
{
return lgmpHostMemAlloc(m_host, size, memory);
}
LGMP_STATUS CLGMPHost::AllocateAligned(uint32_t size,
uint32_t alignment, PLGMPMemory * memory)
{
return lgmpHostMemAllocAligned(m_host, size, alignment, memory);
}
size_t CLGMPHost::Available() const
{
return lgmpHostMemAvail(m_host);
}

View 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
*/
#pragma once
#include <Windows.h>
#include <stddef.h>
#include <stdint.h>
extern "C" {
#include "lgmp/host.h"
}
class CIVSHMEM;
class CLGMPHost
{
private:
PLGMPHost m_host = nullptr;
SRWLOCK m_processLock = SRWLOCK_INIT;
public:
CLGMPHost() = default;
~CLGMPHost();
CLGMPHost(const CLGMPHost&) = delete;
CLGMPHost& operator=(const CLGMPHost&) = delete;
bool Initialize(CIVSHMEM& ivshmem);
void DeInit();
bool IsInitialized() const { return m_host != nullptr; }
LGMP_STATUS Process();
LGMP_STATUS CreateQueue(const struct LGMPQueueConfig& config,
PLGMPHostQueue * queue);
LGMP_STATUS Allocate(uint32_t size, PLGMPMemory * memory);
LGMP_STATUS AllocateAligned(uint32_t size, uint32_t alignment,
PLGMPMemory * memory);
size_t Available() const;
};

View File

@@ -0,0 +1,183 @@
/**
* 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 "transport/lgmp/CLGMPTransport.h"
#include "CDebug.h"
#include "common/KVMFR.h"
static bool TranslateFrameScheduleFlags(
uint32_t source, uint32_t& destination)
{
static const uint32_t validFlags =
KVMFR_FRAME_SCHEDULE_ACTIVE |
KVMFR_FRAME_SCHEDULE_RELEASE |
KVMFR_FRAME_SCHEDULE_RESET |
KVMFR_FRAME_SCHEDULE_IMMEDIATE;
if (source & ~validFlags)
return false;
destination = 0;
if (source & KVMFR_FRAME_SCHEDULE_ACTIVE)
destination |= FRAME_SCHEDULE_ACTIVE;
if (source & KVMFR_FRAME_SCHEDULE_RELEASE)
destination |= FRAME_SCHEDULE_RELEASE;
if (source & KVMFR_FRAME_SCHEDULE_RESET)
destination |= FRAME_SCHEDULE_RESET;
if (source & KVMFR_FRAME_SCHEDULE_IMMEDIATE)
destination |= FRAME_SCHEDULE_IMMEDIATE;
return true;
}
CLGMPTransport::CLGMPTransport() :
m_control(m_host),
m_frames(m_host, m_ivshmem)
{
}
ITransport::OpenResult CLGMPTransport::Open()
{
if (m_ivshmem.GetMem())
return OpenResult::SUCCESS;
if (!m_ivshmem.Init() || !m_ivshmem.Open())
return OpenResult::RETRY;
return OpenResult::SUCCESS;
}
bool CLGMPTransport::Initialize()
{
if (!m_host.Initialize(m_ivshmem))
return false;
// Preserve the shared-memory layout: frame queues precede the pointer queue
// and its retained cursor and color-transform allocations.
if (!m_frames.Initialize() || !m_control.Initialize())
return false;
m_frames.SealMemoryLayout();
return true;
}
bool CLGMPTransport::Setup(size_t alignment)
{
return m_frames.Setup(alignment);
}
void CLGMPTransport::Process(ITransportEvents& events)
{
const LGMP_STATUS processStatus = m_host.Process();
if (processStatus != LGMP_OK)
{
if (processStatus == LGMP_ERR_CORRUPTED)
{
DEBUG_WARN(
"LGMP reported the shared memory has been corrupted, attempting to recover\n");
// TODO: reinitialize LGMP.
return;
}
DEBUG_ERROR("lgmpHostProcess Failed: %s",
lgmpStatusString(processStatus));
// TODO: shut down LGMP.
return;
}
const uint64_t now = CFrameScheduler::Nanotime();
// Take the frame subscriber snapshot before processing scheduling messages,
// then publish both updates together just as the original timer did.
const CLGMPFrameTransport::SubscriberSnapshot subscribers =
m_frames.SnapshotSubscribers();
uint8_t data[LGMP_MSGS_SIZE];
size_t size;
uint32_t sourceClientID;
LGMP_STATUS status;
while ((status = m_control.ReadDataWithSource(
data, &size, &sourceClientID)) == LGMP_OK)
{
KVMFRMessage * msg = reinterpret_cast<KVMFRMessage *>(data);
switch (msg->type)
{
case KVMFR_MESSAGE_SETCURSORPOS:
{
KVMFRSetCursorPos * position =
reinterpret_cast<KVMFRSetCursorPos *>(msg);
events.OnSetCursorPos(position->x, position->y);
break;
}
case KVMFR_MESSAGE_WINDOWSIZE:
{
KVMFRWindowSize * window =
reinterpret_cast<KVMFRWindowSize *>(msg);
events.OnSetResolution(window->w, window->h);
break;
}
case KVMFR_MESSAGE_FRAME_SCHEDULE:
{
const KVMFRFrameSchedule * schedule =
reinterpret_cast<KVMFRFrameSchedule *>(msg);
uint32_t translatedFlags = 0;
bool valid = size == sizeof(*schedule) &&
TranslateFrameScheduleFlags(schedule->flags, translatedFlags);
if (valid)
{
FrameScheduleUpdate update = {};
update.clientID = schedule->clientID;
update.generation = schedule->generation;
update.flags = translatedFlags;
update.period = schedule->period;
update.targetSlack = schedule->targetSlack;
update.phaseError = schedule->phaseError;
update.feedbackFrameSerial = schedule->feedbackFrameSerial;
update.feedbackScheduleEpoch = schedule->feedbackScheduleEpoch;
update.feedbackDeadlineSerial = schedule->feedbackDeadlineSerial;
update.lease = schedule->lease;
valid = m_frames.UpdateSchedule(sourceClientID, update, now);
}
if (!valid)
DEBUG_WARN("Ignoring invalid KVMFR frame schedule");
break;
}
}
m_control.AckData();
}
m_frames.FinalizeSubscribers(subscribers, now);
if (m_control.HasNewSubscribers())
m_control.ResendState();
}
FrameMemoryLimits CLGMPTransport::GetMemoryLimits() const
{
return m_frames.GetMemoryLimits();
}
DirectFrameBufferMemory CLGMPTransport::GetDirectMemory() const
{
return {m_ivshmem.GetMem(), m_ivshmem.GetSize()};
}

View File

@@ -0,0 +1,56 @@
/**
* 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
*/
#pragma once
#include "transport/ITransport.h"
#include "transport/lgmp/CIVSHMEM.h"
#include "transport/lgmp/CLGMPControl.h"
#include "transport/lgmp/CLGMPFrameTransport.h"
#include "transport/lgmp/CLGMPHost.h"
class CLGMPTransport final : public ITransport
{
private:
// Keep this declaration order. Destruction must release frame and control
// allocations before the LGMP host and its IVSHMEM mapping are destroyed.
CIVSHMEM m_ivshmem;
CLGMPHost m_host;
CLGMPControl m_control;
CLGMPFrameTransport m_frames;
public:
CLGMPTransport();
~CLGMPTransport() override = default;
CLGMPTransport(const CLGMPTransport&) = delete;
CLGMPTransport& operator=(const CLGMPTransport&) = delete;
OpenResult Open() override;
bool Initialize() override;
bool Setup(size_t alignment) override;
void Process(ITransportEvents& events) override;
FrameMemoryLimits GetMemoryLimits() const override;
DirectFrameBufferMemory GetDirectMemory() const override;
IFrameTransport& Frames() override { return m_frames; }
IControlTransport& Control() override { return m_control; }
};