From 801a1e58f8c8933908dabb38cf088c8256bede60 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 12 Aug 2026 16:31:35 +1000 Subject: [PATCH] [idd] transport: centralize source and control state --- idd/LGIdd/LGIdd.vcxproj | 2 + idd/LGIdd/LGIdd.vcxproj.filters | 6 + idd/LGIdd/display/CDeviceContext.cpp | 11 +- idd/LGIdd/display/CDeviceContext.h | 8 +- idd/LGIdd/transport/CControlHub.cpp | 161 ++++++++++++++++++++ idd/LGIdd/transport/CControlHub.h | 65 ++++++++ idd/LGIdd/transport/CTransportManager.cpp | 66 +++++++- idd/LGIdd/transport/CTransportManager.h | 4 + idd/LGIdd/transport/ITransport.h | 22 ++- idd/LGIdd/transport/lgmp/CLGMPTransport.cpp | 10 +- 10 files changed, 339 insertions(+), 16 deletions(-) create mode 100644 idd/LGIdd/transport/CControlHub.cpp create mode 100644 idd/LGIdd/transport/CControlHub.h diff --git a/idd/LGIdd/LGIdd.vcxproj b/idd/LGIdd/LGIdd.vcxproj index 1a702282..7569d02e 100644 --- a/idd/LGIdd/LGIdd.vcxproj +++ b/idd/LGIdd/LGIdd.vcxproj @@ -60,6 +60,7 @@ + @@ -107,6 +108,7 @@ + diff --git a/idd/LGIdd/LGIdd.vcxproj.filters b/idd/LGIdd/LGIdd.vcxproj.filters index d9268ee3..ef899d4d 100644 --- a/idd/LGIdd/LGIdd.vcxproj.filters +++ b/idd/LGIdd/LGIdd.vcxproj.filters @@ -154,6 +154,9 @@ Post-processing\Effects + + Transport + Transport @@ -294,6 +297,9 @@ Post-processing\Effects + + Transport + Transport diff --git a/idd/LGIdd/display/CDeviceContext.cpp b/idd/LGIdd/display/CDeviceContext.cpp index c5a154e7..c329a64e 100644 --- a/idd/LGIdd/display/CDeviceContext.cpp +++ b/idd/LGIdd/display/CDeviceContext.cpp @@ -593,18 +593,23 @@ void CDeviceContext::TransportTimer() m_transport->Process(*this); } -void CDeviceContext::OnSetCursorPos(int32_t x, int32_t y) +void CDeviceContext::OnSetCursorPos( + const SourceKey& source, int32_t x, int32_t y) { + UNREFERENCED_PARAMETER(source); g_pipe.SetCursorPos(x, y); } -void CDeviceContext::OnSetResolution(uint32_t width, uint32_t height) +void CDeviceContext::OnSetResolution(const SourceKey& source, + uint32_t width, uint32_t height) { + UNREFERENCED_PARAMETER(source); SetResolution(width, height); } -void CDeviceContext::OnRecoveryRequest( +void CDeviceContext::OnRecoveryRequest(const SourceKey& source, uint64_t session, uint32_t serial, bool active) { + UNREFERENCED_PARAMETER(source); g_pipe.SetRecovery(this, session, serial, active); } diff --git a/idd/LGIdd/display/CDeviceContext.h b/idd/LGIdd/display/CDeviceContext.h index c3ed9677..48ea40b5 100644 --- a/idd/LGIdd/display/CDeviceContext.h +++ b/idd/LGIdd/display/CDeviceContext.h @@ -66,9 +66,11 @@ private: bool InitializeTransport(); void TransportTimer(); - void OnSetCursorPos(int32_t x, int32_t y) override; - void OnSetResolution(uint32_t width, uint32_t height) override; - void OnRecoveryRequest( + void OnSetCursorPos( + const SourceKey& source, int32_t x, int32_t y) override; + void OnSetResolution(const SourceKey& source, + uint32_t width, uint32_t height) override; + void OnRecoveryRequest(const SourceKey& source, uint64_t session, uint32_t serial, bool active) override; void SetResolution(uint32_t width, uint32_t height); diff --git a/idd/LGIdd/transport/CControlHub.cpp b/idd/LGIdd/transport/CControlHub.cpp new file mode 100644 index 00000000..179a1751 --- /dev/null +++ b/idd/LGIdd/transport/CControlHub.cpp @@ -0,0 +1,161 @@ +/** + * 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/CControlHub.h" + +#include +#include +#include + +std::vector> CControlHub::Snapshot() const +{ + CSRWSharedLock lock(m_sinkLock); + return m_sinks; +} + +bool CControlHub::Add( + BackendId backend, uint32_t epoch, IControlTransport& control) +{ + std::shared_ptr sink(new (std::nothrow) Sink); + if (!sink) + return false; + + sink->backend = backend; + sink->epoch = epoch; + sink->control = &control; + { + CSRWExclusiveLock lock(m_sinkLock); + for (const auto& current : m_sinks) + if (current->backend == backend && current->epoch == epoch) + return false; + m_sinks.push_back(sink); + } + + Replay(sink); + return true; +} + +void CControlHub::Remove(BackendId backend, uint32_t epoch) +{ + std::shared_ptr removed; + { + CSRWExclusiveLock lock(m_sinkLock); + for (auto current = m_sinks.begin(); current != m_sinks.end(); ++current) + if ((*current)->backend == backend && (*current)->epoch == epoch) + { + removed = *current; + m_sinks.erase(current); + break; + } + } + + if (removed) + { + CSRWExclusiveLock lock(removed->lock); + removed->control = nullptr; + } +} + +void CControlHub::Replay(const std::shared_ptr& sink) +{ + IDARG_OUT_QUERY_HWCURSOR cursor = {}; + std::vector cursorData; + UINT sdrWhiteLevel; + bool cursorValid; + std::shared_ptr transform; + { + CSRWSharedLock lock(m_stateLock); + cursor = m_cursor; + cursorData = m_cursorData; + sdrWhiteLevel = m_sdrWhiteLevel; + cursorValid = m_cursorValid; + transform = m_colorTransform; + } + + CSRWSharedLock lock(sink->lock); + if (!sink->control) + return; + sink->control->SetColorTransform(std::move(transform)); + if (cursorValid) + sink->control->SendCursor( + cursor, cursorData.empty() ? nullptr : cursorData.data(), + sdrWhiteLevel); +} + +void CControlHub::SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info, + const BYTE * data, UINT sdrWhiteLevel) +{ + { + CSRWExclusiveLock lock(m_stateLock); + m_cursor.IsCursorVisible = info.IsCursorVisible; + m_cursor.X = info.X; + m_cursor.Y = info.Y; + m_cursorValid = true; + m_sdrWhiteLevel = sdrWhiteLevel; + + if (info.CursorShapeInfo.CursorType != + IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED) + { + const size_t size = + (size_t)info.CursorShapeInfo.Height * info.CursorShapeInfo.Pitch; + m_cursor.IsCursorShapeUpdated = info.IsCursorShapeUpdated; + m_cursor.CursorShapeInfo = info.CursorShapeInfo; + m_cursorData.resize(size); + if (size) + memcpy(m_cursorData.data(), data, size); + m_shapeValid = true; + } + else if (!m_shapeValid) + m_cursor.CursorShapeInfo.CursorType = + IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED; + } + + const auto sinks = Snapshot(); + for (const auto& sink : sinks) + { + CSRWSharedLock lock(sink->lock); + if (sink->control) + sink->control->SendCursor(info, data, sdrWhiteLevel); + } +} + +void CControlHub::SetColorTransform( + std::shared_ptr transform) +{ + { + CSRWExclusiveLock lock(m_stateLock); + m_colorTransform = transform; + } + + const auto sinks = Snapshot(); + for (const auto& sink : sinks) + { + CSRWSharedLock lock(sink->lock); + if (sink->control) + sink->control->SetColorTransform(transform); + } +} + +std::shared_ptr +CControlHub::GetColorTransform() const +{ + CSRWSharedLock lock(m_stateLock); + return m_colorTransform; +} diff --git a/idd/LGIdd/transport/CControlHub.h b/idd/LGIdd/transport/CControlHub.h new file mode 100644 index 00000000..9086e2aa --- /dev/null +++ b/idd/LGIdd/transport/CControlHub.h @@ -0,0 +1,65 @@ +/** + * 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 "CSRWLock.h" +#include "transport/IControlTransport.h" +#include "transport/ITransport.h" + +#include +#include + +class CControlHub final : public IControlTransport +{ +private: + struct Sink + { + BackendId backend; + uint32_t epoch; + CSRWLock lock; + IControlTransport * control; + }; + + mutable CSRWLock m_sinkLock; + std::vector> m_sinks; + + mutable CSRWLock m_stateLock; + std::shared_ptr m_colorTransform; + IDARG_OUT_QUERY_HWCURSOR m_cursor = {}; + std::vector m_cursorData; + UINT m_sdrWhiteLevel = 0; + bool m_cursorValid = false; + bool m_shapeValid = false; + + std::vector> Snapshot() const; + void Replay(const std::shared_ptr& sink); + +public: + bool Add(BackendId backend, uint32_t epoch, IControlTransport& control); + void Remove(BackendId backend, uint32_t epoch); + + void SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info, + const BYTE * data, UINT sdrWhiteLevel) override; + void SetColorTransform( + std::shared_ptr transform) override; + std::shared_ptr + GetColorTransform() const override; +}; diff --git a/idd/LGIdd/transport/CTransportManager.cpp b/idd/LGIdd/transport/CTransportManager.cpp index 686e579f..c845e8a3 100644 --- a/idd/LGIdd/transport/CTransportManager.cpp +++ b/idd/LGIdd/transport/CTransportManager.cpp @@ -24,9 +24,50 @@ #include #include +#include static const uint64_t RETRY_DELAY_MS = 500; +class CSourceEvents final : public ITransportEvents +{ +private: + BackendId m_backend; + uint32_t m_epoch; + ITransportEvents& m_events; + + SourceKey Stamp(const SourceKey& source) const + { + SourceKey stamped = source; + stamped.backend = m_backend; + stamped.epoch = m_epoch; + return stamped; + } + +public: + CSourceEvents( + BackendId backend, uint32_t epoch, ITransportEvents& events) : + m_backend(backend), m_epoch(epoch), m_events(events) {} + + void OnSetCursorPos( + const SourceKey& source, int32_t x, int32_t y) override + { + m_events.OnSetCursorPos(Stamp(source), x, y); + } + + void OnSetResolution(const SourceKey& source, + uint32_t width, uint32_t height) override + { + m_events.OnSetResolution(Stamp(source), width, height); + } + + void OnRecoveryRequest(const SourceKey& source, + uint64_t session, uint32_t serial, bool active) override + { + m_events.OnRecoveryRequest( + Stamp(source), session, serial, active); + } +}; + CTransportManager::~CTransportManager() { Stop(); @@ -97,6 +138,13 @@ bool CTransportManager::InitializeEntry(Entry& entry) return false; } + if (!m_control.Add(entry.id, entry.epoch, entry.transport->Control())) + { + entry.state = State::FAILED; + return false; + } + + entry.controlAdded = true; entry.state = State::INITIALIZED; return true; } @@ -187,6 +235,7 @@ void CTransportManager::RetryEntry(Entry& entry, uint64_t now) if (entry.primary && m_exposed) return; + RemoveServices(entry); if (entry.transport) entry.transport->Stop(); entry.transport.reset(); @@ -220,10 +269,12 @@ void CTransportManager::HandleProcessResult( if (result == ProcessResult::RETRY || !entry.required) { + RemoveServices(entry); ScheduleRetry(entry); return; } + RemoveServices(entry); entry.state = State::FAILED; } @@ -238,7 +289,8 @@ ITransport::ProcessResult CTransportManager::Process( if (entry.state != State::INITIALIZED && entry.state != State::READY) continue; - const ProcessResult result = entry.transport->Process(events); + CSourceEvents sourceEvents(entry.id, entry.epoch, events); + const ProcessResult result = entry.transport->Process(sourceEvents); HandleProcessResult(entry, result); } return ProcessResult::OK; @@ -250,12 +302,22 @@ void CTransportManager::Stop() ++current) { Entry& entry = **current; + RemoveServices(entry); if (entry.transport && entry.state != State::STOPPED) entry.transport->Stop(); entry.state = State::STOPPED; } } +void CTransportManager::RemoveServices(Entry& entry) +{ + if (entry.controlAdded) + { + m_control.Remove(entry.id, entry.epoch); + entry.controlAdded = false; + } +} + void CTransportManager::SyncRecovery() { for (const auto& current : m_entries) @@ -299,7 +361,7 @@ IFrameTransport& CTransportManager::Frames() IControlTransport& CTransportManager::Control() { - return Primary().Control(); + return m_control; } IInputTransport * CTransportManager::Input() diff --git a/idd/LGIdd/transport/CTransportManager.h b/idd/LGIdd/transport/CTransportManager.h index 8710f20a..3ce3395e 100644 --- a/idd/LGIdd/transport/CTransportManager.h +++ b/idd/LGIdd/transport/CTransportManager.h @@ -20,6 +20,7 @@ #pragma once +#include "transport/CControlHub.h" #include "transport/ITransport.h" #include @@ -53,9 +54,11 @@ private: State state = State::CLOSED; uint32_t epoch = 1; uint64_t retryAt = 0; + bool controlAdded = false; }; std::vector> m_entries; + CControlHub m_control; Entry * m_primary = nullptr; bool m_initialized = false; bool m_setup = false; @@ -68,6 +71,7 @@ private: void RetryEntry(Entry& entry, uint64_t now); void HandleProcessResult(Entry& entry, ProcessResult result); void ScheduleRetry(Entry& entry); + void RemoveServices(Entry& entry); ITransport& Primary(); public: diff --git a/idd/LGIdd/transport/ITransport.h b/idd/LGIdd/transport/ITransport.h index 4a3bcf3f..70b2485c 100644 --- a/idd/LGIdd/transport/ITransport.h +++ b/idd/LGIdd/transport/ITransport.h @@ -30,22 +30,32 @@ class IControlTransport; class IFrameTransport; class IInputTransport; +using BackendId = uint32_t; + +struct SourceKey +{ + BackendId backend = 0; + uint32_t epoch = 0; + uint32_t client = 0; + uint32_t generation = 0; +}; + class ITransportEvents { public: virtual ~ITransportEvents() = default; - virtual void OnSetCursorPos(int32_t x, int32_t y) = 0; - virtual void OnSetResolution(uint32_t width, uint32_t height) = 0; - virtual void OnRecoveryRequest( + virtual void OnSetCursorPos( + const SourceKey& source, int32_t x, int32_t y) = 0; + virtual void OnSetResolution( + const SourceKey& source, uint32_t width, uint32_t height) = 0; + virtual void OnRecoveryRequest(const SourceKey& source, uint64_t session, uint32_t serial, bool active) = 0; }; class ITransport { public: - using BackendId = uint32_t; - enum class OpenResult { SUCCESS, @@ -72,6 +82,8 @@ public: virtual OpenResult Open() = 0; virtual bool Initialize() = 0; virtual bool Setup(size_t alignment) = 0; + // Process performs a bounded, non-blocking drain. The events reference is + // valid only for the duration of this call and must not be retained. virtual ProcessResult Process(ITransportEvents& events) = 0; virtual void Stop() = 0; virtual void SyncRecovery() {} diff --git a/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp b/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp index 965c1917..96823d3d 100644 --- a/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp +++ b/idd/LGIdd/transport/lgmp/CLGMPTransport.cpp @@ -97,7 +97,7 @@ ITransport::ProcessResult CLGMPTransport::Process(ITransportEvents& events) { const CRecovery::Request recovery = m_recovery.Process(); if (recovery.valid) - events.OnRecoveryRequest( + events.OnRecoveryRequest(SourceKey(), recovery.session, recovery.serial, recovery.active); // Before the swap chain establishes the frame-buffer alignment, service @@ -140,17 +140,21 @@ ITransport::ProcessResult CLGMPTransport::Process(ITransportEvents& events) { case KVMFR_MESSAGE_SETCURSORPOS: { + SourceKey source; + source.client = sourceClientID; KVMFRSetCursorPos * position = reinterpret_cast(msg); - events.OnSetCursorPos(position->x, position->y); + events.OnSetCursorPos(source, position->x, position->y); break; } case KVMFR_MESSAGE_WINDOWSIZE: { + SourceKey source; + source.client = sourceClientID; KVMFRWindowSize * window = reinterpret_cast(msg); - events.OnSetResolution(window->w, window->h); + events.OnSetResolution(source, window->w, window->h); break; }