[idd] project: organize source layout

Move cadence and pipeline declarations under capture, and move local
named-pipe handling under ipc.

Flatten display contexts and rename files after their contained classes.
Keep transport headers limited to transport contracts and capabilities.

Consolidate CSwapChainProcessor definitions so each source file matches
the class it implements.
This commit is contained in:
Geoffrey McRae
2026-08-07 18:16:18 +10:00
parent 6725133375
commit 1de52f6e8a
34 changed files with 482 additions and 523 deletions

View File

@@ -21,8 +21,8 @@
#pragma once
#include "capture/CFrameBufferResource.h"
#include "transport/FrameBufferTypes.h"
#include "transport/TransportTypes.h"
#include "capture/FramePipeline.h"
#include "transport/PreparedFrameBuffer.h"
struct CD3D12Device;
class IFrameTransport;
@@ -33,7 +33,7 @@ private:
IFrameTransport * m_transport = nullptr;
CD3D12Device * m_dx12 = nullptr;
CFrameBufferResource m_buffers[TRANSPORT_FRAME_BUFFER_COUNT];
CFrameBufferResource m_buffers[CAPTURE_FRAME_BUFFERS];
public:
void Init(IFrameTransport * transport, CD3D12Device * dx12);

View File

@@ -27,7 +27,7 @@
#include <atomic>
#include <stdint.h>
#include "transport/CFrameScheduler.h"
#include "capture/CFrameScheduler.h"
#include "d3d/CInteropResource.h"
struct CD3D12Device;

View File

