Files
LookingGlass/idd/LGIdd/transport/lgmp/CLGMPFrameTransport.h
Geoffrey McRae 6725133375 [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.
2026-08-07 17:53:00 +10:00

216 lines
7.8 KiB
C++

/**
* 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;
};