Files
LookingGlass/idd/LGIdd/transport/lgmp/CLGMPFrameTransport.cpp
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

1316 lines
41 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
*/
#include "transport/lgmp/CLGMPFrameTransport.h"
#include "transport/lgmp/CIVSHMEM.h"
#include "transport/lgmp/CLGMPHost.h"
#include "CDebug.h"
#include <cstring>
#pragma warning(push)
#pragma warning(disable: 4200)
struct LGMPBuffer
{
volatile uint32_t wp;
uint8_t data[0];
};
#pragma warning(pop)
static_assert(TRANSPORT_FRAME_QUEUE_LENGTH == LGMP_Q_FRAME_LEN,
"The capture pipeline must match the LGMP frame queue");
static_assert(TRANSPORT_FRAME_BUFFER_COUNT == LGMP_Q_FRAME_BUFFER_LEN,
"The capture buffer pool must match the LGMP frame buffers");
static_assert(TRANSPORT_MAX_CLIENTS == LGMP_MAX_CLIENTS,
"The scheduler must support every LGMP client");
static const struct LGMPQueueConfig FRAME_QUEUE_CONFIG =
{
LGMP_Q_FRAME, // queueID
LGMP_Q_FRAME_LEN, // numMessages
1000 // subTimeout
};
static uint64_t FrameScheduleToken(
const CFrameScheduler::Schedule& schedule)
{
return static_cast<uint64_t>(schedule.epoch) << 32 |
schedule.deliveryDeadlineSerial;
}
static bool FrameScheduleMatches(
const CFrameScheduler::Schedule& a,
const CFrameScheduler::Schedule& b)
{
return a.clientID == b.clientID &&
a.generation == b.generation &&
a.epoch == b.epoch;
}
CLGMPFrameTransport::CLGMPFrameTransport(
CLGMPHost& host, CIVSHMEM& ivshmem) :
m_host(host),
m_ivshmem(ivshmem)
{
}
CLGMPFrameTransport::~CLGMPFrameTransport()
{
DeInit();
}
bool CLGMPFrameTransport::Initialize()
{
if (m_frameQueue)
{
for (PLGMPHostQueue queue : m_frameOwnerQueue)
if (!queue)
return false;
return true;
}
LGMP_STATUS status = m_host.CreateQueue(
FRAME_QUEUE_CONFIG, &m_frameQueue);
if (status != LGMP_OK)
{
DEBUG_ERROR("lgmpHostQueueCreate Failed (Frame): %s",
lgmpStatusString(status));
return false;
}
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
{
const struct LGMPQueueConfig config =
{
LGMP_Q_FRAME_OWNER + i, // queueID
LGMP_Q_FRAME_LEN, // numMessages
1000 // subTimeout
};
status = m_host.CreateQueue(config, &m_frameOwnerQueue[i]);
if (status != LGMP_OK)
{
DEBUG_ERROR("lgmpHostQueueCreate Failed (Frame Owner %u): %s",
i, lgmpStatusString(status));
return false;
}
}
return true;
}
void CLGMPFrameTransport::SealMemoryLayout()
{
m_frameMemoryOffset =
m_ivshmem.GetSize() - m_host.Available();
}
bool CLGMPFrameTransport::Setup(size_t alignSize)
{
// This may get called multiple times as frame buffers cannot be allocated
// until the GPU-specific alignment is known.
if (m_maxFrameSize)
return true;
m_alignSize = alignSize;
if (!m_alignSize || (m_alignSize & (m_alignSize - 1)) ||
m_alignSize < sizeof(KVMFRFrame) + sizeof(LGMPBuffer))
{
DEBUG_ERROR("Invalid frame buffer alignment: %llu",
(unsigned long long)m_alignSize);
return false;
}
const size_t available = m_host.Available();
const size_t alignmentMask = m_alignSize - 1;
const size_t alignedFrameMemoryOffset =
(m_frameMemoryOffset + alignmentMask) & ~alignmentMask;
const size_t alignmentPadding =
alignedFrameMemoryOffset - m_frameMemoryOffset;
if (available <= alignmentPadding)
{
DEBUG_ERROR("Insufficient shared memory for frame buffers");
return false;
}
size_t frameAllocationSize =
(available - alignmentPadding) / LGMP_Q_FRAME_BUFFER_LEN;
frameAllocationSize &= ~alignmentMask;
if (frameAllocationSize <= m_alignSize ||
frameAllocationSize > UINT32_MAX)
{
DEBUG_ERROR("Invalid frame allocation size: %llu",
(unsigned long long)frameAllocationSize);
return false;
}
// The KVMFR frame header and frame-buffer write position occupy the first
// alignment unit. Only the bytes after it are usable for pixel data.
const size_t maxFrameSize = frameAllocationSize - m_alignSize;
DEBUG_INFO("Max Frame Data Size: %u MiB",
(unsigned int)(maxFrameSize / 1048576));
LGMP_STATUS status;
for (int i = 0; i < LGMP_Q_FRAME_BUFFER_LEN; ++i)
{
status = m_host.AllocateAligned(
(uint32_t)frameAllocationSize, (uint32_t)m_alignSize,
&m_frameMemory[i]);
if (status != LGMP_OK)
{
DEBUG_ERROR("lgmpHostMemAllocAligned Failed (Frame): %s",
lgmpStatusString(status));
return false;
}
m_frame[i] =
static_cast<KVMFRFrame *>(lgmpHostMemPtr(m_frameMemory[i]));
/**
* Put the framebuffer on the border of the next page, this is to allow
* for aligned DMA transfers by the receiver.
*/
const size_t alignOffset = alignSize - sizeof(LGMPBuffer);
m_frame[i]->offset = (uint32_t)alignOffset;
m_frameBuffer[i] = reinterpret_cast<LGMPBuffer *>(
reinterpret_cast<uint8_t *>(m_frame[i]) + alignOffset);
m_frameInFlight[i].store(false, std::memory_order_release);
m_frameCompleted[i] = false;
}
m_maxFrameSize = maxFrameSize;
m_submittedFrameIndex.store(-1, std::memory_order_release);
m_readyFrameIndex.store(-1, std::memory_order_release);
m_deferredOwnerFrameIndex = -1;
m_framePublishSequence = 0;
memset(m_frameLastPublishSequence, 0,
sizeof(m_frameLastPublishSequence));
for (FrameDelivery& delivery : m_frameDelivery)
delivery = {};
for (OwnerDelivery& delivery : m_ownerDelivery)
delivery = {};
return true;
}
void CLGMPFrameTransport::DeInit()
{
m_frameScheduler.Reset();
AcquireSRWLockExclusive(&m_framePublishLock);
m_submittedFrameIndex.store(-1, std::memory_order_release);
m_readyFrameIndex.store(-1, std::memory_order_release);
m_deferredOwnerFrameIndex = -1;
m_framePublishSequence = 0;
memset(m_frameLastPublishSequence, 0,
sizeof(m_frameLastPublishSequence));
memset(m_frameCompleted, 0, sizeof(m_frameCompleted));
for (FrameDelivery& delivery : m_frameDelivery)
delivery = {};
for (OwnerDelivery& delivery : m_ownerDelivery)
delivery = {};
ReleaseSRWLockExclusive(&m_framePublishLock);
for (int i = 0; i < LGMP_Q_FRAME_BUFFER_LEN; ++i)
{
m_frameInFlight[i].store(false, std::memory_order_release);
lgmpHostMemFree(&m_frameMemory[i]);
m_frame[i] = nullptr;
m_frameBuffer[i] = nullptr;
}
m_frameQueue = nullptr;
memset(m_frameOwnerQueue, 0, sizeof(m_frameOwnerQueue));
}
FrameMemoryLimits CLGMPFrameTransport::GetMemoryLimits() const
{
FrameMemoryLimits limits;
limits.capacity = m_ivshmem.GetSize();
limits.frameMemoryOffset = m_frameMemoryOffset;
limits.alignment = m_alignSize;
limits.maxFrameSize = m_maxFrameSize;
limits.bufferCount = LGMP_Q_FRAME_BUFFER_LEN;
return limits;
}
CLGMPFrameTransport::SubscriberSnapshot
CLGMPFrameTransport::SnapshotSubscribers() const
{
SubscriberSnapshot snapshot;
snapshot.status = lgmpHostGetClientIDs(
m_frameQueue, snapshot.clientIDs, &snapshot.clientCount);
if (snapshot.status == LGMP_OK)
{
memcpy(snapshot.ownerClientIDs, snapshot.clientIDs,
snapshot.clientCount * sizeof(*snapshot.ownerClientIDs));
snapshot.ownerClientCount = snapshot.clientCount;
}
for (unsigned queueIndex = 0;
snapshot.status == LGMP_OK && queueIndex < LGMP_Q_FRAME_LEN;
++queueIndex)
{
uint32_t queueClientIDs[LGMP_MAX_CLIENTS] = {};
unsigned queueClientCount = 0;
snapshot.status = lgmpHostGetClientIDs(
m_frameOwnerQueue[queueIndex], queueClientIDs, &queueClientCount);
unsigned commonCount = 0;
for (unsigned i = 0;
snapshot.status == LGMP_OK && i < snapshot.ownerClientCount;
++i)
for (unsigned candidate = 0;
candidate < queueClientCount; ++candidate)
if (snapshot.ownerClientIDs[i] == queueClientIDs[candidate])
{
snapshot.ownerClientIDs[commonCount++] =
snapshot.ownerClientIDs[i];
break;
}
snapshot.ownerClientCount = commonCount;
}
return snapshot;
}
void CLGMPFrameTransport::FinalizeSubscribers(
const SubscriberSnapshot& snapshot, uint64_t now)
{
if (snapshot.status == LGMP_OK)
m_frameScheduler.UpdateSubscribers(
snapshot.clientIDs, snapshot.clientCount,
snapshot.ownerClientIDs, snapshot.ownerClientCount, now);
else
DEBUG_WARN("Failed to query LGMP frame subscribers: %s",
lgmpStatusString(snapshot.status));
m_frameScheduler.LogStatistics(now);
if (lgmpHostQueueNewSubs(m_frameQueue))
m_frameScheduler.NotifyPublisher();
bool ownerSubscribed = false;
for (unsigned queueIndex = 0;
queueIndex < LGMP_Q_FRAME_LEN; ++queueIndex)
ownerSubscribed |=
lgmpHostQueueNewSubs(m_frameOwnerQueue[queueIndex]) != 0;
if (ownerSubscribed)
m_frameScheduler.RequestRepublish();
ProcessFrameDeliveries();
}
bool CLGMPFrameTransport::UpdateSchedule(uint32_t sourceClientID,
const FrameScheduleUpdate& schedule, uint64_t now)
{
return m_frameScheduler.UpdateSchedule(sourceClientID, schedule, now);
}
CLGMPFrameTransport::SharedFramePostResult
CLGMPFrameTransport::PostSharedFrame(unsigned frameIndex,
uint32_t excludeClientID, uint64_t now)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN ||
lgmpHostQueuePending(m_frameQueue) != 0)
return SHARED_FRAME_FAILED;
uint32_t clientIDs[LGMP_MAX_CLIENTS] = {};
unsigned clientCount = 0;
LGMP_STATUS status =
lgmpHostGetClientIDs(m_frameQueue, clientIDs, &clientCount);
if (status != LGMP_OK)
{
DEBUG_ERROR("Failed to query shared frame subscribers: %s",
lgmpStatusString(status));
return SHARED_FRAME_FAILED;
}
uint32_t recipients[LGMP_MAX_CLIENTS] = {};
const uint32_t frameSerial = m_frame[frameIndex]->frameSerial;
const unsigned recipientCount =
m_frameScheduler.GetSecondaryRecipients(
clientIDs, clientCount, frameSerial, now, recipients);
unsigned targetCount = 0;
for (unsigned i = 0; i < recipientCount; ++i)
{
bool excluded = recipients[i] == excludeClientID;
for (const OwnerDelivery& delivery : m_ownerDelivery)
if (delivery.active && delivery.clientID == recipients[i])
{
excluded = true;
break;
}
if (!excluded)
recipients[targetCount++] = recipients[i];
}
if (!targetCount)
{
m_frameDelivery[frameIndex].sharedOwnerToken = 0;
m_frameDelivery[frameIndex].sharedOwnerClientID = 0;
m_frameDelivery[frameIndex].sharedOwnerPending = false;
m_frameDelivery[frameIndex].sharedPending = false;
return SHARED_FRAME_IDLE;
}
unsigned postedCount = 0;
status = lgmpHostQueuePostForClients(
m_frameQueue, 0, m_frameMemory[frameIndex],
recipients, targetCount, &postedCount);
if (status != LGMP_OK)
{
if (status != LGMP_ERR_QUEUE_FULL)
DEBUG_ERROR("Failed to publish shared frame: %s",
lgmpStatusString(status));
return SHARED_FRAME_FAILED;
}
if (!postedCount)
{
m_frameDelivery[frameIndex].sharedOwnerToken = 0;
m_frameDelivery[frameIndex].sharedOwnerClientID = 0;
m_frameDelivery[frameIndex].sharedOwnerPending = false;
m_frameDelivery[frameIndex].sharedPending = false;
return SHARED_FRAME_IDLE;
}
m_frameDelivery[frameIndex].sharedOwnerToken = 0;
m_frameDelivery[frameIndex].sharedOwnerClientID = 0;
m_frameDelivery[frameIndex].sharedOwnerPending = false;
m_frameDelivery[frameIndex].sharedPending = true;
// LGMP returns only the number of matching subscribers. Account the full
// snapshot: disappeared client IDs are harmless, while every surviving
// target received this post.
m_frameScheduler.FrameDelivered(
recipients, targetCount, frameSerial, now);
return SHARED_FRAME_POSTED;
}
bool CLGMPFrameTransport::PostSharedOwnerFrame(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN || !schedule.clientID ||
m_frameDelivery[frameIndex].sharedOwnerPending ||
lgmpHostQueuePending(m_frameQueue) >= LGMP_Q_FRAME_LEN)
return false;
unsigned recipientCount = 0;
const LGMP_STATUS status = lgmpHostQueuePostForClients(
m_frameQueue, FrameScheduleToken(schedule), m_frameMemory[frameIndex],
&schedule.clientID, 1, &recipientCount);
if (status != LGMP_OK || !recipientCount)
{
if (status != LGMP_OK && status != LGMP_ERR_QUEUE_FULL)
DEBUG_ERROR("Failed to publish shared owner frame: %s",
lgmpStatusString(status));
return false;
}
FrameDelivery& delivery = m_frameDelivery[frameIndex];
delivery.sharedOwnerToken = FrameScheduleToken(schedule);
delivery.sharedOwnerClientID = schedule.clientID;
delivery.sharedOwnerPending = true;
delivery.sharedPending = true;
return true;
}
void CLGMPFrameTransport::ProcessFrameDeliveries()
{
if (!m_frameQueue)
return;
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
if (!m_frameOwnerQueue[i])
return;
AcquireSRWLockExclusive(&m_framePublishLock);
bool released = false;
for (unsigned i = 0; i < LGMP_Q_FRAME_BUFFER_LEN; ++i)
{
if (m_frameDelivery[i].sharedOwnerPending &&
!lgmpHostQueueMessagePending(
m_frameQueue, m_frameMemory[i],
m_frameDelivery[i].sharedOwnerToken))
{
m_frameDelivery[i].sharedOwnerPending = false;
released = true;
}
if (m_frameDelivery[i].sharedPending &&
!lgmpHostQueuePayloadPending(m_frameQueue, m_frameMemory[i]))
{
m_frameDelivery[i].sharedPending = false;
released = true;
}
}
for (unsigned queueIndex = 0;
queueIndex < LGMP_Q_FRAME_LEN; ++queueIndex)
{
OwnerDelivery& owner = m_ownerDelivery[queueIndex];
if (!owner.active ||
lgmpHostQueuePayloadPending(
m_frameOwnerQueue[queueIndex],
m_frameMemory[owner.frameIndex]))
continue;
const unsigned frameIndex = owner.frameIndex;
m_frameDelivery[frameIndex].ownerQueueMask &=
~(1U << queueIndex);
owner = {};
released = true;
}
ReleaseSRWLockExclusive(&m_framePublishLock);
if (released)
m_frameScheduler.NotifyPublisher();
}
int CLGMPFrameTransport::FindAvailableOwnerQueue(
unsigned preferredIndex) const
{
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
{
const unsigned queueIndex =
(preferredIndex + i) % LGMP_Q_FRAME_LEN;
if (!m_ownerDelivery[queueIndex].active &&
m_frameOwnerQueue[queueIndex] &&
lgmpHostQueuePending(m_frameOwnerQueue[queueIndex]) == 0)
return static_cast<int>(queueIndex);
}
return -1;
}
unsigned CLGMPFrameTransport::CountOwnerDeliveries(
uint32_t clientID) const
{
unsigned count = 0;
for (const OwnerDelivery& delivery : m_ownerDelivery)
if (delivery.active && delivery.clientID == clientID)
++count;
for (const FrameDelivery& delivery : m_frameDelivery)
if (delivery.sharedOwnerPending &&
delivery.sharedOwnerClientID == clientID)
++count;
return count;
}
bool CLGMPFrameTransport::HasMatchingOwnerDelivery(
uint32_t clientID, unsigned frameIndex, uint64_t token) const
{
for (const OwnerDelivery& delivery : m_ownerDelivery)
if (delivery.active &&
delivery.clientID == clientID &&
delivery.frameIndex == frameIndex &&
delivery.token == token)
return true;
for (unsigned i = 0; i < LGMP_Q_FRAME_BUFFER_LEN; ++i)
{
const FrameDelivery& delivery = m_frameDelivery[i];
if (i == frameIndex &&
delivery.sharedOwnerPending &&
delivery.sharedOwnerClientID == clientID &&
delivery.sharedOwnerToken == token)
return true;
}
return false;
}
bool CLGMPFrameTransport::FrameBufferReferenced(
unsigned frameIndex) const
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return true;
const FrameDelivery& delivery = m_frameDelivery[frameIndex];
if (delivery.ownerQueueMask || delivery.sharedPending ||
delivery.sharedOwnerPending ||
lgmpHostQueuePayloadPending(
m_frameQueue, m_frameMemory[frameIndex]))
return true;
for (unsigned queueIndex = 0;
queueIndex < LGMP_Q_FRAME_LEN; ++queueIndex)
if (lgmpHostQueuePayloadPending(
m_frameOwnerQueue[queueIndex], m_frameMemory[frameIndex]))
return true;
return false;
}
int CLGMPFrameTransport::FindAvailableFrameBuffer(
bool allowReady) const
{
const LONG readyFrameIndex =
m_readyFrameIndex.load(std::memory_order_acquire);
int available = -1;
uint64_t newestPublish = 0;
for (unsigned frameIndex = 0;
frameIndex < LGMP_Q_FRAME_BUFFER_LEN; ++frameIndex)
{
if (static_cast<LONG>(frameIndex) == readyFrameIndex ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire) ||
FrameBufferReferenced(frameIndex))
continue;
if (available < 0 ||
m_frameLastPublishSequence[frameIndex] > newestPublish)
{
available = static_cast<int>(frameIndex);
newestPublish = m_frameLastPublishSequence[frameIndex];
}
}
if (available >= 0 || !allowReady || readyFrameIndex < 0 ||
m_frameInFlight[readyFrameIndex].load(std::memory_order_acquire) ||
FrameBufferReferenced(static_cast<unsigned>(readyFrameIndex)))
return available;
// A blocked owner can consume the other two buffers indefinitely. Once
// the retained frame has no queue references it is safe to replace it with
// a newer frame rather than waiting for the owner's LGMP timeout.
return static_cast<int>(readyFrameIndex);
}
int CLGMPFrameTransport::FindNewestCompletedFrame(
unsigned excludeFrameIndex) const
{
int newestFrame = -1;
uint64_t newestSequence = 0;
for (unsigned frameIndex = 0;
frameIndex < LGMP_Q_FRAME_BUFFER_LEN; ++frameIndex)
{
if (frameIndex == excludeFrameIndex || !m_frameCompleted[frameIndex] ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire))
continue;
if (newestFrame < 0 ||
m_frameLastPublishSequence[frameIndex] > newestSequence)
{
newestFrame = static_cast<int>(frameIndex);
newestSequence = m_frameLastPublishSequence[frameIndex];
}
}
return newestFrame;
}
bool CLGMPFrameTransport::FrameBufferAvailable(
const CFrameScheduler::Schedule& schedule,
bool allowReadyReplacement)
{
if (!m_frameQueue)
return false;
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
if (!m_frameOwnerQueue[i])
return false;
AcquireSRWLockShared(&m_framePublishLock);
bool allowReady = false;
// Pipeline one frame through each independent owner lane. Count the shared
// fallback against the same limit so it cannot become a third delivery for
// the same owner. Once both are occupied, a fully unreferenced buffer can
// still retain a newer frame for secondary delivery and later republish.
if (schedule.clientID)
{
const bool ownerBlocked =
CountOwnerDeliveries(schedule.clientID) >= LGMP_Q_FRAME_LEN;
const bool ownerQueuesBlocked = FindAvailableOwnerQueue(0) < 0;
allowReady = allowReadyReplacement &&
(ownerBlocked || ownerQueuesBlocked);
}
else if (lgmpHostQueuePending(m_frameQueue) != 0)
{
ReleaseSRWLockShared(&m_framePublishLock);
return false;
}
// With no owner delivery lane available, a copy can still replace an
// unreferenced retained frame and be republished when a lane clears.
const bool available = FindAvailableFrameBuffer(allowReady) >= 0;
ReleaseSRWLockShared(&m_framePublishLock);
return available;
}
void CLGMPFrameTransport::ProcessDeliveries()
{
if (!m_host.IsInitialized())
return;
const LGMP_STATUS status = m_host.Process();
if (status != LGMP_OK && status != LGMP_ERR_CORRUPTED)
DEBUG_ERROR("lgmpHostProcess Failed: %s", lgmpStatusString(status));
if (status == LGMP_OK)
ProcessFrameDeliveries();
}
bool CLGMPFrameTransport::GetPendingDeliveryTarget(uint64_t now,
uint64_t& target)
{
if (!m_frameQueue)
return false;
AcquireSRWLockShared(&m_framePublishLock);
const LONG frameIndex =
m_readyFrameIndex.load(std::memory_order_acquire);
if (frameIndex < 0 ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire) ||
lgmpHostQueuePending(m_frameQueue) != 0)
{
ReleaseSRWLockShared(&m_framePublishLock);
return false;
}
uint32_t blockedClientIDs[LGMP_Q_FRAME_LEN] = {};
unsigned blockedCount = 0;
for (const OwnerDelivery& delivery : m_ownerDelivery)
if (delivery.active)
blockedClientIDs[blockedCount++] = delivery.clientID;
const bool result = m_frameScheduler.GetSecondaryTarget(
m_frame[frameIndex]->frameSerial, now,
blockedClientIDs, blockedCount, target);
ReleaseSRWLockShared(&m_framePublishLock);
return result;
}
bool CLGMPFrameTransport::RetryPendingDelivery(uint64_t now, bool& retry)
{
retry = false;
if (!m_frameQueue)
return false;
AcquireSRWLockExclusive(&m_framePublishLock);
const LONG frameIndex =
m_readyFrameIndex.load(std::memory_order_acquire);
if (frameIndex < 0 ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire) ||
lgmpHostQueuePending(m_frameQueue) != 0)
{
ReleaseSRWLockExclusive(&m_framePublishLock);
return false;
}
const SharedFramePostResult result = PostSharedFrame(
static_cast<unsigned>(frameIndex), 0, now);
ReleaseSRWLockExclusive(&m_framePublishLock);
retry = result == SHARED_FRAME_FAILED;
return result == SHARED_FRAME_POSTED;
}
PreparedFrameBuffer CLGMPFrameTransport::PrepareFrameBuffer(
unsigned pitch, const D12FrameFormat& srcFormat,
const D12FrameFormat& dstFormat, const RECT * dirtyRects,
unsigned nbDirtyRects, const CFrameScheduler::Schedule& schedule,
bool allowReadyReplacement)
{
PreparedFrameBuffer result = {};
const unsigned dataWidth = dstFormat.dataWidth ?
dstFormat.dataWidth : (unsigned)dstFormat.desc.Width;
const unsigned dataHeight = dstFormat.dataHeight ?
dstFormat.dataHeight : dstFormat.desc.Height;
if (dstFormat.format == FRAME_TYPE_INVALID)
{
DEBUG_ERROR("Unsupported frame format, skipping frame");
return result;
}
AcquireSRWLockExclusive(&m_framePublishLock);
const bool ownerBlocked = schedule.clientID &&
CountOwnerDeliveries(schedule.clientID) >= LGMP_Q_FRAME_LEN;
const bool allowReady = allowReadyReplacement &&
(ownerBlocked ||
(schedule.clientID && FindAvailableOwnerQueue(0) < 0));
const int availableFrameIndex =
FindAvailableFrameBuffer(allowReady);
bool expected = false;
const bool acquired = availableFrameIndex >= 0 &&
m_frameInFlight[availableFrameIndex].compare_exchange_strong(
expected, true, std::memory_order_acq_rel);
if (acquired)
{
const LONG readyFrameIndex =
m_readyFrameIndex.load(std::memory_order_acquire);
if (availableFrameIndex == readyFrameIndex)
m_readyFrameIndex.store(
FindNewestCompletedFrame(
static_cast<unsigned>(availableFrameIndex)),
std::memory_order_release);
m_frameCompleted[availableFrameIndex] = false;
m_frameDelivery[availableFrameIndex] = {};
}
const bool fullCopy = acquired &&
(!m_frameLastPublishSequence[availableFrameIndex] ||
m_framePublishSequence >
m_frameLastPublishSequence[availableFrameIndex] + 1);
ReleaseSRWLockExclusive(&m_framePublishLock);
if (!acquired)
return result;
const unsigned frameIndex = static_cast<unsigned>(availableFrameIndex);
if (m_width != dataWidth ||
m_height != dataHeight ||
m_frameWidth != dstFormat.width ||
m_frameHeight != dstFormat.height ||
m_pitch != pitch ||
m_format != dstFormat.desc.Format ||
m_frameType != dstFormat.format)
{
m_width = dataWidth;
m_height = dataHeight;
m_frameWidth = dstFormat.width;
m_frameHeight = dstFormat.height;
m_pitch = pitch;
m_format = dstFormat.desc.Format;
m_frameType = dstFormat.format;
++m_formatVer;
}
// Detect HDR metadata changes that require a format version bump
// so the client knows to re-apply the HDR image description.
//
// Use dstFormat so post-processing can propagate any metadata adjustments.
if (dstFormat.hdr)
{
const bool metadataChanged =
m_lastHDRMetadata != dstFormat.hdrMetadata ||
(dstFormat.hdrMetadata &&
(memcmp(m_lastHDRDisplayPrimary, dstFormat.displayPrimary,
sizeof(m_lastHDRDisplayPrimary)) != 0 ||
memcmp(m_lastHDRWhitePoint, dstFormat.whitePoint,
sizeof(m_lastHDRWhitePoint)) != 0 ||
m_lastHDRMaxDisplayLuminance !=
dstFormat.maxDisplayLuminance ||
m_lastHDRMinDisplayLuminance !=
dstFormat.minDisplayLuminance ||
m_lastHDRMaxContentLightLevel !=
dstFormat.maxContentLightLevel ||
m_lastHDRMaxFrameAverageLightLevel !=
dstFormat.maxFrameAverageLightLevel));
if (!m_lastHDRActive || metadataChanged ||
m_lastSDRWhiteLevel != dstFormat.sdrWhiteLevel)
++m_formatVer;
}
else if (m_lastHDRActive)
{
// HDR was turned off.
++m_formatVer;
}
m_lastHDRActive = dstFormat.hdr;
m_lastHDRMetadata = dstFormat.hdrMetadata;
memcpy(m_lastHDRDisplayPrimary, dstFormat.displayPrimary,
sizeof(m_lastHDRDisplayPrimary));
memcpy(m_lastHDRWhitePoint, dstFormat.whitePoint,
sizeof(m_lastHDRWhitePoint));
m_lastHDRMaxDisplayLuminance = dstFormat.maxDisplayLuminance;
m_lastHDRMinDisplayLuminance = dstFormat.minDisplayLuminance;
m_lastHDRMaxContentLightLevel = dstFormat.maxContentLightLevel;
m_lastHDRMaxFrameAverageLightLevel =
dstFormat.maxFrameAverageLightLevel;
m_lastSDRWhiteLevel = dstFormat.sdrWhiteLevel;
KVMFRFrame * fi = m_frame[frameIndex];
const unsigned maxRows = (unsigned)(m_maxFrameSize / pitch);
const int bpp = dstFormat.format == FRAME_TYPE_RGBA16F ? 8 : 4;
KVMFRFrameFlags flags =
(dstFormat.hdr ? FRAME_FLAG_HDR : 0) |
(dstFormat.hdrPQ ? FRAME_FLAG_HDR_PQ : 0) |
(dstFormat.hdrMetadata ? FRAME_FLAG_HDR_METADATA : 0);
if (maxRows < dataHeight)
flags |= FRAME_FLAG_TRUNCATED;
fi->formatVer = m_formatVer;
fi->frameSerial = m_frameSerial++;
fi->screenWidth = srcFormat.width;
fi->screenHeight = srcFormat.height;
fi->dataWidth = dataWidth;
fi->dataHeight = min(maxRows, dataHeight);
fi->frameWidth = dstFormat.width;
fi->frameHeight = dstFormat.height;
fi->stride = pitch / bpp;
fi->pitch = pitch;
// fi->offset is initialized at startup.
fi->flags = flags;
fi->sdrWhiteLevel = dstFormat.sdrWhiteLevel;
fi->captureTime = 0;
fi->postProcessTime = 0;
fi->copyTime = 0;
fi->readyTime = 0;
fi->holdTime = 0;
fi->readyLeadTime = 0;
fi->timingSerial = 0;
fi->timingFlags = 0;
fi->scheduleGeneration = 0;
fi->scheduleEpoch = 0;
fi->scheduleDeadlineSerial = 0;
InterlockedExchange((volatile LONG *)&fi->timingValid, 0);
fi->rotation = FRAME_ROT_0;
fi->type = dstFormat.format;
if (flags & FRAME_FLAG_HDR_METADATA)
{
memcpy(fi->hdrDisplayPrimary, dstFormat.displayPrimary,
sizeof(fi->hdrDisplayPrimary));
memcpy(fi->hdrWhitePoint, dstFormat.whitePoint,
sizeof(fi->hdrWhitePoint));
fi->hdrMaxDisplayLuminance = dstFormat.maxDisplayLuminance;
fi->hdrMinDisplayLuminance = dstFormat.minDisplayLuminance;
fi->hdrMaxContentLightLevel = dstFormat.maxContentLightLevel;
fi->hdrMaxFrameAverageLightLevel =
dstFormat.maxFrameAverageLightLevel;
}
else
{
memset(fi->hdrDisplayPrimary, 0, sizeof(fi->hdrDisplayPrimary));
memset(fi->hdrWhitePoint, 0, sizeof(fi->hdrWhitePoint));
fi->hdrMaxDisplayLuminance = 0;
fi->hdrMinDisplayLuminance = 0;
fi->hdrMaxContentLightLevel = 0;
fi->hdrMaxFrameAverageLightLevel = 0;
}
fi->damageRectsCount = 0;
if (nbDirtyRects <= ARRAYSIZE(fi->damageRects))
{
fi->damageRectsCount = nbDirtyRects;
for (unsigned i = 0; i < nbDirtyRects; ++i)
{
fi->damageRects[i].x = dirtyRects[i].left;
fi->damageRects[i].y = dirtyRects[i].top;
fi->damageRects[i].width =
dirtyRects[i].right - dirtyRects[i].left;
fi->damageRects[i].height =
dirtyRects[i].bottom - dirtyRects[i].top;
}
}
LGMPBuffer * fb = m_frameBuffer[frameIndex];
fb->wp = 0;
result.frameIndex = frameIndex;
result.mem = fb->data;
result.heapOffset = reinterpret_cast<uintptr_t>(fb->data) -
reinterpret_cast<uintptr_t>(m_ivshmem.GetMem());
result.fullCopy = fullCopy;
return result;
}
bool CLGMPFrameTransport::PublishFrameBuffer(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule, bool& deliveredToOwner)
{
deliveredToOwner = false;
if (!m_frameQueue || frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return false;
const uint64_t now = CFrameScheduler::Nanotime();
AcquireSRWLockExclusive(&m_framePublishLock);
CFrameScheduler::Schedule currentSchedule = {};
const bool scheduling =
m_frameScheduler.GetSchedule(currentSchedule);
if (scheduling != (schedule.clientID != 0) ||
(scheduling && !FrameScheduleMatches(schedule, currentSchedule)))
{
ReleaseSRWLockExclusive(&m_framePublishLock);
return false;
}
KVMFRFrame * frame = m_frame[frameIndex];
frame->timingFlags = 0;
frame->scheduleGeneration = schedule.generation;
frame->scheduleEpoch = schedule.epoch;
frame->scheduleDeadlineSerial = schedule.deliveryDeadlineSerial;
LGMP_STATUS status = LGMP_OK;
bool published = false;
if (schedule.clientID)
{
if (CountOwnerDeliveries(schedule.clientID) >= LGMP_Q_FRAME_LEN)
{
// Both owner lanes still reference older frames. Retain the newest
// frame locally and serve any unblocked secondary clients without
// violating the lifetime of those outstanding LGMP payloads.
PostSharedFrame(frameIndex, schedule.clientID, now);
published = true;
}
else
{
const int ownerQueueIndex = FindAvailableOwnerQueue(frameIndex);
if (ownerQueueIndex < 0)
{
published = PostSharedOwnerFrame(frameIndex, schedule);
deliveredToOwner = published;
if (!published)
{
PostSharedFrame(frameIndex, schedule.clientID, now);
published = true;
}
}
else
{
unsigned recipientCount = 0;
status = lgmpHostQueuePostForClients(
m_frameOwnerQueue[ownerQueueIndex], FrameScheduleToken(schedule),
m_frameMemory[frameIndex],
&schedule.clientID, 1, &recipientCount);
if (status == LGMP_OK && recipientCount)
{
const unsigned queueIndex =
static_cast<unsigned>(ownerQueueIndex);
OwnerDelivery& owner = m_ownerDelivery[queueIndex];
owner.token = FrameScheduleToken(schedule);
owner.clientID = schedule.clientID;
owner.frameIndex = frameIndex;
owner.active = true;
FrameDelivery& delivery = m_frameDelivery[frameIndex];
delivery.ownerQueueMask |= 1U << queueIndex;
deliveredToOwner = true;
published = true;
PostSharedFrame(frameIndex, schedule.clientID, now);
}
}
}
}
else
{
published = PostSharedFrame(
frameIndex, 0, now) != SHARED_FRAME_FAILED;
deliveredToOwner = published;
}
if (published)
{
m_frameLastPublishSequence[frameIndex] = ++m_framePublishSequence;
m_deferredOwnerFrameIndex = schedule.clientID && !deliveredToOwner ?
static_cast<LONG>(frameIndex) : -1;
m_submittedFrameIndex.store(
static_cast<LONG>(frameIndex), std::memory_order_release);
}
ReleaseSRWLockExclusive(&m_framePublishLock);
if (!published)
{
if (status != LGMP_OK && status != LGMP_ERR_QUEUE_FULL)
DEBUG_ERROR("Failed to publish frame: %s",
lgmpStatusString(status));
return false;
}
return true;
}
bool CLGMPFrameTransport::RepublishFrameBuffer(
const CFrameScheduler::Schedule& schedule)
{
if (!schedule.clientID)
return false;
AcquireSRWLockExclusive(&m_framePublishLock);
CFrameScheduler::Schedule currentSchedule = {};
if (!m_frameScheduler.GetSchedule(currentSchedule) ||
!FrameScheduleMatches(schedule, currentSchedule))
{
ReleaseSRWLockExclusive(&m_framePublishLock);
return false;
}
LONG frameIndex = m_deferredOwnerFrameIndex;
if (frameIndex >= 0 &&
!m_frameCompleted[frameIndex] &&
!m_frameInFlight[frameIndex].load(std::memory_order_acquire))
{
m_deferredOwnerFrameIndex = -1;
frameIndex = -1;
}
if (frameIndex < 0)
frameIndex = m_readyFrameIndex.load(std::memory_order_acquire);
if (frameIndex < 0 ||
m_frameInFlight[frameIndex].load(std::memory_order_acquire))
{
ReleaseSRWLockExclusive(&m_framePublishLock);
return false;
}
CFrameScheduler::Schedule deliverySchedule = schedule;
deliverySchedule.deliveryDeadlineSerial = 0;
deliverySchedule.phaseEligible = false;
const uint64_t scheduleToken = FrameScheduleToken(deliverySchedule);
const uint32_t frameSerial = m_frame[frameIndex]->frameSerial;
if (HasMatchingOwnerDelivery(schedule.clientID,
static_cast<unsigned>(frameIndex), scheduleToken))
{
if (m_deferredOwnerFrameIndex == frameIndex)
m_deferredOwnerFrameIndex = -1;
ReleaseSRWLockExclusive(&m_framePublishLock);
m_frameScheduler.FrameRepublished(schedule, frameSerial);
return true;
}
if (CountOwnerDeliveries(schedule.clientID) >= LGMP_Q_FRAME_LEN)
{
ReleaseSRWLockExclusive(&m_framePublishLock);
return false;
}
const int ownerQueueIndex =
FindAvailableOwnerQueue(static_cast<unsigned>(frameIndex));
if (ownerQueueIndex < 0)
{
const bool published = PostSharedOwnerFrame(
static_cast<unsigned>(frameIndex), deliverySchedule);
if (published && m_deferredOwnerFrameIndex == frameIndex)
m_deferredOwnerFrameIndex = -1;
ReleaseSRWLockExclusive(&m_framePublishLock);
if (published)
m_frameScheduler.FrameRepublished(schedule, frameSerial);
return published;
}
unsigned recipientCount = 0;
const LGMP_STATUS status = lgmpHostQueuePostForClients(
m_frameOwnerQueue[ownerQueueIndex], scheduleToken,
m_frameMemory[frameIndex],
&schedule.clientID, 1, &recipientCount);
if (status == LGMP_OK && recipientCount)
{
const unsigned queueIndex =
static_cast<unsigned>(ownerQueueIndex);
OwnerDelivery& owner = m_ownerDelivery[queueIndex];
owner.token = scheduleToken;
owner.clientID = schedule.clientID;
owner.frameIndex = static_cast<unsigned>(frameIndex);
owner.active = true;
FrameDelivery& delivery = m_frameDelivery[frameIndex];
delivery.ownerQueueMask |= 1U << queueIndex;
if (m_deferredOwnerFrameIndex == frameIndex)
m_deferredOwnerFrameIndex = -1;
}
ReleaseSRWLockExclusive(&m_framePublishLock);
if (status != LGMP_OK || !recipientCount)
{
if (status != LGMP_OK && status != LGMP_ERR_QUEUE_FULL)
DEBUG_ERROR("Failed to republish frame: %s",
lgmpStatusString(status));
return false;
}
m_frameScheduler.FrameRepublished(schedule, frameSerial);
return true;
}
void CLGMPFrameTransport::CommitFrameBuffer(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule, bool periodic,
bool deliveredToOwner)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return;
const uint64_t now = CFrameScheduler::Nanotime();
if (deliveredToOwner)
m_frameScheduler.FramePublished(
schedule, m_frame[frameIndex]->frameSerial, now, periodic);
else
m_frameScheduler.FrameRetained(schedule, now, periodic);
}
bool CLGMPFrameTransport::TryFrameSubmitted(unsigned frameIndex,
const CFrameScheduler::Schedule& schedule)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return false;
return m_frameScheduler.TryFrameSubmitted(
schedule, m_frame[frameIndex]->frameSerial);
}
void CLGMPFrameTransport::ObserveFrame(uint64_t now)
{
m_frameScheduler.ObserveFrame(now);
}
void CLGMPFrameTransport::ForceFrame()
{
m_frameScheduler.ForceFrame();
}
bool CLGMPFrameTransport::GetPublishTarget(uint64_t now,
uint64_t& target, CFrameScheduler::Schedule& schedule, bool& periodic,
bool& republish)
{
return m_frameScheduler.GetPublishTarget(
now, target, schedule, periodic, republish);
}
void CLGMPFrameTransport::FrameMissed(
const CFrameScheduler::Schedule& schedule, uint64_t now, bool periodic)
{
m_frameScheduler.FrameMissed(schedule, now, periodic);
}
void CLGMPFrameTransport::FrameSuperseded()
{
m_frameScheduler.FrameSuperseded();
}
void CLGMPFrameTransport::TryRecordFrameTiming(uint64_t duration)
{
m_frameScheduler.TryRecordFrameTiming(duration);
}
void CLGMPFrameTransport::AbortFrameBuffer(unsigned frameIndex)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return;
AcquireSRWLockExclusive(&m_framePublishLock);
m_frameBuffer[frameIndex]->wp = 0;
InterlockedExchange(
(volatile LONG *)&m_frame[frameIndex]->timingValid, 0);
m_frameCompleted[frameIndex] = false;
if (m_deferredOwnerFrameIndex == static_cast<LONG>(frameIndex))
m_deferredOwnerFrameIndex = -1;
m_frameInFlight[frameIndex].store(false, std::memory_order_release);
ReleaseSRWLockExclusive(&m_framePublishLock);
}
void CLGMPFrameTransport::FailFrameBuffer(unsigned frameIndex)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return;
InterlockedExchange(
(volatile LONG *)&m_frame[frameIndex]->timingValid, 0);
FinalizeFrameBuffer(frameIndex);
CompleteFrameBuffer(frameIndex, false);
}
void CLGMPFrameTransport::CompleteFrameBuffer(
unsigned frameIndex, bool succeeded)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return;
AcquireSRWLockExclusive(&m_framePublishLock);
m_frameCompleted[frameIndex] = succeeded;
if (!succeeded &&
m_deferredOwnerFrameIndex == static_cast<LONG>(frameIndex))
m_deferredOwnerFrameIndex = -1;
if (succeeded)
{
// Completion callbacks may run out of order. Never replace a newer ready
// frame with an older submission.
const uint64_t sequence = m_frameLastPublishSequence[frameIndex];
const LONG readyFrameIndex =
m_readyFrameIndex.load(std::memory_order_acquire);
if (sequence &&
(readyFrameIndex < 0 ||
sequence > m_frameLastPublishSequence[readyFrameIndex]))
m_readyFrameIndex.store(
static_cast<LONG>(frameIndex), std::memory_order_release);
}
m_frameInFlight[frameIndex].store(false, std::memory_order_release);
ReleaseSRWLockExclusive(&m_framePublishLock);
}
void CLGMPFrameTransport::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)
{
if (frameIndex >= LGMP_Q_FRAME_BUFFER_LEN)
return;
KVMFRFrame * frame = m_frame[frameIndex];
const bool phaseValid = m_frameScheduler.TryFrameCompleted(
schedule, frame->frameSerial, completedAt);
frame->captureTime = captureTime;
frame->postProcessTime = postProcessTime;
frame->copyTime = copyTime;
frame->readyTime = readyTime;
frame->holdTime = holdTime;
frame->readyLeadTime = phaseValid && schedule.deadline >= completedAt ?
schedule.deadline - completedAt : 0;
frame->timingFlags = phaseValid ?
KVMFR_FRAME_TIMING_PHASE_VALID : 0;
frame->timingSerial = frame->frameSerial;
InterlockedExchange((volatile LONG *)&frame->timingValid, 1);
}
void CLGMPFrameTransport::WriteFrameBuffer(unsigned frameIndex, void * src,
size_t offset, size_t len, bool setWritePos) const
{
LGMPBuffer * fb = m_frameBuffer[frameIndex];
memcpy(
reinterpret_cast<void *>(
reinterpret_cast<uintptr_t>(fb->data) + offset),
reinterpret_cast<void *>(
reinterpret_cast<uintptr_t>(src) + offset),
len);
if (setWritePos)
fb->wp = (uint32_t)(offset + len);
}
void CLGMPFrameTransport::WriteFrameBufferRows(unsigned frameIndex,
void * src, size_t offset, size_t rowBytes, size_t pitch,
unsigned rows) const
{
LGMPBuffer * fb = m_frameBuffer[frameIndex];
uint8_t * dst = fb->data + offset;
uint8_t * source = static_cast<uint8_t *>(src) + offset;
for (unsigned row = 0; row < rows; ++row)
{
memcpy(dst, source, rowBytes);
dst += pitch;
source += pitch;
}
}
void CLGMPFrameTransport::FinalizeFrameBuffer(unsigned frameIndex) const
{
const KVMFRFrame * frame = m_frame[frameIndex];
LGMPBuffer * fb = m_frameBuffer[frameIndex];
fb->wp = frame->dataHeight * frame->pitch;
}