mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-04 06:12:04 +00:00
[idd] elect the fastest frame timing client
This commit is contained in:
216
idd/LGIdd/CFrameScheduler.cpp
Normal file
216
idd/LGIdd/CFrameScheduler.cpp
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
/**
|
||||||
|
* 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 "CFrameScheduler.h"
|
||||||
|
|
||||||
|
#include "CDebug.h"
|
||||||
|
|
||||||
|
static const uint64_t MIN_PERIOD_NS = 2000000ULL;
|
||||||
|
static const uint64_t MAX_PERIOD_NS = 1000000000ULL;
|
||||||
|
static const uint32_t MIN_LEASE_MS = 100;
|
||||||
|
static const uint32_t MAX_LEASE_MS = 5000;
|
||||||
|
|
||||||
|
uint64_t CFrameScheduler::Nanotime()
|
||||||
|
{
|
||||||
|
static const uint64_t frequency = []()
|
||||||
|
{
|
||||||
|
LARGE_INTEGER value;
|
||||||
|
QueryPerformanceFrequency(&value);
|
||||||
|
return static_cast<uint64_t>(value.QuadPart);
|
||||||
|
}();
|
||||||
|
|
||||||
|
LARGE_INTEGER counter;
|
||||||
|
QueryPerformanceCounter(&counter);
|
||||||
|
const uint64_t ticks = static_cast<uint64_t>(counter.QuadPart);
|
||||||
|
return ticks / frequency * 1000000000ULL +
|
||||||
|
ticks % frequency * 1000000000ULL / frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
CFrameScheduler::Client * CFrameScheduler::FindClient(uint32_t clientID)
|
||||||
|
{
|
||||||
|
if (!clientID)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
for (Client& client : m_clients)
|
||||||
|
if (client.clientID == clientID)
|
||||||
|
return &client;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFrameScheduler::ElectOwner(uint64_t now)
|
||||||
|
{
|
||||||
|
Client * fastest = nullptr;
|
||||||
|
Client * incumbent = FindClient(m_schedule.clientID);
|
||||||
|
unsigned subscribers = 0;
|
||||||
|
|
||||||
|
for (Client& client : m_clients)
|
||||||
|
{
|
||||||
|
if (!client.subscribed)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
++subscribers;
|
||||||
|
if (!client.active || client.expiry <= now)
|
||||||
|
{
|
||||||
|
client.active = false;
|
||||||
|
fastest = nullptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fastest || client.period < fastest->period)
|
||||||
|
fastest = &client;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!subscribers)
|
||||||
|
fastest = nullptr;
|
||||||
|
|
||||||
|
if (fastest && incumbent && incumbent->subscribed && incumbent->active &&
|
||||||
|
incumbent->expiry > now &&
|
||||||
|
incumbent->period <= fastest->period + fastest->period / 200)
|
||||||
|
fastest = incumbent;
|
||||||
|
|
||||||
|
const uint32_t oldClientID = m_schedule.clientID;
|
||||||
|
const uint32_t oldGeneration = m_schedule.generation;
|
||||||
|
if (!fastest)
|
||||||
|
{
|
||||||
|
m_schedule = {};
|
||||||
|
m_scheduling = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_schedule.clientID = fastest->clientID;
|
||||||
|
m_schedule.generation = fastest->generation;
|
||||||
|
m_schedule.period = fastest->period;
|
||||||
|
m_schedule.targetSlack = fastest->targetSlack;
|
||||||
|
m_scheduling = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldClientID != m_schedule.clientID ||
|
||||||
|
oldGeneration != m_schedule.generation)
|
||||||
|
{
|
||||||
|
if (m_scheduling)
|
||||||
|
DEBUG_INFO("Frame timing owner %u generation %u at %.3f Hz",
|
||||||
|
m_schedule.clientID, m_schedule.generation,
|
||||||
|
1000000000.0 / m_schedule.period);
|
||||||
|
else if (oldClientID)
|
||||||
|
DEBUG_INFO("Frame timing owner released; using push delivery");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFrameScheduler::Reset()
|
||||||
|
{
|
||||||
|
AcquireSRWLockExclusive(&m_lock);
|
||||||
|
for (Client& client : m_clients)
|
||||||
|
client = {};
|
||||||
|
m_schedule = {};
|
||||||
|
m_scheduling = false;
|
||||||
|
ReleaseSRWLockExclusive(&m_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CFrameScheduler::UpdateSubscribers(const uint32_t * clientIDs,
|
||||||
|
unsigned count, uint64_t now)
|
||||||
|
{
|
||||||
|
AcquireSRWLockExclusive(&m_lock);
|
||||||
|
|
||||||
|
for (Client& client : m_clients)
|
||||||
|
client.subscribed = false;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
Client * client = FindClient(clientIDs[i]);
|
||||||
|
if (!client)
|
||||||
|
for (Client& candidate : m_clients)
|
||||||
|
if (!candidate.clientID)
|
||||||
|
{
|
||||||
|
candidate.clientID = clientIDs[i];
|
||||||
|
client = &candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client)
|
||||||
|
client->subscribed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Client& client : m_clients)
|
||||||
|
if (client.clientID && !client.subscribed)
|
||||||
|
client = {};
|
||||||
|
|
||||||
|
ElectOwner(now);
|
||||||
|
ReleaseSRWLockExclusive(&m_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CFrameScheduler::UpdateSchedule(const KVMFRFrameSchedule& schedule,
|
||||||
|
uint64_t now)
|
||||||
|
{
|
||||||
|
static const KVMFRFrameScheduleFlags validFlags =
|
||||||
|
KVMFR_FRAME_SCHEDULE_ACTIVE |
|
||||||
|
KVMFR_FRAME_SCHEDULE_RELEASE |
|
||||||
|
KVMFR_FRAME_SCHEDULE_RESET |
|
||||||
|
KVMFR_FRAME_SCHEDULE_IMMEDIATE;
|
||||||
|
|
||||||
|
if (!schedule.clientID || schedule.flags & ~validFlags)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
AcquireSRWLockExclusive(&m_lock);
|
||||||
|
Client * client = FindClient(schedule.clientID);
|
||||||
|
if (!client || !client->subscribed)
|
||||||
|
{
|
||||||
|
ReleaseSRWLockExclusive(&m_lock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schedule.flags & KVMFR_FRAME_SCHEDULE_RELEASE)
|
||||||
|
{
|
||||||
|
client->active = false;
|
||||||
|
client->expiry = 0;
|
||||||
|
ElectOwner(now);
|
||||||
|
ReleaseSRWLockExclusive(&m_lock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(schedule.flags & KVMFR_FRAME_SCHEDULE_ACTIVE) ||
|
||||||
|
schedule.period < MIN_PERIOD_NS ||
|
||||||
|
schedule.period > MAX_PERIOD_NS ||
|
||||||
|
schedule.targetSlack >= schedule.period ||
|
||||||
|
schedule.lease < MIN_LEASE_MS || schedule.lease > MAX_LEASE_MS)
|
||||||
|
{
|
||||||
|
ReleaseSRWLockExclusive(&m_lock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client->generation = schedule.generation;
|
||||||
|
client->period = schedule.period;
|
||||||
|
client->targetSlack = schedule.targetSlack;
|
||||||
|
client->expiry = now + static_cast<uint64_t>(schedule.lease) * 1000000;
|
||||||
|
client->active = true;
|
||||||
|
ElectOwner(now);
|
||||||
|
ReleaseSRWLockExclusive(&m_lock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CFrameScheduler::GetSchedule(Schedule& schedule) const
|
||||||
|
{
|
||||||
|
AcquireSRWLockShared(&m_lock);
|
||||||
|
const bool result = m_scheduling;
|
||||||
|
if (result)
|
||||||
|
schedule = m_schedule;
|
||||||
|
ReleaseSRWLockShared(&m_lock);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
71
idd/LGIdd/CFrameScheduler.h
Normal file
71
idd/LGIdd/CFrameScheduler.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <lgmp/lgmp.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "common/KVMFR.h"
|
||||||
|
|
||||||
|
class CFrameScheduler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Schedule
|
||||||
|
{
|
||||||
|
uint32_t clientID;
|
||||||
|
uint32_t generation;
|
||||||
|
uint64_t period;
|
||||||
|
uint64_t targetSlack;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Client
|
||||||
|
{
|
||||||
|
uint32_t clientID;
|
||||||
|
uint32_t generation;
|
||||||
|
uint64_t period;
|
||||||
|
uint64_t targetSlack;
|
||||||
|
uint64_t expiry;
|
||||||
|
bool subscribed;
|
||||||
|
bool active;
|
||||||
|
};
|
||||||
|
|
||||||
|
mutable SRWLOCK m_lock = SRWLOCK_INIT;
|
||||||
|
Client m_clients[LGMP_MAX_CLIENTS] = {};
|
||||||
|
Schedule m_schedule = {};
|
||||||
|
bool m_scheduling = false;
|
||||||
|
|
||||||
|
Client * FindClient(uint32_t clientID);
|
||||||
|
void ElectOwner(uint64_t now);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static uint64_t Nanotime();
|
||||||
|
|
||||||
|
void Reset();
|
||||||
|
void UpdateSubscribers(const uint32_t * clientIDs, unsigned count,
|
||||||
|
uint64_t now);
|
||||||
|
bool UpdateSchedule(const KVMFRFrameSchedule& schedule, uint64_t now);
|
||||||
|
bool GetSchedule(Schedule& schedule) const;
|
||||||
|
};
|
||||||
@@ -1195,6 +1195,7 @@ void CIndirectDeviceContext::DeInitLGMP()
|
|||||||
|
|
||||||
if (m_lgmp == nullptr)
|
if (m_lgmp == nullptr)
|
||||||
{
|
{
|
||||||
|
m_frameScheduler.Reset();
|
||||||
m_publishedFrameIndex.store(-1, std::memory_order_release);
|
m_publishedFrameIndex.store(-1, std::memory_order_release);
|
||||||
m_frameResendPending = false;
|
m_frameResendPending = false;
|
||||||
return;
|
return;
|
||||||
@@ -1206,6 +1207,8 @@ void CIndirectDeviceContext::DeInitLGMP()
|
|||||||
m_lgmpTimer = nullptr;
|
m_lgmpTimer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_frameScheduler.Reset();
|
||||||
|
|
||||||
AcquireSRWLockExclusive(&m_framePublishLock);
|
AcquireSRWLockExclusive(&m_framePublishLock);
|
||||||
m_publishedFrameIndex.store(-1, std::memory_order_release);
|
m_publishedFrameIndex.store(-1, std::memory_order_release);
|
||||||
m_frameResendPending = false;
|
m_frameResendPending = false;
|
||||||
@@ -1256,6 +1259,16 @@ void CIndirectDeviceContext::LGMPTimer()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uint64_t now = CFrameScheduler::Nanotime();
|
||||||
|
uint32_t clientIDs[LGMP_MAX_CLIENTS] = {};
|
||||||
|
unsigned clientCount = 0;
|
||||||
|
status = lgmpHostGetClientIDs(m_frameQueue, clientIDs, &clientCount);
|
||||||
|
if (status == LGMP_OK)
|
||||||
|
m_frameScheduler.UpdateSubscribers(clientIDs, clientCount, now);
|
||||||
|
else
|
||||||
|
DEBUG_WARN("Failed to query LGMP frame subscribers: %s",
|
||||||
|
lgmpStatusString(status));
|
||||||
|
|
||||||
uint8_t data[LGMP_MSGS_SIZE];
|
uint8_t data[LGMP_MSGS_SIZE];
|
||||||
size_t size;
|
size_t size;
|
||||||
while ((status = lgmpHostReadData(m_pointerQueue, &data, &size)) == LGMP_OK)
|
while ((status = lgmpHostReadData(m_pointerQueue, &data, &size)) == LGMP_OK)
|
||||||
@@ -1274,6 +1287,16 @@ void CIndirectDeviceContext::LGMPTimer()
|
|||||||
{
|
{
|
||||||
KVMFRWindowSize* ws = (KVMFRWindowSize*)msg;
|
KVMFRWindowSize* ws = (KVMFRWindowSize*)msg;
|
||||||
SetResolution(ws->w, ws->h);
|
SetResolution(ws->w, ws->h);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case KVMFR_MESSAGE_FRAME_SCHEDULE:
|
||||||
|
{
|
||||||
|
if (size != sizeof(KVMFRFrameSchedule) ||
|
||||||
|
!m_frameScheduler.UpdateSchedule(
|
||||||
|
*reinterpret_cast<KVMFRFrameSchedule *>(msg), now))
|
||||||
|
DEBUG_WARN("Ignoring invalid KVMFR frame schedule");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "CSettings.h"
|
#include "CSettings.h"
|
||||||
#include "CEdid.h"
|
#include "CEdid.h"
|
||||||
#include "CPostProcessor.h"
|
#include "CPostProcessor.h"
|
||||||
|
#include "CFrameScheduler.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "lgmp/host.h"
|
#include "lgmp/host.h"
|
||||||
@@ -89,6 +90,8 @@ private:
|
|||||||
WDFTIMER m_lgmpTimer = nullptr;
|
WDFTIMER m_lgmpTimer = nullptr;
|
||||||
PLGMPHostQueue m_frameQueue = nullptr;
|
PLGMPHostQueue m_frameQueue = nullptr;
|
||||||
|
|
||||||
|
CFrameScheduler m_frameScheduler;
|
||||||
|
|
||||||
PLGMPHostQueue m_pointerQueue = nullptr;
|
PLGMPHostQueue m_pointerQueue = nullptr;
|
||||||
PLGMPMemory m_pointerMemory [LGMP_Q_POINTER_LEN ] = {};
|
PLGMPMemory m_pointerMemory [LGMP_Q_POINTER_LEN ] = {};
|
||||||
PLGMPMemory m_pointerShapeMemory[POINTER_SHAPE_BUFFERS] = {};
|
PLGMPMemory m_pointerShapeMemory[POINTER_SHAPE_BUFFERS] = {};
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
<ClCompile Include="CEdid.cpp" />
|
<ClCompile Include="CEdid.cpp" />
|
||||||
<ClCompile Include="CFrameBufferPool.cpp" />
|
<ClCompile Include="CFrameBufferPool.cpp" />
|
||||||
<ClCompile Include="CFrameBufferResource.cpp" />
|
<ClCompile Include="CFrameBufferResource.cpp" />
|
||||||
|
<ClCompile Include="CFrameScheduler.cpp" />
|
||||||
<ClCompile Include="CIndirectDeviceContext.cpp" />
|
<ClCompile Include="CIndirectDeviceContext.cpp" />
|
||||||
<ClCompile Include="CIndirectMonitorContext.cpp" />
|
<ClCompile Include="CIndirectMonitorContext.cpp" />
|
||||||
<ClCompile Include="CInteropResourcePool.cpp" />
|
<ClCompile Include="CInteropResourcePool.cpp" />
|
||||||
@@ -55,6 +56,7 @@
|
|||||||
<ClInclude Include="CEdid.h" />
|
<ClInclude Include="CEdid.h" />
|
||||||
<ClInclude Include="CFrameBufferPool.h" />
|
<ClInclude Include="CFrameBufferPool.h" />
|
||||||
<ClInclude Include="CFrameBufferResource.h" />
|
<ClInclude Include="CFrameBufferResource.h" />
|
||||||
|
<ClInclude Include="CFrameScheduler.h" />
|
||||||
<ClInclude Include="CIndirectMonitorContext.h" />
|
<ClInclude Include="CIndirectMonitorContext.h" />
|
||||||
<ClInclude Include="CInteropResourcePool.h" />
|
<ClInclude Include="CInteropResourcePool.h" />
|
||||||
<ClInclude Include="CInteropResource.h" />
|
<ClInclude Include="CInteropResource.h" />
|
||||||
|
|||||||
@@ -82,6 +82,9 @@
|
|||||||
<ClInclude Include="CFrameBufferPool.h">
|
<ClInclude Include="CFrameBufferPool.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="CFrameScheduler.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="$(SolutionDir)/LGCommon/*.h" />
|
<ClInclude Include="$(SolutionDir)/LGCommon/*.h" />
|
||||||
<ClInclude Include="CPipeServer.h">
|
<ClInclude Include="CPipeServer.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
@@ -122,6 +125,9 @@
|
|||||||
<ClCompile Include="CIndirectDeviceContext.cpp">
|
<ClCompile Include="CIndirectDeviceContext.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="CFrameScheduler.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="CIndirectMonitorContext.cpp">
|
<ClCompile Include="CIndirectMonitorContext.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
Reference in New Issue
Block a user