[client] transport: track video component availability

This commit is contained in:
Geoffrey McRae
2026-08-12 22:40:07 +10:00
parent c92acb0a34
commit 4768396180
28 changed files with 3965 additions and 284 deletions

View File

@@ -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,

View File

@@ -237,6 +237,7 @@ static LG_TransportStatus spiceConnectCancellable(LG_Transport * transport,
atomic_store_explicit(
&transport->sessionValid, false, memory_order_release);
lgResetEvent(transport->connectEvent);
purespice_beginConnect();
if (!lgCreateThread(
"spiceProcess", spiceSession_thread, transport, &transport->thread))
@@ -248,6 +249,7 @@ static LG_TransportStatus spiceConnectCancellable(LG_Transport * transport,
{
cancel = true;
atomic_store_explicit(&transport->stop, true, memory_order_release);
purespice_cancelConnect();
lgWaitEvent(transport->connectEvent, TIMEOUT_INFINITE);
break;
}
@@ -255,6 +257,7 @@ static LG_TransportStatus spiceConnectCancellable(LG_Transport * transport,
if (cancel || (cancelled && cancelled(opaque)))
{
atomic_store_explicit(&transport->stop, true, memory_order_release);
purespice_cancelConnect();
lgJoinThread(transport->thread, NULL);
transport->thread = NULL;
return LG_TRANSPORT_DISCONNECTED;
@@ -283,6 +286,7 @@ static void spiceDisconnect(LG_Transport * transport)
return;
atomic_store_explicit(&transport->stop, true, memory_order_release);
purespice_cancelConnect();
lgJoinThread(transport->thread, NULL);
transport->thread = NULL;
}

View File

@@ -32,6 +32,7 @@ struct SpiceSurface
{
LG_RWLock eventsLock;
LG_Lock activationLock;
atomic_uint_fast64_t activationCancel;
const LG_SwSurfaceEventOps * events;
void * eventOpaque;
@@ -68,8 +69,17 @@ static void surfaceDetach(LG_Transport * transport)
static bool surfaceSetActive(LG_Transport * transport, bool active)
{
SpiceSurface * surface = transport->surface;
const uint64_t cancel = atomic_load_explicit(
&surface->activationCancel, memory_order_acquire);
LG_LOCK(surface->activationLock);
if (cancel != atomic_load_explicit(
&surface->activationCancel, memory_order_acquire))
{
LG_UNLOCK(surface->activationLock);
return false;
}
if (surface->active == active)
{
LG_UNLOCK(surface->activationLock);
@@ -88,8 +98,15 @@ static bool surfaceSetActive(LG_Transport * transport, bool active)
if (!atomic_load_explicit(
&transport->sessionValid, memory_order_acquire) ||
!purespice_hasChannel(PS_CHANNEL_DISPLAY) ||
!purespice_hasChannel(PS_CHANNEL_CURSOR) ||
!purespice_connectChannel(PS_CHANNEL_DISPLAY))
!purespice_hasChannel(PS_CHANNEL_CURSOR))
goto done;
purespice_beginChannelConnect(PS_CHANNEL_DISPLAY);
purespice_beginChannelConnect(PS_CHANNEL_CURSOR);
if (cancel != atomic_load_explicit(
&surface->activationCancel, memory_order_acquire))
goto done;
if (!purespice_connectChannel(PS_CHANNEL_DISPLAY))
goto done;
if (!purespice_connectChannel(PS_CHANNEL_CURSOR))
@@ -105,11 +122,23 @@ static bool surfaceSetActive(LG_Transport * transport, bool active)
if (!purespice_disconnectChannel(PS_CHANNEL_CURSOR))
{
purespice_beginChannelConnect(PS_CHANNEL_DISPLAY);
if (cancel != atomic_load_explicit(
&surface->activationCancel, memory_order_acquire))
goto done;
purespice_connectChannel(PS_CHANNEL_DISPLAY);
goto done;
}
}
if (active && cancel != atomic_load_explicit(
&surface->activationCancel, memory_order_acquire))
{
purespice_disconnectChannel(PS_CHANNEL_CURSOR);
purespice_disconnectChannel(PS_CHANNEL_DISPLAY);
goto done;
}
surface->active = active;
result = true;
@@ -118,11 +147,25 @@ done:
return result;
}
static void surfaceCancelActivation(SpiceSurface * surface)
{
atomic_fetch_add_explicit(&surface->activationCancel,
1, memory_order_acq_rel);
purespice_cancelChannelConnect(PS_CHANNEL_DISPLAY);
purespice_cancelChannelConnect(PS_CHANNEL_CURSOR);
}
static void surfaceCancelPending(LG_Transport * transport)
{
surfaceCancelActivation(transport->surface);
}
static const LG_SwSurfaceOps swSurfaceOps =
{
.attach = surfaceAttach,
.detach = surfaceDetach,
.setActive = surfaceSetActive,
.attach = surfaceAttach,
.detach = surfaceDetach,
.setActive = surfaceSetActive,
.cancelPending = surfaceCancelPending,
};
static const LG_VideoOps videoOps =
@@ -143,6 +186,7 @@ bool spiceSurface_init(SpiceSurface ** result)
LG_RWLOCK_INIT(surface->eventsLock);
LG_LOCK_INIT(surface->activationLock);
atomic_init(&surface->activationCancel, 0);
*result = surface;
return true;
}
@@ -165,12 +209,25 @@ const LG_VideoOps * spiceSurface_getVideoOps(void)
void spiceSurface_sessionStopped(SpiceSurface * surface)
{
surfaceCancelActivation(surface);
LG_LOCK(surface->activationLock);
surface->active = false;
surface->primaryValid = false;
LG_UNLOCK(surface->activationLock);
}
#ifdef ENABLE_TESTS
void spiceSurface_testLockActivation(SpiceSurface * surface)
{
LG_LOCK(surface->activationLock);
}
void spiceSurface_testUnlockActivation(SpiceSurface * surface)
{
LG_UNLOCK(surface->activationLock);
}
#endif
void spiceSurface_create(SpiceSurface * surface, unsigned int surfaceId,
PSSurfaceFormat format, unsigned int width, unsigned int height)
{

View File

@@ -33,6 +33,11 @@ void spiceSurface_free(SpiceSurface ** surface);
const LG_VideoOps * spiceSurface_getVideoOps(void);
void spiceSurface_sessionStopped(SpiceSurface * surface);
#ifdef ENABLE_TESTS
void spiceSurface_testLockActivation(SpiceSurface * surface);
void spiceSurface_testUnlockActivation(SpiceSurface * surface);
#endif
void spiceSurface_create(SpiceSurface * surface, unsigned int surfaceId,
PSSurfaceFormat format, unsigned int width, unsigned int height);
void spiceSurface_destroy(SpiceSurface * surface, unsigned int surfaceId);

View File

@@ -86,6 +86,7 @@ struct LG_Transport
bool framePending;
uint64_t serial;
uint64_t nextFrameTime;
uint64_t framePrepareTime;
unsigned bufferIndex;
struct TestBuffer buffers[TEST_BUFFER_COUNT];
FrameDamageRect damage[LG_TRANSPORT_MAX_DAMAGE_RECTS];
@@ -416,6 +417,23 @@ static LG_TransportStatus test_connect(LG_Transport * this,
return LG_TRANSPORT_OK;
}
static void test_disconnect(LG_Transport * this);
static LG_TransportStatus test_connectCancellable(LG_Transport * this,
LG_TransportSession * session, LG_TransportCancelledFn cancelled,
void * opaque)
{
if (cancelled && cancelled(opaque))
return LG_TRANSPORT_DISCONNECTED;
const LG_TransportStatus status = test_connect(this, session);
if (!cancelled || !cancelled(opaque))
return status;
test_disconnect(this);
return LG_TRANSPORT_DISCONNECTED;
}
static void test_disconnect(LG_Transport * this)
{
this->connected = false;
@@ -581,6 +599,7 @@ static void test_generateFrame(struct LG_Transport * this, FrameBuffer * fb)
static LG_TransportStatus test_nextFrame(LG_Transport * this, bool useDMA,
LG_TransportFrame * frame)
{
const uint64_t prepareStart = nanotime();
if (!this->connected)
return LG_TRANSPORT_DISCONNECTED;
if (this->framePending)
@@ -614,6 +633,7 @@ static LG_TransportStatus test_nextFrame(LG_Transport * this, bool useDMA,
memset(frame, 0, sizeof(*frame));
frame->serial = this->serial;
frame->epoch = 1;
frame->timestamp = this->realtime ? nanotime() :
this->serial * (1000000000ULL / this->frameRate);
frame->format = &this->format;
@@ -688,16 +708,33 @@ static LG_TransportStatus test_nextFrame(LG_Transport * this, bool useDMA,
}
this->framePending = true;
this->framePrepareTime = nanotime() - prepareStart;
this->nextFrameTime += 1000000000ULL / this->frameRate;
return LG_TRANSPORT_OK;
}
static void test_getFrameTiming(LG_Transport * this,
const LG_TransportFrame * frame, LG_TransportFrameTiming * timing)
{
memset(timing, 0, sizeof(*timing));
if (!this->framePending || frame->serial != this->serial)
return;
timing->providerValid = true;
timing->prepareTime = this->framePrepareTime;
}
static void test_releaseFrame(LG_Transport * this, LG_TransportFrame * frame)
{
this->framePending = false;
memset(frame, 0, sizeof(*frame));
}
static void test_cancelFrameWait(LG_Transport * this)
{
(void)this;
}
static LG_TransportStatus test_nextPointer(LG_Transport * this,
LG_TransportPointer * pointer)
{
@@ -712,6 +749,11 @@ static void test_releasePointer(LG_Transport * this,
{
}
static void test_cancelPointerWait(LG_Transport * this)
{
(void)this;
}
static LG_TransportStatus test_sendControl(LG_Transport * this,
const LG_TransportControl * control, LG_TransportControlToken * token)
{
@@ -740,9 +782,12 @@ static const LG_FrameOps testFrameOps =
.attachRenderer = test_attachRenderer,
.detachRenderer = test_detachRenderer,
.nextFrame = test_nextFrame,
.getFrameTiming = test_getFrameTiming,
.releaseFrame = test_releaseFrame,
.cancelFrameWait = test_cancelFrameWait,
.nextPointer = test_nextPointer,
.releasePointer = test_releasePointer,
.cancelPointerWait = test_cancelPointerWait,
};
static const LG_VideoOps testVideoOps =
@@ -764,6 +809,7 @@ const LG_TransportOps LGT_Test =
.create = test_create,
.destroy = test_destroy,
.connect = test_connect,
.connectCancellable = test_connectCancellable,
.disconnect = test_disconnect,
.sessionValid = test_sessionValid,
.getVideoOps = test_getVideoOps,