mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +00:00
[client] transport: track video component availability
This commit is contained in:
@@ -65,10 +65,42 @@ struct LGMPFrameLease
|
||||
LG_TransportFrameFormat format;
|
||||
uint64_t handle;
|
||||
uint32_t generation;
|
||||
uint64_t receiveTime;
|
||||
uint64_t prepareTime;
|
||||
bool providerValid;
|
||||
bool releaseRequested;
|
||||
bool active;
|
||||
};
|
||||
|
||||
typedef struct LGMPVideoStatusUpdate
|
||||
{
|
||||
bool frame;
|
||||
bool frameAvailable;
|
||||
bool frameReplaced;
|
||||
LG_TransportStatus frameReason;
|
||||
bool pointer;
|
||||
bool pointerAvailable;
|
||||
bool pointerReplaced;
|
||||
LG_TransportStatus pointerReason;
|
||||
}
|
||||
LGMPVideoStatusUpdate;
|
||||
|
||||
typedef struct LGMPVideoStatusDispatch
|
||||
{
|
||||
LG_VideoStatusFn callback;
|
||||
void * opaque;
|
||||
uint64_t listenerSerial;
|
||||
bool pending;
|
||||
}
|
||||
LGMPVideoStatusDispatch;
|
||||
|
||||
typedef struct LGMPVideoCallbackContext
|
||||
{
|
||||
struct LGMPVideoCallbackContext * previous;
|
||||
LG_Transport * transport;
|
||||
}
|
||||
LGMPVideoCallbackContext;
|
||||
|
||||
struct LG_Transport
|
||||
{
|
||||
struct IVSHMEM shm;
|
||||
@@ -79,16 +111,28 @@ struct LG_Transport
|
||||
LGMPInput * input;
|
||||
LG_Lock frameLock;
|
||||
LG_Lock pointerLock;
|
||||
LG_RWLock videoStatusLock;
|
||||
LGEvent * frameWake;
|
||||
LGEvent * pointerWake;
|
||||
|
||||
LG_VideoStatusFn videoStatusCallback;
|
||||
void * videoStatusOpaque;
|
||||
atomic_uint videoStatusCallbacks;
|
||||
uint64_t videoStatusListenerSerial;
|
||||
uint64_t videoFrameEpoch;
|
||||
uint64_t videoPointerEpoch;
|
||||
LG_TransportStatus videoFrameReason;
|
||||
LG_TransportStatus videoPointerReason;
|
||||
bool videoFrameAvailable;
|
||||
bool videoPointerAvailable;
|
||||
|
||||
unsigned cursorPollInterval;
|
||||
unsigned framePollInterval;
|
||||
bool allowDMA;
|
||||
bool connected;
|
||||
atomic_bool connected;
|
||||
bool frameStopRequested;
|
||||
bool frameScheduleSupported;
|
||||
bool inputSupported;
|
||||
atomic_bool inputSupported;
|
||||
uint64_t frameLeaseHandle;
|
||||
uint32_t frameGeneration;
|
||||
uint32_t clientID;
|
||||
@@ -111,8 +155,249 @@ struct LG_Transport
|
||||
uint32_t recoveryCandidateHeartbeat;
|
||||
uint32_t recoveryLGMPVersion;
|
||||
bool recoveryLive;
|
||||
atomic_bool destroyPending;
|
||||
};
|
||||
|
||||
static LG_Lock l_videoStatusCallbackLock = ATOMIC_FLAG_INIT;
|
||||
static _Thread_local LGMPVideoCallbackContext * l_videoStatusContext;
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
static atomic_bool * l_videoStatusDispatchWaiting;
|
||||
static atomic_bool * l_videoStatusDispatchHold;
|
||||
static LG_Transport * l_videoStatusDispatchTarget;
|
||||
static LG_Lock l_videoStatusTestLock = ATOMIC_FLAG_INIT;
|
||||
|
||||
void lgmp_testSetVideoStatusDispatchGate(
|
||||
LG_Transport * target, atomic_bool * waiting, atomic_bool * hold)
|
||||
{
|
||||
LG_LOCK(l_videoStatusTestLock);
|
||||
l_videoStatusDispatchTarget = target;
|
||||
l_videoStatusDispatchWaiting = waiting;
|
||||
l_videoStatusDispatchHold = hold;
|
||||
LG_UNLOCK(l_videoStatusTestLock);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void lgmp_setVideoStatusListener(LG_Transport * this,
|
||||
LG_VideoStatusFn callback, void * callbackOpaque);
|
||||
static void lgmp_destroyNow(LG_Transport * this);
|
||||
static void lgmp_releaseFrame(LG_Transport * this,
|
||||
LG_TransportFrame * frame);
|
||||
|
||||
static unsigned lgmp_videoStatusOwnCallbacks(const LG_Transport * transport)
|
||||
{
|
||||
unsigned count = 0;
|
||||
for (const LGMPVideoCallbackContext * context = l_videoStatusContext;
|
||||
context; context = context->previous)
|
||||
count += context->transport == transport;
|
||||
return count;
|
||||
}
|
||||
|
||||
static bool lgmp_videoStatusContextContains(const LG_Transport * transport)
|
||||
{
|
||||
for (const LGMPVideoCallbackContext * context = l_videoStatusContext;
|
||||
context; context = context->previous)
|
||||
if (context->transport == transport)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void lgmp_waitVideoStatusCallbacks(
|
||||
LG_Transport * transport, unsigned ownCallbacks)
|
||||
{
|
||||
while (atomic_load_explicit(
|
||||
&transport->videoStatusCallbacks, memory_order_acquire) >
|
||||
ownCallbacks)
|
||||
nsleep(1000000U);
|
||||
|
||||
/* A retiring callback publishes the count while holding this lock. Taking
|
||||
* it here ensures that callback has completed every access to the instance
|
||||
* before an external caller tears it down. */
|
||||
LG_LOCK_EXCLUSIVE(transport->videoStatusLock);
|
||||
LG_UNLOCK_EXCLUSIVE(transport->videoStatusLock);
|
||||
}
|
||||
|
||||
static bool lgmp_releaseVideoStatusCallback(LG_Transport * transport)
|
||||
{
|
||||
/* Linearize the final reference and a nested destroy request. Whichever
|
||||
* side acquires the lock first either observes the pending request or leaves
|
||||
* a zero count for the destroy path to observe. */
|
||||
LG_LOCK_EXCLUSIVE(transport->videoStatusLock);
|
||||
const bool destroy = atomic_load_explicit(
|
||||
&transport->destroyPending, memory_order_acquire);
|
||||
const unsigned previous = atomic_fetch_sub_explicit(
|
||||
&transport->videoStatusCallbacks, 1, memory_order_acq_rel);
|
||||
DEBUG_ASSERT(previous != 0);
|
||||
LG_UNLOCK_EXCLUSIVE(transport->videoStatusLock);
|
||||
return destroy && previous == 1;
|
||||
}
|
||||
|
||||
static void lgmp_retainVideoStatusLifetime(LG_Transport * transport)
|
||||
{
|
||||
atomic_fetch_add_explicit(
|
||||
&transport->videoStatusCallbacks, 1, memory_order_acq_rel);
|
||||
}
|
||||
|
||||
static bool lgmp_destroyRequested(const LG_Transport * transport)
|
||||
{
|
||||
return atomic_load_explicit(
|
||||
&transport->destroyPending, memory_order_acquire);
|
||||
}
|
||||
|
||||
static void lgmp_releaseVideoStatusLifetime(LG_Transport * transport)
|
||||
{
|
||||
if (lgmp_releaseVideoStatusCallback(transport))
|
||||
lgmp_destroyNow(transport);
|
||||
}
|
||||
|
||||
static uint64_t lgmp_nextEpoch(uint64_t epoch)
|
||||
{
|
||||
if (++epoch == 0)
|
||||
++epoch;
|
||||
return epoch;
|
||||
}
|
||||
|
||||
static void lgmp_callVideoStatusCallback(LG_VideoStatusFn callback,
|
||||
void * opaque,
|
||||
LG_Transport * transport,
|
||||
const LG_VideoStatus * status)
|
||||
{
|
||||
LGMPVideoCallbackContext context =
|
||||
{
|
||||
.previous = l_videoStatusContext,
|
||||
.transport = transport,
|
||||
};
|
||||
l_videoStatusContext = &context;
|
||||
callback(opaque, status);
|
||||
l_videoStatusContext = context.previous;
|
||||
}
|
||||
|
||||
static LG_VideoStatus lgmp_videoStatusLocked(const LG_Transport * this)
|
||||
{
|
||||
return (LG_VideoStatus)
|
||||
{
|
||||
.frame =
|
||||
{
|
||||
.available = this->videoFrameAvailable,
|
||||
.epoch = this->videoFrameEpoch,
|
||||
.reason = this->videoFrameAvailable ? LG_TRANSPORT_OK :
|
||||
this->videoFrameReason,
|
||||
},
|
||||
.pointer =
|
||||
{
|
||||
.available = this->videoPointerAvailable,
|
||||
.epoch = this->videoPointerEpoch,
|
||||
.reason = this->videoPointerAvailable ? LG_TRANSPORT_OK :
|
||||
this->videoPointerReason,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static LG_VideoStatus lgmp_updateVideoStatus(LG_Transport * this,
|
||||
const LGMPVideoStatusUpdate update,
|
||||
LGMPVideoStatusDispatch * dispatch)
|
||||
{
|
||||
*dispatch = (LGMPVideoStatusDispatch) { 0 };
|
||||
LG_VideoStatus published;
|
||||
|
||||
LG_LOCK_EXCLUSIVE(this->videoStatusLock);
|
||||
const LG_TransportStatus frameReason = update.frameAvailable ?
|
||||
LG_TRANSPORT_OK : update.frameReason == LG_TRANSPORT_OK ?
|
||||
LG_TRANSPORT_UNAVAILABLE : update.frameReason;
|
||||
const LG_TransportStatus pointerReason = update.pointerAvailable ?
|
||||
LG_TRANSPORT_OK : update.pointerReason == LG_TRANSPORT_OK ?
|
||||
LG_TRANSPORT_UNAVAILABLE : update.pointerReason;
|
||||
const bool frameChanged = update.frame &&
|
||||
this->videoFrameAvailable != update.frameAvailable;
|
||||
const bool pointerChanged = update.pointer &&
|
||||
this->videoPointerAvailable != update.pointerAvailable;
|
||||
const bool frameReasonChanged = update.frame &&
|
||||
this->videoFrameReason != frameReason;
|
||||
const bool pointerReasonChanged = update.pointer &&
|
||||
this->videoPointerReason != pointerReason;
|
||||
if (frameChanged || update.frameReplaced)
|
||||
this->videoFrameEpoch = lgmp_nextEpoch(this->videoFrameEpoch);
|
||||
if (pointerChanged || update.pointerReplaced)
|
||||
this->videoPointerEpoch = lgmp_nextEpoch(this->videoPointerEpoch);
|
||||
if (update.frame)
|
||||
{
|
||||
this->videoFrameAvailable = update.frameAvailable;
|
||||
this->videoFrameReason = frameReason;
|
||||
}
|
||||
if (update.pointer)
|
||||
{
|
||||
this->videoPointerAvailable = update.pointerAvailable;
|
||||
this->videoPointerReason = pointerReason;
|
||||
}
|
||||
published = lgmp_videoStatusLocked(this);
|
||||
|
||||
dispatch->callback = this->videoStatusCallback;
|
||||
dispatch->opaque = this->videoStatusOpaque;
|
||||
dispatch->listenerSerial = this->videoStatusListenerSerial;
|
||||
if (!dispatch->callback || (!frameChanged && !pointerChanged &&
|
||||
!frameReasonChanged && !pointerReasonChanged &&
|
||||
!update.frameReplaced && !update.pointerReplaced))
|
||||
dispatch->callback = NULL;
|
||||
if (dispatch->callback)
|
||||
atomic_fetch_add_explicit(
|
||||
&this->videoStatusCallbacks, 1, memory_order_acq_rel);
|
||||
dispatch->pending = dispatch->callback != NULL;
|
||||
LG_UNLOCK_EXCLUSIVE(this->videoStatusLock);
|
||||
return published;
|
||||
}
|
||||
|
||||
static void lgmp_dispatchVideoStatus(LG_Transport * this,
|
||||
const LGMPVideoStatusDispatch * dispatch)
|
||||
{
|
||||
if (dispatch->pending)
|
||||
{
|
||||
const bool serialized = l_videoStatusContext == NULL;
|
||||
if (serialized)
|
||||
{
|
||||
#ifdef ENABLE_TESTS
|
||||
LG_LOCK(l_videoStatusTestLock);
|
||||
LG_Transport * target = l_videoStatusDispatchTarget;
|
||||
atomic_bool * waiting = l_videoStatusDispatchWaiting;
|
||||
atomic_bool * hold = l_videoStatusDispatchHold;
|
||||
LG_UNLOCK(l_videoStatusTestLock);
|
||||
if ((!target || target == this) && waiting)
|
||||
atomic_store_explicit(
|
||||
waiting, true, memory_order_release);
|
||||
if ((!target || target == this) && hold)
|
||||
while (atomic_load_explicit(
|
||||
hold, memory_order_acquire))
|
||||
nsleep(1000000U);
|
||||
#endif
|
||||
LG_LOCK(l_videoStatusCallbackLock);
|
||||
}
|
||||
LG_LOCK_SHARED(this->videoStatusLock);
|
||||
const bool current =
|
||||
this->videoStatusCallback == dispatch->callback &&
|
||||
this->videoStatusOpaque == dispatch->opaque &&
|
||||
this->videoStatusListenerSerial == dispatch->listenerSerial;
|
||||
const LG_VideoStatus status = lgmp_videoStatusLocked(this);
|
||||
LG_UNLOCK_SHARED(this->videoStatusLock);
|
||||
if (current)
|
||||
lgmp_callVideoStatusCallback(
|
||||
dispatch->callback, dispatch->opaque, this, &status);
|
||||
const bool destroy = lgmp_releaseVideoStatusCallback(this);
|
||||
if (serialized)
|
||||
LG_UNLOCK(l_videoStatusCallbackLock);
|
||||
if (destroy)
|
||||
lgmp_destroyNow(this);
|
||||
}
|
||||
}
|
||||
|
||||
static LG_VideoStatus lgmp_publishVideoStatus(LG_Transport * this,
|
||||
const LGMPVideoStatusUpdate update)
|
||||
{
|
||||
LGMPVideoStatusDispatch dispatch;
|
||||
const LG_VideoStatus published =
|
||||
lgmp_updateVideoStatus(this, update, &dispatch);
|
||||
lgmp_dispatchVideoStatus(this, &dispatch);
|
||||
return published;
|
||||
}
|
||||
|
||||
static bool lgmp_deviceValidator(struct Option * opt, const char ** error)
|
||||
{
|
||||
const char * transport = option_get_string("app", "transport");
|
||||
@@ -238,13 +523,23 @@ static bool lgmp_recoverySnapshot(const struct LG_Transport * this,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool lgmp_refreshRecoveryLocked(struct LG_Transport * this, bool wait)
|
||||
static bool lgmp_cancelled(LG_TransportCancelledFn cancelled, void * opaque)
|
||||
{
|
||||
return cancelled && cancelled(opaque);
|
||||
}
|
||||
|
||||
static bool lgmp_refreshRecoveryLockedCancellable(
|
||||
struct LG_Transport * this, bool wait,
|
||||
LG_TransportCancelledFn cancelled, void * opaque)
|
||||
{
|
||||
const uint64_t deadline = wait ?
|
||||
microtime() + LGMP_RECOVERY_PROBE_TIMEOUT_US : 0;
|
||||
|
||||
do
|
||||
{
|
||||
if (lgmp_cancelled(cancelled, opaque))
|
||||
break;
|
||||
|
||||
KVMFRRHeader header;
|
||||
KVMFRRInfo info;
|
||||
const bool valid = lgmp_recoverySnapshot(this, &header, &info);
|
||||
@@ -293,6 +588,12 @@ static bool lgmp_refreshRecoveryLocked(struct LG_Transport * this, bool wait)
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool lgmp_refreshRecoveryLocked(struct LG_Transport * this, bool wait)
|
||||
{
|
||||
return lgmp_refreshRecoveryLockedCancellable(
|
||||
this, wait, NULL, NULL);
|
||||
}
|
||||
|
||||
static bool lgmp_recoveryVersions(struct LG_Transport * this,
|
||||
uint32_t * lgmpVersion, uint32_t * kvmfrVersion)
|
||||
{
|
||||
@@ -347,6 +648,9 @@ static bool lgmp_create(LG_Transport ** result)
|
||||
}
|
||||
this->framePollInterval = framePoll;
|
||||
this->cursorPollInterval = cursorPoll;
|
||||
atomic_init(&this->connected, false);
|
||||
atomic_init(&this->inputSupported, false);
|
||||
atomic_init(&this->destroyPending, false);
|
||||
|
||||
this->frameWake = lgCreateEvent(true, 0);
|
||||
this->pointerWake = lgCreateEvent(true, 0);
|
||||
@@ -365,7 +669,9 @@ static bool lgmp_create(LG_Transport ** result)
|
||||
|
||||
LG_LOCK_INIT(this->frameLock);
|
||||
LG_LOCK_INIT(this->pointerLock);
|
||||
LG_RWLOCK_INIT(this->videoStatusLock);
|
||||
LG_LOCK_INIT(this->recoveryLock);
|
||||
atomic_init(&this->videoStatusCallbacks, 0);
|
||||
|
||||
this->frameGeneration = 1;
|
||||
this->frameLease[0].subscription = &this->frameQueue;
|
||||
@@ -377,6 +683,7 @@ static bool lgmp_create(LG_Transport ** result)
|
||||
{
|
||||
LG_LOCK_FREE(this->frameLock);
|
||||
LG_LOCK_FREE(this->pointerLock);
|
||||
LG_RWLOCK_FREE(this->videoStatusLock);
|
||||
LG_LOCK_FREE(this->recoveryLock);
|
||||
lgFreeEvent(this->frameWake);
|
||||
lgFreeEvent(this->pointerWake);
|
||||
@@ -415,6 +722,7 @@ static bool lgmp_create(LG_Transport ** result)
|
||||
ivshmemClose(&this->shm);
|
||||
LG_LOCK_FREE(this->frameLock);
|
||||
LG_LOCK_FREE(this->pointerLock);
|
||||
LG_RWLOCK_FREE(this->videoStatusLock);
|
||||
LG_LOCK_FREE(this->recoveryLock);
|
||||
lgFreeEvent(this->frameWake);
|
||||
lgFreeEvent(this->pointerWake);
|
||||
@@ -527,6 +835,13 @@ static void lgmp_closeQueues(struct LG_Transport * this)
|
||||
{
|
||||
lgmp_stopFrame(this);
|
||||
lgmp_stopPointer(this);
|
||||
lgmp_publishVideoStatus(this, (LGMPVideoStatusUpdate)
|
||||
{
|
||||
.frame = true,
|
||||
.frameReason = LG_TRANSPORT_DISCONNECTED,
|
||||
.pointer = true,
|
||||
.pointerReason = LG_TRANSPORT_DISCONNECTED,
|
||||
});
|
||||
}
|
||||
|
||||
static void lgmp_closeDMA(struct LG_Transport * this)
|
||||
@@ -541,12 +856,8 @@ static void lgmp_closeDMA(struct LG_Transport * this)
|
||||
}
|
||||
}
|
||||
|
||||
static void lgmp_destroy(LG_Transport ** transport)
|
||||
static void lgmp_destroyNow(LG_Transport * this)
|
||||
{
|
||||
if (!transport || !*transport)
|
||||
return;
|
||||
|
||||
struct LG_Transport * this = *transport;
|
||||
if (this->client)
|
||||
{
|
||||
lgmpInput_destroy(&this->input);
|
||||
@@ -558,11 +869,36 @@ static void lgmp_destroy(LG_Transport ** transport)
|
||||
ivshmemClose(&this->shm);
|
||||
LG_LOCK_FREE(this->frameLock);
|
||||
LG_LOCK_FREE(this->pointerLock);
|
||||
LG_RWLOCK_FREE(this->videoStatusLock);
|
||||
LG_LOCK_FREE(this->recoveryLock);
|
||||
lgFreeEvent(this->frameWake);
|
||||
lgFreeEvent(this->pointerWake);
|
||||
free(this);
|
||||
}
|
||||
|
||||
static void lgmp_destroy(LG_Transport ** transport)
|
||||
{
|
||||
if (!transport || !*transport)
|
||||
return;
|
||||
|
||||
struct LG_Transport * this = *transport;
|
||||
*transport = NULL;
|
||||
const bool nested = l_videoStatusContext != NULL;
|
||||
lgmp_setVideoStatusListener(this, NULL, NULL);
|
||||
if (nested)
|
||||
{
|
||||
LG_LOCK_EXCLUSIVE(this->videoStatusLock);
|
||||
atomic_store_explicit(
|
||||
&this->destroyPending, true, memory_order_release);
|
||||
const bool deferred = lgmp_videoStatusContextContains(this) ||
|
||||
atomic_load_explicit(
|
||||
&this->videoStatusCallbacks, memory_order_acquire) != 0;
|
||||
LG_UNLOCK_EXCLUSIVE(this->videoStatusLock);
|
||||
if (deferred)
|
||||
return;
|
||||
}
|
||||
|
||||
lgmp_destroyNow(this);
|
||||
}
|
||||
|
||||
static void lgmp_setVersionMismatch(LG_TransportSession * session,
|
||||
@@ -675,19 +1011,26 @@ static bool lgmp_parseSession(struct LG_Transport * this,
|
||||
return true;
|
||||
}
|
||||
|
||||
static LG_TransportStatus lgmp_connect(LG_Transport * this,
|
||||
LG_TransportSession * session)
|
||||
static LG_TransportStatus lgmp_connectInternal(LG_Transport * this,
|
||||
LG_TransportSession * session, LG_TransportCancelledFn cancelled,
|
||||
void * opaque)
|
||||
{
|
||||
memset(session, 0, sizeof(*session));
|
||||
session->os = LG_TRANSPORT_OS_OTHER;
|
||||
|
||||
if (lgmp_cancelled(cancelled, opaque))
|
||||
return LG_TRANSPORT_DISCONNECTED;
|
||||
|
||||
if (!this->client)
|
||||
{
|
||||
LG_LOCK(this->recoveryLock);
|
||||
lgmp_refreshRecoveryLocked(this,
|
||||
lgmp_recoveryMagic(this->recovery));
|
||||
lgmp_refreshRecoveryLockedCancellable(this,
|
||||
lgmp_recoveryMagic(this->recovery), cancelled, opaque);
|
||||
LG_UNLOCK(this->recoveryLock);
|
||||
|
||||
if (lgmp_cancelled(cancelled, opaque))
|
||||
return LG_TRANSPORT_DISCONNECTED;
|
||||
|
||||
const LGMP_STATUS status = lgmp_initializeClient(this);
|
||||
if (status != LGMP_OK)
|
||||
{
|
||||
@@ -709,6 +1052,9 @@ static LG_TransportStatus lgmp_connect(LG_Transport * this,
|
||||
}
|
||||
}
|
||||
|
||||
if (lgmp_cancelled(cancelled, opaque))
|
||||
return LG_TRANSPORT_DISCONNECTED;
|
||||
|
||||
uint32_t size;
|
||||
uint8_t * data;
|
||||
uint32_t remoteVersion = 0;
|
||||
@@ -720,19 +1066,28 @@ static LG_TransportStatus lgmp_connect(LG_Transport * this,
|
||||
if (!lgmp_parseSession(this, data, size, session))
|
||||
return LG_TRANSPORT_INVALID_VERSION;
|
||||
|
||||
LGMPVideoStatusDispatch dispatch;
|
||||
LG_LOCK(this->frameLock);
|
||||
if (++this->frameGeneration == 0)
|
||||
++this->frameGeneration;
|
||||
this->connected = true;
|
||||
this->frameStopRequested = false;
|
||||
this->frameScheduleSupported =
|
||||
session->features & LG_TRANSPORT_FEATURE_FRAME_SCHEDULE;
|
||||
this->inputSupported =
|
||||
session->features & LG_TRANSPORT_FEATURE_INPUT;
|
||||
atomic_store_explicit(&this->inputSupported,
|
||||
session->features & LG_TRANSPORT_FEATURE_INPUT,
|
||||
memory_order_release);
|
||||
this->frameSerial = 0;
|
||||
this->frameSerialValid = false;
|
||||
this->formatValid = false;
|
||||
lgmp_updateVideoStatus(this, (LGMPVideoStatusUpdate)
|
||||
{
|
||||
.frame = true,
|
||||
.pointer = true,
|
||||
}, &dispatch);
|
||||
atomic_store_explicit(
|
||||
&this->connected, true, memory_order_release);
|
||||
LG_UNLOCK(this->frameLock);
|
||||
lgmp_dispatchVideoStatus(this, &dispatch);
|
||||
return LG_TRANSPORT_OK;
|
||||
|
||||
case LGMP_ERR_INVALID_VERSION:
|
||||
@@ -761,32 +1116,68 @@ static LG_TransportStatus lgmp_connect(LG_Transport * this,
|
||||
}
|
||||
}
|
||||
|
||||
static LG_TransportStatus lgmp_connect(LG_Transport * this,
|
||||
LG_TransportSession * session)
|
||||
{
|
||||
lgmp_retainVideoStatusLifetime(this);
|
||||
LG_TransportStatus status =
|
||||
lgmp_connectInternal(this, session, NULL, NULL);
|
||||
if (lgmp_destroyRequested(this))
|
||||
status = LG_TRANSPORT_DISCONNECTED;
|
||||
lgmp_releaseVideoStatusLifetime(this);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void lgmp_disconnect(LG_Transport * this)
|
||||
{
|
||||
if (!this->client)
|
||||
return;
|
||||
|
||||
lgmp_retainVideoStatusLifetime(this);
|
||||
|
||||
lgmpInput_disconnect(this->input);
|
||||
lgmp_closeQueues(this);
|
||||
|
||||
LG_LOCK(this->frameLock);
|
||||
if (++this->frameGeneration == 0)
|
||||
++this->frameGeneration;
|
||||
this->frameQueue = NULL;
|
||||
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
||||
this->ownerFrameQueue[i] = NULL;
|
||||
this->connected = false;
|
||||
atomic_store_explicit(
|
||||
&this->connected, false, memory_order_release);
|
||||
this->frameScheduleSupported = false;
|
||||
this->inputSupported = false;
|
||||
atomic_store_explicit(
|
||||
&this->inputSupported, false, memory_order_release);
|
||||
this->clientID = 0;
|
||||
LG_UNLOCK(this->frameLock);
|
||||
|
||||
lgmp_closeQueues(this);
|
||||
lgmp_closeDMA(this);
|
||||
lgmp_releaseVideoStatusLifetime(this);
|
||||
}
|
||||
|
||||
static LG_TransportStatus lgmp_connectCancellable(LG_Transport * this,
|
||||
LG_TransportSession * session, LG_TransportCancelledFn cancelled,
|
||||
void * opaque)
|
||||
{
|
||||
lgmp_retainVideoStatusLifetime(this);
|
||||
const LG_TransportStatus status = lgmp_connectInternal(
|
||||
this, session, cancelled, opaque);
|
||||
LG_TransportStatus result = status;
|
||||
if (lgmp_destroyRequested(this))
|
||||
result = LG_TRANSPORT_DISCONNECTED;
|
||||
else if (lgmp_cancelled(cancelled, opaque))
|
||||
{
|
||||
if (status == LG_TRANSPORT_OK)
|
||||
lgmp_disconnect(this);
|
||||
result = LG_TRANSPORT_DISCONNECTED;
|
||||
}
|
||||
|
||||
lgmp_releaseVideoStatusLifetime(this);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool lgmp_sessionValid(LG_Transport * this)
|
||||
{
|
||||
return this->client && this->connected &&
|
||||
return this->client && atomic_load_explicit(
|
||||
&this->connected, memory_order_acquire) &&
|
||||
lgmpClientSessionValid(this->client);
|
||||
}
|
||||
|
||||
@@ -834,11 +1225,36 @@ static LG_RecoveryError lgmp_recoveryError(uint32_t error)
|
||||
return LG_RECOVERY_ERR_TOPOLOGY_FAILED;
|
||||
case KVMFR_R_ERR_NO_FALLBACK_DISPLAY:
|
||||
return LG_RECOVERY_ERR_NO_FALLBACK_DISPLAY;
|
||||
case KVMFR_R_ERR_BUSY:
|
||||
return LG_RECOVERY_ERR_BUSY;
|
||||
case KVMFR_R_ERR_CAPACITY:
|
||||
return LG_RECOVERY_ERR_CAPACITY;
|
||||
default:
|
||||
return LG_RECOVERY_ERR_UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
LG_RecoveryError lgmp_testRecoveryError(uint32_t error)
|
||||
{
|
||||
return lgmp_recoveryError(error);
|
||||
}
|
||||
|
||||
bool lgmp_testRecoveryProbeCancellation(
|
||||
LG_TransportCancelledFn cancelled, void * opaque)
|
||||
{
|
||||
struct LG_Transport transport =
|
||||
{
|
||||
.lgmpSize = 1,
|
||||
};
|
||||
LG_LOCK_INIT(transport.recoveryLock);
|
||||
const bool result = lgmp_refreshRecoveryLockedCancellable(
|
||||
&transport, true, cancelled, opaque);
|
||||
LG_LOCK_FREE(transport.recoveryLock);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool lgmp_recoveryRequestSnapshot(const KVMFRRRequest * source,
|
||||
KVMFRRRequest * result)
|
||||
{
|
||||
@@ -1380,13 +1796,14 @@ static int lgmp_getDMA(struct LG_Transport * this, const KVMFRFrame * frame,
|
||||
}
|
||||
|
||||
static void lgmp_releaseFrameLease(void * opaque, uint64_t handle);
|
||||
static bool lgmp_frameTimingReady(const KVMFRFrame * frame);
|
||||
|
||||
static LG_TransportStatus lgmp_nextFrameLocked(LG_Transport * this,
|
||||
bool useDMA,
|
||||
LG_TransportFrame * result)
|
||||
{
|
||||
lgmp_drainFrameLeasesLocked(this);
|
||||
if (!this->connected)
|
||||
if (!atomic_load_explicit(&this->connected, memory_order_acquire))
|
||||
return LG_TRANSPORT_DISCONNECTED;
|
||||
|
||||
this->frameStopRequested = false;
|
||||
@@ -1513,6 +1930,9 @@ static LG_TransportStatus lgmp_nextFrameLocked(LG_Transport * this,
|
||||
return done == LG_TRANSPORT_OK ? LG_TRANSPORT_TIMEOUT : done;
|
||||
}
|
||||
|
||||
const bool providerValid = lgmp_frameTimingReady(frame);
|
||||
const uint64_t providerStart = providerValid ? nanotime() : 0;
|
||||
|
||||
const bool fullDamage = !this->frameSerialValid || equalSerial ||
|
||||
frame->frameSerial != this->frameSerial + 1;
|
||||
this->frameSerial = frame->frameSerial;
|
||||
@@ -1602,6 +2022,9 @@ static LG_TransportStatus lgmp_nextFrameLocked(LG_Transport * this,
|
||||
++this->frameLeaseHandle;
|
||||
lease->handle = this->frameLeaseHandle;
|
||||
lease->generation = this->frameGeneration;
|
||||
lease->providerValid = providerValid;
|
||||
lease->receiveTime = 0;
|
||||
lease->prepareTime = providerValid ? nanotime() - providerStart : 0;
|
||||
lease->releaseRequested = false;
|
||||
lease->active = true;
|
||||
result->releaseFn = lgmp_releaseFrameLease;
|
||||
@@ -1613,14 +2036,57 @@ static LG_TransportStatus lgmp_nextFrameLocked(LG_Transport * this,
|
||||
static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA,
|
||||
LG_TransportFrame * result)
|
||||
{
|
||||
lgmp_retainVideoStatusLifetime(this);
|
||||
LG_LOCK(this->frameLock);
|
||||
bool subscribed[LGMP_FRAME_LEASE_COUNT];
|
||||
subscribed[0] = this->frameQueue != NULL;
|
||||
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
||||
subscribed[i + 1] = this->ownerFrameQueue[i] != NULL;
|
||||
const LG_TransportStatus status =
|
||||
lgmp_nextFrameLocked(this, useDMA, result);
|
||||
bool available = this->frameQueue != NULL;
|
||||
bool replaced = subscribed[0] != available;
|
||||
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
||||
{
|
||||
const bool ownerAvailable = this->ownerFrameQueue[i] != NULL;
|
||||
available |= ownerAvailable;
|
||||
replaced |= subscribed[i + 1] != ownerAvailable;
|
||||
}
|
||||
if (status == LG_TRANSPORT_OK)
|
||||
{
|
||||
struct LGMPFrameLease * lease =
|
||||
lgmp_findFrameLeaseLocked(this, result->releaseHandle);
|
||||
DEBUG_ASSERT(lease);
|
||||
}
|
||||
if (status == LG_TRANSPORT_DISCONNECTED || status == LG_TRANSPORT_ERROR)
|
||||
available = false;
|
||||
LGMPVideoStatusDispatch dispatch;
|
||||
const LG_VideoStatus published = lgmp_updateVideoStatus(this,
|
||||
(LGMPVideoStatusUpdate)
|
||||
{
|
||||
.frame = true,
|
||||
.frameAvailable = available,
|
||||
.frameReplaced = replaced,
|
||||
.frameReason = status,
|
||||
}, &dispatch);
|
||||
if (status == LG_TRANSPORT_OK)
|
||||
result->epoch = published.frame.epoch;
|
||||
LG_UNLOCK(this->frameLock);
|
||||
if ((status == LG_TRANSPORT_TIMEOUT ||
|
||||
status == LG_TRANSPORT_UNAVAILABLE) && this->framePollInterval)
|
||||
lgmp_dispatchVideoStatus(this, &dispatch);
|
||||
LG_TransportStatus resultStatus = status;
|
||||
if (lgmp_destroyRequested(this))
|
||||
{
|
||||
if (status == LG_TRANSPORT_OK)
|
||||
lgmp_releaseFrame(this, result);
|
||||
resultStatus = LG_TRANSPORT_DISCONNECTED;
|
||||
}
|
||||
else if ((status == LG_TRANSPORT_TIMEOUT ||
|
||||
status == LG_TRANSPORT_UNAVAILABLE ||
|
||||
(status == LG_TRANSPORT_ERROR && !available)) &&
|
||||
this->framePollInterval)
|
||||
lgmp_waitPoll(this->frameWake, this->framePollInterval);
|
||||
return status;
|
||||
lgmp_releaseVideoStatusLifetime(this);
|
||||
return resultStatus;
|
||||
}
|
||||
|
||||
static void lgmp_cancelFrameWait(LG_Transport * this)
|
||||
@@ -1651,6 +2117,13 @@ static void lgmp_getFrameTiming(LG_Transport * this,
|
||||
return;
|
||||
}
|
||||
|
||||
timing->providerValid = lease->providerValid;
|
||||
if (lease->providerValid)
|
||||
{
|
||||
timing->receiveTime = lease->receiveTime;
|
||||
timing->prepareTime = lease->prepareTime;
|
||||
}
|
||||
|
||||
/* The producer writes these immediately after publishing FrameBuffer::wp.
|
||||
* nextFrame can observe the header earlier, so briefly observe the
|
||||
* publication tail after onFrame consumes the framebuffer without sleeping
|
||||
@@ -1719,8 +2192,17 @@ static void lgmp_releaseFrame(LG_Transport * this, LG_TransportFrame * frame)
|
||||
static LG_TransportStatus lgmp_nextPointer(LG_Transport * this,
|
||||
LG_TransportPointer * result)
|
||||
{
|
||||
if (!this->connected)
|
||||
lgmp_retainVideoStatusLifetime(this);
|
||||
if (!atomic_load_explicit(&this->connected, memory_order_acquire))
|
||||
{
|
||||
lgmp_publishVideoStatus(this, (LGMPVideoStatusUpdate)
|
||||
{
|
||||
.pointer = true,
|
||||
.pointerReason = LG_TRANSPORT_DISCONNECTED,
|
||||
});
|
||||
lgmp_releaseVideoStatusLifetime(this);
|
||||
return LG_TRANSPORT_DISCONNECTED;
|
||||
}
|
||||
|
||||
uint32_t pointerFlags = 0;
|
||||
size_t shapeSize = 0;
|
||||
@@ -1728,6 +2210,7 @@ static LG_TransportStatus lgmp_nextPointer(LG_Transport * this,
|
||||
LG_TransportStatus status = LG_TRANSPORT_OK;
|
||||
PLGMPClientQueue pointerQueue;
|
||||
LG_LOCK(this->pointerLock);
|
||||
const bool wasAvailable = this->pointerQueue != NULL;
|
||||
if (!this->pointerQueue)
|
||||
{
|
||||
status = lgmp_subscribe(this->client, LGMP_Q_POINTER,
|
||||
@@ -1798,10 +2281,58 @@ static LG_TransportStatus lgmp_nextPointer(LG_Transport * this,
|
||||
}
|
||||
}
|
||||
if (status != LG_TRANSPORT_OK)
|
||||
return status;
|
||||
{
|
||||
LGMPVideoStatusDispatch dispatch;
|
||||
LG_LOCK(this->pointerLock);
|
||||
bool available = atomic_load_explicit(
|
||||
&this->connected, memory_order_acquire) &&
|
||||
this->pointerQueue != NULL;
|
||||
if (status == LG_TRANSPORT_DISCONNECTED || status == LG_TRANSPORT_ERROR)
|
||||
available = false;
|
||||
lgmp_updateVideoStatus(this, (LGMPVideoStatusUpdate)
|
||||
{
|
||||
.pointer = true,
|
||||
.pointerAvailable = available,
|
||||
.pointerReplaced = !wasAvailable && available,
|
||||
.pointerReason = status,
|
||||
}, &dispatch);
|
||||
LG_UNLOCK(this->pointerLock);
|
||||
lgmp_dispatchVideoStatus(this, &dispatch);
|
||||
LG_TransportStatus resultStatus = status;
|
||||
if (lgmp_destroyRequested(this))
|
||||
resultStatus = LG_TRANSPORT_DISCONNECTED;
|
||||
else if (status == LG_TRANSPORT_ERROR && !available &&
|
||||
this->cursorPollInterval)
|
||||
lgmp_waitPoll(this->pointerWake, this->cursorPollInterval);
|
||||
lgmp_releaseVideoStatusLifetime(this);
|
||||
return resultStatus;
|
||||
}
|
||||
|
||||
LGMPVideoStatusDispatch dispatch;
|
||||
LG_LOCK(this->pointerLock);
|
||||
const bool current =
|
||||
atomic_load_explicit(&this->connected, memory_order_acquire) &&
|
||||
this->pointerQueue == pointerQueue;
|
||||
const LG_VideoStatus published = lgmp_updateVideoStatus(this,
|
||||
(LGMPVideoStatusUpdate)
|
||||
{
|
||||
.pointer = true,
|
||||
.pointerAvailable = current,
|
||||
.pointerReplaced = current && !wasAvailable,
|
||||
.pointerReason = current ? LG_TRANSPORT_OK :
|
||||
LG_TRANSPORT_DISCONNECTED,
|
||||
}, &dispatch);
|
||||
LG_UNLOCK(this->pointerLock);
|
||||
lgmp_dispatchVideoStatus(this, &dispatch);
|
||||
if (!current || lgmp_destroyRequested(this))
|
||||
{
|
||||
lgmp_releaseVideoStatusLifetime(this);
|
||||
return LG_TRANSPORT_DISCONNECTED;
|
||||
}
|
||||
|
||||
const KVMFRCursor * cursor = (const KVMFRCursor *)this->pointerData;
|
||||
memset(result, 0, sizeof(*result));
|
||||
result->epoch = published.pointer.epoch;
|
||||
if (pointerFlags & CURSOR_FLAG_POSITION)
|
||||
result->flags |= LG_TRANSPORT_POINTER_POSITION;
|
||||
if (pointerFlags & CURSOR_FLAG_VISIBLE)
|
||||
@@ -1825,6 +2356,7 @@ static LG_TransportStatus lgmp_nextPointer(LG_Transport * this,
|
||||
if (transformSize)
|
||||
result->colorTransform = (const LGColorTransform *)(result->shape +
|
||||
shapeSize);
|
||||
lgmp_releaseVideoStatusLifetime(this);
|
||||
return LG_TRANSPORT_OK;
|
||||
}
|
||||
|
||||
@@ -1933,7 +2465,8 @@ static const LG_InputOps * lgmp_getInputOps(LG_Transport * this,
|
||||
void ** opaque)
|
||||
{
|
||||
*opaque = NULL;
|
||||
if (!this->connected || !this->inputSupported ||
|
||||
if (!atomic_load_explicit(&this->connected, memory_order_acquire) ||
|
||||
!atomic_load_explicit(&this->inputSupported, memory_order_acquire) ||
|
||||
!lgmpInput_connect(this->input, this->clientID))
|
||||
return NULL;
|
||||
|
||||
@@ -1941,8 +2474,111 @@ static const LG_InputOps * lgmp_getInputOps(LG_Transport * this,
|
||||
return lgmpInput_getOps();
|
||||
}
|
||||
|
||||
static void lgmp_probeVideoStatus(LG_Transport * this)
|
||||
{
|
||||
bool frameAvailable = false;
|
||||
bool pointerAvailable = false;
|
||||
LGMPVideoStatusDispatch frameDispatch;
|
||||
LGMPVideoStatusDispatch pointerDispatch;
|
||||
|
||||
LG_LOCK(this->frameLock);
|
||||
const bool frameConnected = atomic_load_explicit(
|
||||
&this->connected, memory_order_acquire);
|
||||
if (frameConnected)
|
||||
{
|
||||
lgmp_subscribe(this->client, LGMP_Q_FRAME, &this->frameQueue);
|
||||
frameAvailable = this->frameQueue != NULL;
|
||||
if (this->frameScheduleSupported)
|
||||
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
|
||||
{
|
||||
lgmp_subscribe(this->client, LGMP_Q_FRAME_OWNER + i,
|
||||
&this->ownerFrameQueue[i]);
|
||||
frameAvailable |= this->ownerFrameQueue[i] != NULL;
|
||||
}
|
||||
}
|
||||
lgmp_updateVideoStatus(this, (LGMPVideoStatusUpdate)
|
||||
{
|
||||
.frame = true,
|
||||
.frameAvailable = frameAvailable,
|
||||
.frameReason = frameConnected ? LG_TRANSPORT_UNAVAILABLE :
|
||||
LG_TRANSPORT_DISCONNECTED,
|
||||
}, &frameDispatch);
|
||||
LG_UNLOCK(this->frameLock);
|
||||
lgmp_dispatchVideoStatus(this, &frameDispatch);
|
||||
|
||||
LG_LOCK(this->pointerLock);
|
||||
const bool pointerConnected = atomic_load_explicit(
|
||||
&this->connected, memory_order_acquire);
|
||||
if (pointerConnected)
|
||||
{
|
||||
lgmp_subscribe(this->client, LGMP_Q_POINTER, &this->pointerQueue);
|
||||
pointerAvailable = this->pointerQueue != NULL;
|
||||
}
|
||||
lgmp_updateVideoStatus(this, (LGMPVideoStatusUpdate)
|
||||
{
|
||||
.pointer = true,
|
||||
.pointerAvailable = pointerAvailable,
|
||||
.pointerReason = pointerConnected ? LG_TRANSPORT_UNAVAILABLE :
|
||||
LG_TRANSPORT_DISCONNECTED,
|
||||
}, &pointerDispatch);
|
||||
LG_UNLOCK(this->pointerLock);
|
||||
lgmp_dispatchVideoStatus(this, &pointerDispatch);
|
||||
}
|
||||
|
||||
static void lgmp_setVideoStatusListener(LG_Transport * this,
|
||||
LG_VideoStatusFn callback, void * callbackOpaque)
|
||||
{
|
||||
LG_VideoStatus status;
|
||||
const bool nested = l_videoStatusContext != NULL;
|
||||
const unsigned ownCallbacks =
|
||||
lgmp_videoStatusOwnCallbacks(this);
|
||||
|
||||
if (!nested)
|
||||
LG_LOCK(l_videoStatusCallbackLock);
|
||||
LG_LOCK_EXCLUSIVE(this->videoStatusLock);
|
||||
++this->videoStatusListenerSerial;
|
||||
this->videoStatusCallback = NULL;
|
||||
this->videoStatusOpaque = NULL;
|
||||
LG_UNLOCK_EXCLUSIVE(this->videoStatusLock);
|
||||
|
||||
if (callback)
|
||||
lgmp_probeVideoStatus(this);
|
||||
|
||||
LG_LOCK_EXCLUSIVE(this->videoStatusLock);
|
||||
this->videoStatusCallback = callback;
|
||||
this->videoStatusOpaque = callbackOpaque;
|
||||
status = lgmp_videoStatusLocked(this);
|
||||
LG_UNLOCK_EXCLUSIVE(this->videoStatusLock);
|
||||
|
||||
if (callback)
|
||||
{
|
||||
atomic_fetch_add_explicit(
|
||||
&this->videoStatusCallbacks, 1, memory_order_acq_rel);
|
||||
lgmp_callVideoStatusCallback(
|
||||
callback, callbackOpaque, this, &status);
|
||||
|
||||
/* Releasing this callback's lifetime reference is the final access to the
|
||||
* instance unless this thread becomes the deferred destroy owner. */
|
||||
const bool destroy = lgmp_releaseVideoStatusCallback(this);
|
||||
if (!nested)
|
||||
LG_UNLOCK(l_videoStatusCallbackLock);
|
||||
if (destroy)
|
||||
lgmp_destroyNow(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nested)
|
||||
return;
|
||||
|
||||
/* Publications accepted before unregistration retain lifetime references,
|
||||
* but will skip the cleared listener when they acquire serialization. */
|
||||
LG_UNLOCK(l_videoStatusCallbackLock);
|
||||
lgmp_waitVideoStatusCallbacks(this, ownCallbacks);
|
||||
}
|
||||
|
||||
static const LG_FrameOps lgmpFrameOps =
|
||||
{
|
||||
.setStatusListener = lgmp_setVideoStatusListener,
|
||||
.supportsDMA = lgmp_supportsDMA,
|
||||
.attachRenderer = lgmp_attachRenderer,
|
||||
.detachRenderer = lgmp_detachRenderer,
|
||||
@@ -1975,8 +2611,9 @@ const LG_TransportOps LGT_LGMP =
|
||||
.setup = lgmp_setup,
|
||||
.create = lgmp_create,
|
||||
.destroy = lgmp_destroy,
|
||||
.connect = lgmp_connect,
|
||||
.disconnect = lgmp_disconnect,
|
||||
.connect = lgmp_connect,
|
||||
.connectCancellable = lgmp_connectCancellable,
|
||||
.disconnect = lgmp_disconnect,
|
||||
.sessionValid = lgmp_sessionValid,
|
||||
.getVideoOps = lgmp_getVideoOps,
|
||||
.getInputOps = lgmp_getInputOps,
|
||||
|
||||
Reference in New Issue
Block a user