@@ -29,7 +29,7 @@
CFrameProcessor::CFrameProcessor(IFrameTransport * transport,
std::shared_ptr<CD3D12Device> dx12,
CPostProcessor postProcessors[TRANSPORT_FRAME_QUEUE_LENGTH],
CPostProcessor postProcessors[CAPTURE_PIPELINE_SLOTS],
SRWLOCK * pipelineLock, HANDLE terminateEvent) :
m_transport(transport),
m_dx12(std::move(dx12)),
@@ -156,7 +156,7 @@ void CFrameProcessor::GetPreviousDamage(
std::unique_ptr<CFrameProcessor> CreateFrameProcessor(
bool software, IFrameTransport * transport,
std::shared_ptr<CD3D12Device> dx12,
CPostProcessor postProcessors[TRANSPORT_FRAME_QUEUE_LENGTH],
CPostProcessor postProcessors[CAPTURE_PIPELINE_SLOTS],
SRWLOCK * pipelineLock, HANDLE terminateEvent)
{
std::unique_ptr<CFrameProcessor> processor;

View File

@@ -24,8 +24,8 @@
#include "capture/CFrameBufferPool.h"
#include "d3d/CInteropResource.h"
#include "postprocess/CPostProcessor.h"
#include "transport/CFrameScheduler.h"
#include "transport/TransportTypes.h"
#include "capture/CFrameScheduler.h"
#include "capture/FramePipeline.h"
#include <Windows.h>
#include <memory>
@@ -76,7 +76,7 @@ protected:
public:
CFrameProcessor(IFrameTransport * transport,
std::shared_ptr<CD3D12Device> dx12,
CPostProcessor postProcessors[TRANSPORT_FRAME_QUEUE_LENGTH],
CPostProcessor postProcessors[CAPTURE_PIPELINE_SLOTS],
SRWLOCK * pipelineLock, HANDLE terminateEvent);
virtual ~CFrameProcessor() = default;
@@ -98,5 +98,5 @@ public:
std::unique_ptr<CFrameProcessor> CreateFrameProcessor(
bool software, IFrameTransport * transport,
std::shared_ptr<CD3D12Device> dx12,
CPostProcessor postProcessors[TRANSPORT_FRAME_QUEUE_LENGTH],
CPostProcessor postProcessors[CAPTURE_PIPELINE_SLOTS],
SRWLOCK * pipelineLock, HANDLE terminateEvent);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,189 @@
/**
* 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 <stdint.h>
enum : uint32_t
{
FRAME_SCHEDULE_ACTIVE = 0x1,
FRAME_SCHEDULE_RELEASE = 0x2,
FRAME_SCHEDULE_RESET = 0x4,
FRAME_SCHEDULE_IMMEDIATE = 0x8,
};
struct FrameScheduleUpdate
{
uint32_t clientID;
uint32_t generation;
uint32_t flags;
uint64_t period;
uint64_t targetSlack;
int64_t phaseError;
uint32_t feedbackFrameSerial;
uint32_t feedbackScheduleEpoch;
uint32_t feedbackDeadlineSerial;
uint32_t lease;
};
class CFrameScheduler
{
public:
static const unsigned MAX_CLIENTS = 8;
struct Schedule
{
uint32_t clientID;
uint32_t generation;
uint32_t epoch;
uint64_t period;
uint64_t targetSlack;
uint64_t deadline;
uint64_t forceTicket;
uint64_t republishTicket;
uint32_t deadlineSerial;
uint32_t deliveryDeadlineSerial;
bool phaseEligible;
};
private:
struct Client
{
uint32_t clientID;
uint32_t generation;
uint64_t period;
uint64_t targetSlack;
uint64_t expiry;
uint64_t nextDelivery;
uint32_t lastFeedbackDeadlineSerial;
uint32_t lastDeliveredFrameSerial;
bool subscribed;
bool ownerCapable;
bool subscriptionSeen;
bool active;
bool immediate;
bool deliveredFrameValid;
};
struct Publication
{
uint32_t generation;
uint32_t epoch;
uint32_t deadlineSerial;
uint32_t frameSerial;
uint64_t deadline;
bool committed;
bool completed;
bool phaseValid;
bool accepted;
};
static const unsigned PUBLICATION_HISTORY_SIZE = 128;
static const unsigned WORK_TIMING_HISTORY_SIZE = 32;
mutable SRWLOCK m_lock = SRWLOCK_INIT;
HANDLE m_wakeEvent = nullptr;
Client m_clients[MAX_CLIENTS] = {};
Schedule m_schedule = {};
bool m_scheduling = false;
uint32_t m_epoch = 0;
// A result acknowledges only the request tickets captured by its attempt.
uint64_t m_forceRequestTicket = 0;
uint64_t m_forceAckTicket = 0;
uint64_t m_republishRequestTicket = 0;
uint64_t m_republishAckTicket = 0;
uint64_t m_lastArrival = 0;
uint64_t m_guestPeriod = 0;
uint64_t m_workEstimate = 0;
uint64_t m_workTiming[WORK_TIMING_HISTORY_SIZE] = {};
unsigned m_workTimingCount = 0;
unsigned m_workTimingIndex = 0;
uint64_t m_nextDeadline = 0;
uint32_t m_deadlineSerial = 0;
int64_t m_pendingCorrection = 0;
Publication m_publications[PUBLICATION_HISTORY_SIZE] = {};
unsigned m_publicationIndex = 0;
int64_t m_lastPhaseError = 0;
uint64_t m_acquiredFrames = 0;
uint64_t m_skippedFrames = 0;
uint64_t m_publishedFrames = 0;
uint64_t m_lastLog = 0;
uint64_t m_lastLogAcquired = 0;
uint64_t m_lastLogSkipped = 0;
uint64_t m_lastLogPublished = 0;
Client * FindClient(uint32_t clientID);
Client * FindOrAllocateClient(uint32_t clientID);
Publication * FindPublication(const Schedule& schedule,
uint32_t frameSerial);
bool ElectOwner(uint64_t now, uint32_t resetClientID = 0);
bool ApplyFeedback(Client& client, const FrameScheduleUpdate& schedule);
void AdvanceCurrentDeadline();
void AdvanceDeadlineSerial(uint64_t count);
void AdvanceDeadline(uint64_t now);
static void AdvanceDelivery(Client& client, uint64_t now);
void WakePublisher() const;
public:
CFrameScheduler();
~CFrameScheduler();
static uint64_t Nanotime();
void Reset();
void UpdateSubscribers(const uint32_t * clientIDs, unsigned count,
const uint32_t * ownerClientIDs, unsigned ownerCount, uint64_t now);
bool UpdateSchedule(uint32_t sourceClientID,
const FrameScheduleUpdate& schedule, uint64_t now);
bool GetSchedule(Schedule& schedule) const;
HANDLE GetWakeEvent() const { return m_wakeEvent; }
void ObserveFrame(uint64_t now);
void ForceFrame();
bool GetPublishTarget(uint64_t now, uint64_t& target,
Schedule& schedule, bool& periodic, bool& republish);
void FrameMissed(const Schedule& schedule, uint64_t now, bool periodic);
void FrameSuperseded();
bool TryFrameSubmitted(const Schedule& schedule, uint32_t frameSerial);
void FramePublished(const Schedule& schedule, uint32_t frameSerial,
uint64_t now, bool periodic);
void FrameRetained(const Schedule& schedule, uint64_t now,
bool periodic);
void FrameRepublished(const Schedule& schedule, uint32_t frameSerial);
bool TryFrameCompleted(const Schedule& schedule, uint32_t frameSerial,
uint64_t completedAt);
unsigned GetSecondaryRecipients(const uint32_t * clientIDs,
unsigned count, uint32_t frameSerial, uint64_t now,
uint32_t * recipients) const;
bool GetSecondaryTarget(uint32_t frameSerial, uint64_t now,
const uint32_t * blockedClientIDs, unsigned blockedCount,
uint64_t& target) const;
void FrameDelivered(const uint32_t * clientIDs, unsigned count,
uint32_t frameSerial, uint64_t now);
void RequestRepublish();
void NotifyPublisher() const { WakePublisher(); }
void TryRecordFrameTiming(uint64_t duration);
void LogStatistics(uint64_t now);
};

View File

@@ -29,7 +29,7 @@
using namespace Microsoft::WRL;
static_assert(TRANSPORT_FRAME_QUEUE_LENGTH == 2,
static_assert(CAPTURE_PIPELINE_SLOTS == 2,
"IDD candidate pipeline assumes two slots");
class CPublishPending
@@ -72,7 +72,7 @@ public:
CHardwareFrameProcessor::CHardwareFrameProcessor(
IFrameTransport * transport, std::shared_ptr<CD3D12Device> dx12,
CPostProcessor postProcessors[TRANSPORT_FRAME_QUEUE_LENGTH],
CPostProcessor postProcessors[CAPTURE_PIPELINE_SLOTS],
SRWLOCK * pipelineLock, HANDLE terminateEvent) :
CFrameProcessor(transport, std::move(dx12), postProcessors,
pipelineLock, terminateEvent)

View File

@@ -65,8 +65,8 @@ private:
bool active = false;
};
FrameCandidate m_candidates[TRANSPORT_FRAME_QUEUE_LENGTH];
CandidateDamageTail m_candidateDamageTail[TRANSPORT_FRAME_QUEUE_LENGTH];
FrameCandidate m_candidates[CAPTURE_PIPELINE_SLOTS];
CandidateDamageTail m_candidateDamageTail[CAPTURE_PIPELINE_SLOTS];
mutable SRWLOCK m_candidateLock = SRWLOCK_INIT;
SRWLOCK m_copySubmitLock = SRWLOCK_INIT;
uint64_t m_candidateSequence = 0;
@@ -91,7 +91,7 @@ private:
public:
CHardwareFrameProcessor(IFrameTransport * transport,
std::shared_ptr<CD3D12Device> dx12,
CPostProcessor postProcessors[TRANSPORT_FRAME_QUEUE_LENGTH],
CPostProcessor postProcessors[CAPTURE_PIPELINE_SLOTS],
SRWLOCK * pipelineLock, HANDLE terminateEvent);
bool IsValid() const override;

View File

@@ -20,7 +20,7 @@
#include "capture/CSoftwareFrameProcessor.h"
#include "capture/CFrameProcessorUtil.h"
#include "transport/TransportTypes.h"
#include "capture/FramePipeline.h"
#include "transport/IFrameTransport.h"
#include "util/CSRWLock.h"
#include "CDebug.h"
@@ -29,7 +29,7 @@
CSoftwareFrameProcessor::CSoftwareFrameProcessor(
IFrameTransport * transport, std::shared_ptr<CD3D12Device> dx12,
CPostProcessor postProcessors[TRANSPORT_FRAME_QUEUE_LENGTH],
CPostProcessor postProcessors[CAPTURE_PIPELINE_SLOTS],
SRWLOCK * pipelineLock, HANDLE terminateEvent) :
CFrameProcessor(transport, std::move(dx12), postProcessors,
pipelineLock, terminateEvent),

View File

@@ -33,7 +33,7 @@ private:
public:
CSoftwareFrameProcessor(IFrameTransport * transport,
std::shared_ptr<CD3D12Device> dx12,
CPostProcessor postProcessors[TRANSPORT_FRAME_QUEUE_LENGTH],
CPostProcessor postProcessors[CAPTURE_PIPELINE_SLOTS],
SRWLOCK * pipelineLock, HANDLE terminateEvent);
bool Submit(const FrameSubmission& submission) override;

View File

@@ -1,122 +0,0 @@
/**
* 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 "capture/CSwapChainProcessor.h"
#include "display/IddCxCompat.h"
#include "display/device/CDeviceContext.h"
#include "transport/IControlTransport.h"
#include "CDebug.h"
DWORD CALLBACK CSwapChainProcessor::_CursorThread(LPVOID arg)
{
reinterpret_cast<CSwapChainProcessor*>(arg)->CursorThread();
return 0;
}
bool CSwapChainProcessor::QueryHWCursor()
{
IDARG_IN_QUERY_HWCURSOR in = {};
in.LastShapeId = m_lastShapeId;
in.pShapeBuffer = m_shapeBuffer;
in.ShapeBufferSizeInBytes = 512 * 512 * 4;
IDARG_OUT_QUERY_HWCURSOR out = {};
UINT cursorWhiteLevel = m_sdrWhiteLevel.load(std::memory_order_relaxed);
NTSTATUS status;
#ifdef HAS_IDDCX_110
if (m_devContext->HasIddCx110DDIs())
{
IDARG_OUT_QUERY_HWCURSOR3 out3 = {};
status = IddCxMonitorQueryHardwareCursor3(m_monitor, &in, &out3);
out.IsCursorVisible = out3.IsCursorVisible;
out.X = out3.X;
out.Y = out3.Y;
out.IsCursorShapeUpdated = out3.IsCursorShapeUpdated;
out.CursorShapeInfo = out3.CursorShapeInfo;
if (out3.SdrWhiteLevel)
cursorWhiteLevel = out3.SdrWhiteLevel;
}
else
#endif
{
status = IddCxMonitorQueryHardwareCursor(m_monitor, &in, &out);
}
if (FAILED(status))
{
// this occurs if the display went away (ie, screen blanking or disabled)
if (status == STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY)
{
SetEvent(m_terminateEvent.Get());
return false;
}
DEBUG_ERROR("IddCxMonitorQueryHardwareCursor failed (0x%08x)", status);
return false;
}
if (out.IsCursorShapeUpdated)
m_lastShapeId = out.CursorShapeInfo.ShapeId;
m_control.SendCursor(out, m_shapeBuffer, cursorWhiteLevel);
return true;
}
void CSwapChainProcessor::CursorThread()
{
HRESULT hr = 0;
bool running = true;
while (running)
{
HANDLE waitHandles[] =
{
m_cursorDataEvent.Get(),
m_terminateEvent.Get()
};
DWORD waitResult = WaitForMultipleObjects(
ARRAYSIZE(waitHandles), waitHandles, FALSE, 100);
switch (waitResult)
{
case WAIT_TIMEOUT:
continue;
// cursorDataEvent
case WAIT_OBJECT_0:
if (!QueryHWCursor())
return;
continue;
// terminateEvent
case WAIT_OBJECT_0 + 1:
running = false;
continue;
default:
hr = HRESULT_FROM_WIN32(waitResult);
DEBUG_ERROR_HR(hr, "WaitForMultipleObjects");
return;
}
}
}

View File

@@ -21,8 +21,8 @@
#include "capture/CSwapChainProcessor.h"
#include "capture/CFrameProcessorUtil.h"
#include "display/IddCxCompat.h"
#include "display/device/CDeviceContext.h"
#include "display/monitor/Context.h"
#include "display/CDeviceContext.h"
#include "display/CMonitorContext.h"
#include "platform/CPlatformInfo.h"
#include "transport/IFrameTransport.h"
#include "transport/IControlTransport.h"
@@ -31,7 +31,7 @@
#include <avrt.h>
#include <new>
#include "CDebug.h"
#include "transport/CPipeServer.h"
#include "ipc/CPipeServer.h"
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
@@ -737,3 +737,340 @@ bool CSwapChainProcessor::SwapChainNewFrame(ComPtr<IDXGIResource> acquiredBuffer
};
return m_frameProcessor->Submit(submission);
}
static const uint64_t PUBLISH_RETRY_NS = 1000000ULL;
static bool ArmPublishTimer(HANDLE timer, uint64_t delay)
{
if (!timer)
return false;
LARGE_INTEGER due = {};
due.QuadPart = -static_cast<LONGLONG>((delay + 99) / 100);
if (!due.QuadPart)
due.QuadPart = -1;
return SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE) != FALSE;
}
DWORD CALLBACK CSwapChainProcessor::_PublisherThread(LPVOID arg)
{
reinterpret_cast<CSwapChainProcessor *>(arg)->PublisherThread();
return 0;
}
void CSwapChainProcessor::PublisherThread()
{
DWORD avTask = 0;
HANDLE avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask);
if (avTaskHandle &&
!AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH))
DEBUG_WARN("Failed to raise publisher MMCSS priority: %lu",
GetLastError());
const HANDLE scheduleEvent = m_transport.GetFrameScheduleEvent();
HANDLE idleHandles[] =
{
m_terminateEvent.Get(),
m_frameProcessor->GetReadyEvent(),
scheduleEvent,
};
HANDLE timerHandles[] =
{
m_terminateEvent.Get(),
m_frameProcessor->GetReadyEvent(),
scheduleEvent,
m_publishTimer.Get(),
};
const bool cadenceEnabled = m_frameProcessor->UsesCadence();
for (;;)
{
const uint64_t now = CFrameScheduler::Nanotime();
uint64_t target;
CFrameScheduler::Schedule schedule;
bool periodic;
bool republish;
m_transport.GetPublishTarget(
now, target, schedule, periodic, republish);
const bool ready = m_frameProcessor->HasReadyFrame();
if (!ready)
{
m_transport.ProcessDeliveries();
if (m_frameProcessor->HasReadyFrame())
continue;
uint64_t current = CFrameScheduler::Nanotime();
uint64_t cadenceTarget = 0;
if (cadenceEnabled && schedule.deliveryDeadlineSerial && periodic)
{
if (schedule.deadline <= current)
{
m_transport.FrameMissed(schedule, current, periodic);
continue;
}
cadenceTarget = schedule.deadline;
}
if (republish && m_transport.HasPublishedFrame())
{
if (m_transport.RepublishFrameBuffer(schedule))
continue;
current = CFrameScheduler::Nanotime();
if (cadenceTarget && cadenceTarget <= current)
{
m_transport.FrameMissed(schedule, current, periodic);
continue;
}
uint64_t retryTarget = current + PUBLISH_RETRY_NS;
if (cadenceTarget)
retryTarget = min(retryTarget, cadenceTarget);
ArmPublishTimer(m_publishTimer.Get(), retryTarget - current);
if (WaitForMultipleObjects(
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
uint64_t replayTarget;
if (m_transport.GetPendingDeliveryTarget(current, replayTarget))
{
bool retry = false;
if (replayTarget <= current)
{
if (m_transport.RetryPendingDelivery(current, retry))
continue;
current = CFrameScheduler::Nanotime();
if (cadenceTarget && cadenceTarget <= current)
{
m_transport.FrameMissed(schedule, current, periodic);
continue;
}
if (retry)
replayTarget = current + PUBLISH_RETRY_NS;
else
{
if (cadenceTarget)
replayTarget = cadenceTarget;
else
{
if (m_publishTimer.Get())
CancelWaitableTimer(m_publishTimer.Get());
if (WaitForMultipleObjects(
ARRAYSIZE(idleHandles), idleHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
}
}
if (cadenceTarget)
replayTarget = min(replayTarget, cadenceTarget);
current = CFrameScheduler::Nanotime();
if (cadenceTarget && cadenceTarget <= current)
{
m_transport.FrameMissed(schedule, current, periodic);
continue;
}
if (replayTarget <= current)
continue;
ArmPublishTimer(m_publishTimer.Get(), replayTarget - current);
if (WaitForMultipleObjects(
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
if (cadenceTarget)
{
current = CFrameScheduler::Nanotime();
if (cadenceTarget <= current)
{
m_transport.FrameMissed(schedule, current, periodic);
continue;
}
ArmPublishTimer(m_publishTimer.Get(), cadenceTarget - current);
if (WaitForMultipleObjects(
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
if (m_publishTimer.Get())
CancelWaitableTimer(m_publishTimer.Get());
if (WaitForMultipleObjects(
ARRAYSIZE(idleHandles), idleHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
uint64_t current = CFrameScheduler::Nanotime();
uint64_t replayTarget;
if (m_transport.GetPendingDeliveryTarget(current, replayTarget) &&
replayTarget < target)
{
if (replayTarget <= current)
{
m_transport.ProcessDeliveries();
current = CFrameScheduler::Nanotime();
bool retry = false;
if (m_transport.RetryPendingDelivery(current, retry))
continue;
current = CFrameScheduler::Nanotime();
if (retry)
replayTarget = current + PUBLISH_RETRY_NS;
else
replayTarget = target;
}
replayTarget = min(replayTarget, target);
current = CFrameScheduler::Nanotime();
if (target > current)
{
if (replayTarget <= current)
continue;
ArmPublishTimer(m_publishTimer.Get(), replayTarget - current);
if (WaitForMultipleObjects(
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
}
current = CFrameScheduler::Nanotime();
if (target > current)
{
ArmPublishTimer(m_publishTimer.Get(), target - current);
if (WaitForMultipleObjects(
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
const uint64_t publishStart = CFrameScheduler::Nanotime();
m_transport.ProcessDeliveries();
if (!m_transport.FrameBufferAvailable(schedule) ||
!m_frameProcessor->Publish(schedule, periodic, publishStart))
{
ArmPublishTimer(m_publishTimer.Get(), PUBLISH_RETRY_NS);
if (WaitForMultipleObjects(
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
}
}
if (avTaskHandle)
AvRevertMmThreadCharacteristics(avTaskHandle);
}
DWORD CALLBACK CSwapChainProcessor::_CursorThread(LPVOID arg)
{
reinterpret_cast<CSwapChainProcessor*>(arg)->CursorThread();
return 0;
}
bool CSwapChainProcessor::QueryHWCursor()
{
IDARG_IN_QUERY_HWCURSOR in = {};
in.LastShapeId = m_lastShapeId;
in.pShapeBuffer = m_shapeBuffer;
in.ShapeBufferSizeInBytes = 512 * 512 * 4;
IDARG_OUT_QUERY_HWCURSOR out = {};
UINT cursorWhiteLevel = m_sdrWhiteLevel.load(std::memory_order_relaxed);
NTSTATUS status;
#ifdef HAS_IDDCX_110
if (m_devContext->HasIddCx110DDIs())
{
IDARG_OUT_QUERY_HWCURSOR3 out3 = {};
status = IddCxMonitorQueryHardwareCursor3(m_monitor, &in, &out3);
out.IsCursorVisible = out3.IsCursorVisible;
out.X = out3.X;
out.Y = out3.Y;
out.IsCursorShapeUpdated = out3.IsCursorShapeUpdated;
out.CursorShapeInfo = out3.CursorShapeInfo;
if (out3.SdrWhiteLevel)
cursorWhiteLevel = out3.SdrWhiteLevel;
}
else
#endif
{
status = IddCxMonitorQueryHardwareCursor(m_monitor, &in, &out);
}
if (FAILED(status))
{
// this occurs if the display went away (ie, screen blanking or disabled)
if (status == STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY)
{
SetEvent(m_terminateEvent.Get());
return false;
}
DEBUG_ERROR("IddCxMonitorQueryHardwareCursor failed (0x%08x)", status);
return false;
}
if (out.IsCursorShapeUpdated)
m_lastShapeId = out.CursorShapeInfo.ShapeId;
m_control.SendCursor(out, m_shapeBuffer, cursorWhiteLevel);
return true;
}
void CSwapChainProcessor::CursorThread()
{
HRESULT hr = 0;
bool running = true;
while (running)
{
HANDLE waitHandles[] =
{
m_cursorDataEvent.Get(),
m_terminateEvent.Get()
};
DWORD waitResult = WaitForMultipleObjects(
ARRAYSIZE(waitHandles), waitHandles, FALSE, 100);
switch (waitResult)
{
case WAIT_TIMEOUT:
continue;
// cursorDataEvent
case WAIT_OBJECT_0:
if (!QueryHWCursor())
return;
continue;
// terminateEvent
case WAIT_OBJECT_0 + 1:
running = false;
continue;
default:
hr = HRESULT_FROM_WIN32(waitResult);
DEBUG_ERROR_HR(hr, "WaitForMultipleObjects");
return;
}
}
}

View File

@@ -56,7 +56,7 @@ private:
HANDLE m_newFrameEvent;
CInteropResourcePool m_resPool;
CPostProcessor m_postProcessors[TRANSPORT_FRAME_QUEUE_LENGTH];
CPostProcessor m_postProcessors[CAPTURE_PIPELINE_SLOTS];
std::unique_ptr<CFrameProcessor> m_frameProcessor;
// Reconfiguration is exclusive while per-candidate recording is shared.
SRWLOCK m_pipelineLock = SRWLOCK_INIT;

View File

@@ -1,268 +0,0 @@
/**
* 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 "capture/CSwapChainProcessor.h"
#include "transport/IFrameTransport.h"
#include <avrt.h>
#include "CDebug.h"
static const uint64_t PUBLISH_RETRY_NS = 1000000ULL;
static bool ArmPublishTimer(HANDLE timer, uint64_t delay)
{
if (!timer)
return false;
LARGE_INTEGER due = {};
due.QuadPart = -static_cast<LONGLONG>((delay + 99) / 100);
if (!due.QuadPart)
due.QuadPart = -1;
return SetWaitableTimer(timer, &due, 0, nullptr, nullptr, FALSE) != FALSE;
}
DWORD CALLBACK CSwapChainProcessor::_PublisherThread(LPVOID arg)
{
reinterpret_cast<CSwapChainProcessor *>(arg)->PublisherThread();
return 0;
}
void CSwapChainProcessor::PublisherThread()
{
DWORD avTask = 0;
HANDLE avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask);
if (avTaskHandle &&
!AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH))
DEBUG_WARN("Failed to raise publisher MMCSS priority: %lu",
GetLastError());
const HANDLE scheduleEvent = m_transport.GetFrameScheduleEvent();
HANDLE idleHandles[] =
{
m_terminateEvent.Get(),
m_frameProcessor->GetReadyEvent(),
scheduleEvent,
};
HANDLE timerHandles[] =
{
m_terminateEvent.Get(),
m_frameProcessor->GetReadyEvent(),
scheduleEvent,
m_publishTimer.Get(),
};
const bool cadenceEnabled = m_frameProcessor->UsesCadence();
for (;;)
{
const uint64_t now = CFrameScheduler::Nanotime();
uint64_t target;
CFrameScheduler::Schedule schedule;
bool periodic;
bool republish;
m_transport.GetPublishTarget(
now, target, schedule, periodic, republish);
const bool ready = m_frameProcessor->HasReadyFrame();
if (!ready)
{
m_transport.ProcessDeliveries();
if (m_frameProcessor->HasReadyFrame())
continue;
uint64_t current = CFrameScheduler::Nanotime();
uint64_t cadenceTarget = 0;
if (cadenceEnabled && schedule.deliveryDeadlineSerial && periodic)
{
if (schedule.deadline <= current)
{
m_transport.FrameMissed(schedule, current, periodic);
continue;
}
cadenceTarget = schedule.deadline;
}
if (republish && m_transport.HasPublishedFrame())
{
if (m_transport.RepublishFrameBuffer(schedule))
continue;
current = CFrameScheduler::Nanotime();
if (cadenceTarget && cadenceTarget <= current)
{
m_transport.FrameMissed(schedule, current, periodic);
continue;
}
uint64_t retryTarget = current + PUBLISH_RETRY_NS;
if (cadenceTarget)
retryTarget = min(retryTarget, cadenceTarget);
ArmPublishTimer(m_publishTimer.Get(), retryTarget - current);
if (WaitForMultipleObjects(
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
uint64_t replayTarget;
if (m_transport.GetPendingDeliveryTarget(current, replayTarget))
{
bool retry = false;
if (replayTarget <= current)
{
if (m_transport.RetryPendingDelivery(current, retry))
continue;
current = CFrameScheduler::Nanotime();
if (cadenceTarget && cadenceTarget <= current)
{
m_transport.FrameMissed(schedule, current, periodic);
continue;
}
if (retry)
replayTarget = current + PUBLISH_RETRY_NS;
else
{
if (cadenceTarget)
replayTarget = cadenceTarget;
else
{
if (m_publishTimer.Get())
CancelWaitableTimer(m_publishTimer.Get());
if (WaitForMultipleObjects(
ARRAYSIZE(idleHandles), idleHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
}
}
if (cadenceTarget)
replayTarget = min(replayTarget, cadenceTarget);
current = CFrameScheduler::Nanotime();
if (cadenceTarget && cadenceTarget <= current)
{
m_transport.FrameMissed(schedule, current, periodic);
continue;
}
if (replayTarget <= current)
continue;
ArmPublishTimer(m_publishTimer.Get(), replayTarget - current);
if (WaitForMultipleObjects(
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
if (cadenceTarget)
{
current = CFrameScheduler::Nanotime();
if (cadenceTarget <= current)
{
m_transport.FrameMissed(schedule, current, periodic);
continue;
}
ArmPublishTimer(m_publishTimer.Get(), cadenceTarget - current);
if (WaitForMultipleObjects(
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
if (m_publishTimer.Get())
CancelWaitableTimer(m_publishTimer.Get());
if (WaitForMultipleObjects(
ARRAYSIZE(idleHandles), idleHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
uint64_t current = CFrameScheduler::Nanotime();
uint64_t replayTarget;
if (m_transport.GetPendingDeliveryTarget(current, replayTarget) &&
replayTarget < target)
{
if (replayTarget <= current)
{
m_transport.ProcessDeliveries();
current = CFrameScheduler::Nanotime();
bool retry = false;
if (m_transport.RetryPendingDelivery(current, retry))
continue;
current = CFrameScheduler::Nanotime();
if (retry)
replayTarget = current + PUBLISH_RETRY_NS;
else
replayTarget = target;
}
replayTarget = min(replayTarget, target);
current = CFrameScheduler::Nanotime();
if (target > current)
{
if (replayTarget <= current)
continue;
ArmPublishTimer(m_publishTimer.Get(), replayTarget - current);
if (WaitForMultipleObjects(
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
}
current = CFrameScheduler::Nanotime();
if (target > current)
{
ArmPublishTimer(m_publishTimer.Get(), target - current);
if (WaitForMultipleObjects(
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
continue;
}
const uint64_t publishStart = CFrameScheduler::Nanotime();
m_transport.ProcessDeliveries();
if (!m_transport.FrameBufferAvailable(schedule) ||
!m_frameProcessor->Publish(schedule, periodic, publishStart))
{
ArmPublishTimer(m_publishTimer.Get(), PUBLISH_RETRY_NS);
if (WaitForMultipleObjects(
ARRAYSIZE(timerHandles), timerHandles, FALSE, INFINITE) ==
WAIT_OBJECT_0)
break;
}
}
if (avTaskHandle)
AvRevertMmThreadCharacteristics(avTaskHandle);
}

View File

@@ -0,0 +1,27 @@
/**
* 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
enum : unsigned
{
CAPTURE_PIPELINE_SLOTS = 2,
CAPTURE_FRAME_BUFFERS = 3,
};