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:
@@ -188,6 +188,7 @@ set(SOURCES
|
||||
src/evdev.c
|
||||
src/transport.c
|
||||
src/transport_fallback.c
|
||||
src/sw_surface.c
|
||||
src/input.c
|
||||
|
||||
src/overlay/splash.c
|
||||
|
||||
@@ -113,6 +113,8 @@ typedef enum LG_RecoveryError
|
||||
LG_RECOVERY_ERR_HELPER_UNAVAILABLE,
|
||||
LG_RECOVERY_ERR_TOPOLOGY_FAILED,
|
||||
LG_RECOVERY_ERR_NO_FALLBACK_DISPLAY,
|
||||
LG_RECOVERY_ERR_BUSY,
|
||||
LG_RECOVERY_ERR_CAPACITY,
|
||||
}
|
||||
LG_RecoveryError;
|
||||
|
||||
@@ -175,7 +177,10 @@ typedef uint32_t LG_TransportFrameFlags;
|
||||
|
||||
typedef struct LG_TransportFrameTiming
|
||||
{
|
||||
bool valid; /* producer fields below are available and coherent */
|
||||
/* The producer fields through readyLeadTime are available and coherent. */
|
||||
bool valid;
|
||||
/* receiveTime and prepareTime are available and coherent. */
|
||||
bool providerValid;
|
||||
bool phaseValid;
|
||||
uint32_t scheduleGeneration;
|
||||
uint32_t scheduleEpoch;
|
||||
@@ -186,6 +191,10 @@ typedef struct LG_TransportFrameTiming
|
||||
uint64_t readyTime;
|
||||
uint64_t holdTime;
|
||||
uint64_t readyLeadTime;
|
||||
/* Non-overlapping work performed while acquiring and preparing the returned
|
||||
* payload. These do not include status callbacks or renderer import time. */
|
||||
uint64_t receiveTime;
|
||||
uint64_t prepareTime;
|
||||
}
|
||||
LG_TransportFrameTiming;
|
||||
|
||||
@@ -219,6 +228,9 @@ LG_TransportFrameFormat;
|
||||
typedef struct LG_TransportFrame
|
||||
{
|
||||
uint64_t serial;
|
||||
/* When setStatusListener is present, matches LG_VideoComponentStatus::epoch
|
||||
* for the frame endpoint which returned this payload; otherwise may be 0. */
|
||||
uint64_t epoch;
|
||||
uint64_t timestamp;
|
||||
uint32_t scheduleEpoch;
|
||||
uint32_t scheduleDeadlineSerial;
|
||||
@@ -253,6 +265,10 @@ typedef uint32_t LG_TransportPointerFlags;
|
||||
|
||||
typedef struct LG_TransportPointer
|
||||
{
|
||||
/* When setStatusListener is present, matches LG_VideoComponentStatus::epoch
|
||||
* for the pointer endpoint which returned this payload; otherwise may be 0.
|
||||
*/
|
||||
uint64_t epoch;
|
||||
LG_TransportPointerFlags flags;
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
@@ -268,6 +284,27 @@ typedef struct LG_TransportPointer
|
||||
}
|
||||
LG_TransportPointer;
|
||||
|
||||
typedef struct LG_VideoComponentStatus
|
||||
{
|
||||
bool available;
|
||||
/* Nonzero while available and changed whenever the endpoint is replaced or
|
||||
* changes availability. */
|
||||
uint64_t epoch;
|
||||
/* LG_TRANSPORT_OK while available; otherwise the latest observed cause. */
|
||||
LG_TransportStatus reason;
|
||||
}
|
||||
LG_VideoComponentStatus;
|
||||
|
||||
typedef struct LG_VideoStatus
|
||||
{
|
||||
LG_VideoComponentStatus frame;
|
||||
LG_VideoComponentStatus pointer;
|
||||
}
|
||||
LG_VideoStatus;
|
||||
|
||||
typedef void (*LG_VideoStatusFn)(void * opaque,
|
||||
const LG_VideoStatus * status);
|
||||
|
||||
typedef enum LG_TransportControlType
|
||||
{
|
||||
LG_TRANSPORT_CONTROL_SET_CURSOR_POS,
|
||||
@@ -314,6 +351,16 @@ typedef uint64_t LG_TransportControlToken;
|
||||
|
||||
typedef struct LG_FrameOps
|
||||
{
|
||||
/* Registration is scoped to a connected session and must synchronously
|
||||
* report the current status after releasing component state locks. Frame and
|
||||
* pointer epochs advance independently whenever their endpoint is
|
||||
* replaced or changes availability. Passing NULL unregisters the listener
|
||||
* and synchronously quiesces its callbacks. Callbacks must be serialized.
|
||||
* Providers without a listener are assumed to keep every component they
|
||||
* expose available for the session. */
|
||||
void (*setStatusListener)(LG_Transport * transport,
|
||||
LG_VideoStatusFn callback, void * callbackOpaque);
|
||||
|
||||
bool (*supportsDMA)(LG_Transport * transport);
|
||||
bool (*attachRenderer)(LG_Transport * transport,
|
||||
const LG_RendererInterop * interop);
|
||||
@@ -327,7 +374,7 @@ typedef struct LG_FrameOps
|
||||
void (*getFrameTiming)(LG_Transport * transport,
|
||||
const LG_TransportFrame * frame, LG_TransportFrameTiming * timing);
|
||||
void (*releaseFrame)(LG_Transport * transport, LG_TransportFrame * frame);
|
||||
/* Optional, thread-safe cancellation of a blocking nextFrame call. This
|
||||
/* Required, thread-safe cancellation of a blocking nextFrame call. This
|
||||
* does not release a frame already returned to the consumer. */
|
||||
void (*cancelFrameWait)(LG_Transport * transport);
|
||||
/* Called by the frame consumer as it exits. A backend may release transient
|
||||
@@ -335,11 +382,14 @@ typedef struct LG_FrameOps
|
||||
* restarts. */
|
||||
void (*stopFrame)(LG_Transport * transport);
|
||||
|
||||
/* Pointer operations are optional. When absent, pointer.available must be
|
||||
* false for providers implementing the status listener. */
|
||||
LG_TransportStatus (*nextPointer)(LG_Transport * transport,
|
||||
LG_TransportPointer * pointer);
|
||||
void (*releasePointer)(LG_Transport * transport,
|
||||
LG_TransportPointer * pointer);
|
||||
/* Optional, thread-safe cancellation of a blocking nextPointer call. */
|
||||
/* Required whenever nextPointer is present; thread-safe cancellation of a
|
||||
* blocking nextPointer call. */
|
||||
void (*cancelPointerWait)(LG_Transport * transport);
|
||||
/* Called by the pointer consumer as it exits. A backend may release
|
||||
* transient stream resources; nextPointer must reacquire them when the
|
||||
@@ -368,7 +418,12 @@ typedef struct LG_SwSurfaceOps
|
||||
bool (*attach)(LG_Transport * transport,
|
||||
const LG_SwSurfaceEventOps * events, void * opaque);
|
||||
void (*detach)(LG_Transport * transport);
|
||||
/* Activation may wait while the requested component becomes available.
|
||||
* Deactivation must complete promptly without cancellation. */
|
||||
bool (*setActive)(LG_Transport * transport, bool active);
|
||||
/* Required. Interrupts a pending setActive call and causes it to return
|
||||
* promptly. Safe to invoke repeatedly from another thread. */
|
||||
void (*cancelPending)(LG_Transport * transport);
|
||||
}
|
||||
LG_SwSurfaceOps;
|
||||
|
||||
@@ -400,8 +455,8 @@ typedef struct LG_TransportOps
|
||||
|
||||
LG_TransportStatus (*connect)(LG_Transport * transport,
|
||||
LG_TransportSession * session);
|
||||
/* Optional cancellable form used by transports which may wait while
|
||||
* establishing a session. */
|
||||
/* Required session entry point. The callback must be observed promptly
|
||||
* while establishing a session, and cancellation must bound this call. */
|
||||
LG_TransportStatus (*connectCancellable)(LG_Transport * transport,
|
||||
LG_TransportSession * session, LG_TransportCancelledFn cancelled,
|
||||
void * opaque);
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "render_queue.h"
|
||||
#include "evdev.h"
|
||||
#include "input.h"
|
||||
#include "sw_surface.h"
|
||||
|
||||
#include "kb.h"
|
||||
|
||||
@@ -59,6 +60,13 @@
|
||||
|
||||
extern _Atomic(enum RunState) p_appState;
|
||||
|
||||
static bool swSurfaceActivationCancelled(void * opaque)
|
||||
{
|
||||
(void)opaque;
|
||||
return app_getState() != APP_STATE_RUNNING ||
|
||||
atomic_load_explicit(&g_state.stopVideo, memory_order_acquire);
|
||||
}
|
||||
|
||||
enum RunState app_getState(void)
|
||||
{
|
||||
return atomic_load_explicit(&p_appState, memory_order_acquire);
|
||||
@@ -670,6 +678,12 @@ void app_handleRenderEvent(const uint64_t timeUs)
|
||||
app_invalidateWindow(false);
|
||||
}
|
||||
|
||||
void app_handleFramePresented(uint64_t frameToken, uint64_t presentTime,
|
||||
bool valid)
|
||||
{
|
||||
main_framePresented(frameToken, presentTime, valid);
|
||||
}
|
||||
|
||||
void app_setFullscreen(bool fs)
|
||||
{
|
||||
g_state.ds->setFullscreen(fs);
|
||||
@@ -1203,11 +1217,19 @@ void app_stopVideo(bool stop)
|
||||
|
||||
if (stop)
|
||||
{
|
||||
if (g_state.fallback)
|
||||
lgTransportFallback_requestVideoActive(g_state.fallback, false);
|
||||
|
||||
if (g_state.videoOps->type == LG_VIDEO_TYPE_FRAME)
|
||||
{
|
||||
core_stopVideoThreads();
|
||||
/* A status callback may have requested fallback before it quiesced. */
|
||||
if (g_state.fallback)
|
||||
lgTransportFallback_requestVideoActive(g_state.fallback, false);
|
||||
}
|
||||
else
|
||||
g_state.videoOps->swSurface->setActive(
|
||||
g_state.transport.handle, false);
|
||||
lgSwSurface_setActive(g_state.videoOps->swSurface,
|
||||
g_state.transport.handle, false, NULL, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1217,14 +1239,19 @@ void app_stopVideo(bool stop)
|
||||
{
|
||||
atomic_store_explicit(
|
||||
&g_state.stopVideo, true, memory_order_release);
|
||||
if (g_state.fallback)
|
||||
lgTransportFallback_requestVideoActive(g_state.fallback, false);
|
||||
app_alert(LG_ALERT_ERROR, "Failed to enable the video stream");
|
||||
}
|
||||
else
|
||||
main_videoStreamEnabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
g_state.videoOps->swSurface->setActive(
|
||||
g_state.transport.handle, true);
|
||||
app_useVideoSource(LG_VIDEO_SOURCE_PRIMARY);
|
||||
if (lgSwSurface_setActive(g_state.videoOps->swSurface,
|
||||
g_state.transport.handle, true,
|
||||
swSurfaceActivationCancelled, NULL))
|
||||
app_useVideoSource(LG_VIDEO_SOURCE_PRIMARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,9 +627,9 @@ static void cancelVideoWaits(void)
|
||||
return;
|
||||
|
||||
const LG_FrameOps * ops = g_state.videoOps->frame;
|
||||
if (g_state.frameThread && ops->cancelFrameWait)
|
||||
if (g_state.frameThread)
|
||||
ops->cancelFrameWait(g_state.transport.handle);
|
||||
if (g_state.cursorThread && ops->cancelPointerWait)
|
||||
if (g_state.cursorThread)
|
||||
ops->cancelPointerWait(g_state.transport.handle);
|
||||
}
|
||||
|
||||
@@ -650,24 +650,26 @@ void core_stopVideoThreads(void)
|
||||
|
||||
bool core_startVideoThreads(void)
|
||||
{
|
||||
if (g_state.frameThread && g_state.cursorThread)
|
||||
const LG_FrameOps * ops = g_state.videoOps->frame;
|
||||
const bool pointer = ops->nextPointer && ops->releasePointer;
|
||||
if (g_state.frameThread && (!pointer || g_state.cursorThread))
|
||||
return true;
|
||||
if (g_state.frameThread || g_state.cursorThread)
|
||||
core_stopVideoThreads();
|
||||
|
||||
atomic_store_explicit(
|
||||
&g_state.stopVideoThreads, false, memory_order_release);
|
||||
if (!lgCreateThread("cursorThread", main_cursorThread, NULL,
|
||||
&g_state.cursorThread))
|
||||
{
|
||||
DEBUG_ERROR("cursor create thread failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lgCreateThread("frameThread", main_frameThread, NULL,
|
||||
&g_state.frameThread))
|
||||
{
|
||||
DEBUG_ERROR("frame create thread failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pointer && !lgCreateThread("cursorThread", main_cursorThread, NULL,
|
||||
&g_state.cursorThread))
|
||||
{
|
||||
DEBUG_ERROR("cursor create thread failed");
|
||||
core_stopVideoThreads();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
#include "evdev.h"
|
||||
#include "frame_scheduler.h"
|
||||
#include "input.h"
|
||||
#include "sw_surface.h"
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
#include "interface/test_capture.h"
|
||||
@@ -85,6 +86,7 @@ _Static_assert((int)LG_CAPTURE_RGBA32F == (int)LG_TEST_CAPTURE_RGBA32F,
|
||||
#define TRANSPORT_LOST_FALLBACK (1U << 1)
|
||||
#define TRANSPORT_LOST_ALL \
|
||||
(TRANSPORT_LOST_PRIMARY | TRANSPORT_LOST_FALLBACK)
|
||||
#define VIDEO_STATUS_ERROR_RETRY_NS 1000000U
|
||||
|
||||
// forwards
|
||||
static int renderThread(void * unused);
|
||||
@@ -93,6 +95,17 @@ static bool videoSourceInvalidate(LG_VideoSource source);
|
||||
static void videoSourceShowSplashIfNeeded(void);
|
||||
static bool fallbackRequestVideo(void);
|
||||
static void primaryLost(void);
|
||||
static bool videoComponentPayloadCurrent(const atomic_bool * available,
|
||||
const atomic_uint_least64_t * epoch, uint64_t payloadEpoch,
|
||||
bool trackEpoch);
|
||||
static bool videoPayloadAccept(const atomic_bool * available,
|
||||
const atomic_uint_least64_t * epoch, uint64_t payloadEpoch,
|
||||
bool trackEpoch, uint64_t sourceGeneration);
|
||||
static void videoPayloadRelease(void);
|
||||
static bool videoFramePayloadAccept(const LG_TransportFrame * frame,
|
||||
uint64_t currentGeneration, bool trackEpoch,
|
||||
uint64_t * sourceGeneration, uint64_t * frameSerial,
|
||||
uint32_t * formatVersion, bool * sourceChanged);
|
||||
|
||||
static LGEvent *e_startup = NULL;
|
||||
static LGEvent *e_cursorRepaint = NULL;
|
||||
@@ -119,6 +132,20 @@ struct CursorState g_cursor;
|
||||
// this structure is initialized in config.c
|
||||
struct AppParams g_params = { 0 };
|
||||
|
||||
static bool videoStatusRetry(LG_TransportStatus status,
|
||||
bool statusAware, bool available)
|
||||
{
|
||||
if (status == LG_TRANSPORT_TIMEOUT ||
|
||||
status == LG_TRANSPORT_UNAVAILABLE)
|
||||
return true;
|
||||
|
||||
if (status != LG_TRANSPORT_ERROR || !statusAware || available)
|
||||
return false;
|
||||
|
||||
nsleep(VIDEO_STATUS_ERROR_RETRY_NS);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
static struct
|
||||
{
|
||||
@@ -243,6 +270,7 @@ struct FrameTimingRecord
|
||||
uint32_t scheduleDeadlineSerial;
|
||||
unsigned readyMask;
|
||||
bool producerValid;
|
||||
bool providerValid;
|
||||
bool phaseValid;
|
||||
bool transportValid;
|
||||
bool presentValid;
|
||||
@@ -253,6 +281,8 @@ struct FrameTimingRecord
|
||||
uint64_t readyTime;
|
||||
uint64_t holdTime;
|
||||
uint64_t readyLeadTime;
|
||||
uint64_t receiveTime;
|
||||
uint64_t providerPrepareTime;
|
||||
uint64_t importTime;
|
||||
uint64_t importWaitTime;
|
||||
uint64_t dispatchTime;
|
||||
@@ -319,6 +349,11 @@ static void frameTimingReset(void)
|
||||
LG_RENDERER_FRAME_TOKEN_NONE, memory_order_release);
|
||||
}
|
||||
|
||||
static uint64_t frameTimingAdd(uint64_t lhs, uint64_t rhs)
|
||||
{
|
||||
return rhs > UINT64_MAX - lhs ? UINT64_MAX : lhs + rhs;
|
||||
}
|
||||
|
||||
static struct FrameTimingRecord * frameTimingRecord(
|
||||
LG_RendererFrameToken token)
|
||||
{
|
||||
@@ -326,7 +361,7 @@ static struct FrameTimingRecord * frameTimingRecord(
|
||||
(token - 1) % FRAME_TIMING_RECORD_COUNT];
|
||||
}
|
||||
|
||||
void app_handleFramePresented(uint64_t frameToken, uint64_t presentTime,
|
||||
void main_framePresented(uint64_t frameToken, uint64_t presentTime,
|
||||
bool valid)
|
||||
{
|
||||
if (!frameToken)
|
||||
@@ -386,7 +421,8 @@ static void frameTimingQueue(LG_RendererFrameToken token, uint64_t frameSerial,
|
||||
{
|
||||
const uint64_t elapsed = queueStart > dispatchStart ?
|
||||
queueStart - dispatchStart : 0;
|
||||
const uint64_t accounted = importTime + importWaitTime;
|
||||
const uint64_t accounted =
|
||||
frameTimingAdd(importTime, importWaitTime);
|
||||
record->importTime = importTime;
|
||||
record->importWaitTime = importWaitTime;
|
||||
record->dispatchTime = elapsed > accounted ?
|
||||
@@ -433,6 +469,9 @@ static void frameTimingFinishFrame(LG_RendererFrameToken token,
|
||||
record->readyTime = timing->readyTime;
|
||||
record->holdTime = timing->holdTime;
|
||||
record->readyLeadTime = timing->readyLeadTime;
|
||||
record->providerValid = timing->providerValid;
|
||||
record->receiveTime = timing->receiveTime;
|
||||
record->providerPrepareTime = timing->prepareTime;
|
||||
if (record->timestamp < timestamp)
|
||||
record->timestamp = timestamp;
|
||||
record->readyMask |= FRAME_TIMING_FRAME_READY;
|
||||
@@ -569,8 +608,13 @@ static void frameTimingPublishReady(void)
|
||||
const uint64_t queueTime =
|
||||
record->prepareStart > record->queueStart ?
|
||||
record->prepareStart - record->queueStart : 0;
|
||||
const uint64_t providerAccounted =
|
||||
record->providerValid ?
|
||||
frameTimingAdd(
|
||||
record->receiveTime, record->providerPrepareTime) : 0;
|
||||
const uint64_t transportAccounted =
|
||||
record->importTime + record->dispatchTime + queueTime;
|
||||
frameTimingAdd(frameTimingAdd(frameTimingAdd(providerAccounted,
|
||||
record->importTime), record->dispatchTime), queueTime);
|
||||
const uint64_t transportTime =
|
||||
record->readyLeadTime > transportAccounted ?
|
||||
record->readyLeadTime - transportAccounted : 0;
|
||||
@@ -580,6 +624,8 @@ static void frameTimingPublishReady(void)
|
||||
validMask &= ~OVERLAY_FRAME_TIMING_VALID_PRODUCER;
|
||||
if (!record->producerValid || !record->transportValid)
|
||||
validMask &= ~OVERLAY_FRAME_TIMING_VALID_TRANSPORT;
|
||||
if (!record->providerValid)
|
||||
validMask &= ~OVERLAY_FRAME_TIMING_VALID_PROVIDER;
|
||||
if (!record->presentValid)
|
||||
validMask &= ~OVERLAY_FRAME_TIMING_VALID_PRESENT;
|
||||
|
||||
@@ -592,8 +638,10 @@ static void frameTimingPublishReady(void)
|
||||
.ready = record->readyTime * 1e-6f,
|
||||
.hold = record->holdTime * 1e-6f,
|
||||
.transport = transportTime * 1e-6f,
|
||||
.import = (record->importTime +
|
||||
(record->producerValid ? 0 : record->importWaitTime)) * 1e-6f,
|
||||
.receive = record->receiveTime * 1e-6f,
|
||||
.providerPrepare = record->providerPrepareTime * 1e-6f,
|
||||
.import = frameTimingAdd(record->importTime,
|
||||
record->producerValid ? 0 : record->importWaitTime) * 1e-6f,
|
||||
.dispatch = record->dispatchTime * 1e-6f,
|
||||
.queue = queueTime * 1e-6f,
|
||||
.prepare = record->prepareTime * 1e-6f,
|
||||
@@ -752,13 +800,15 @@ static bool queueCursorRedraw(void)
|
||||
const int y = state->cursorY;
|
||||
const int hx = state->cursorHX;
|
||||
const int hy = state->cursorHY;
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
renderQueue_sourceCursorState(renderQueueSource(source), generation,
|
||||
visible, x, y, hx, hy);
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1109,8 +1159,8 @@ static int renderThread(void * unused)
|
||||
else if (g_state.videoOps &&
|
||||
g_state.videoOps->type == LG_VIDEO_TYPE_SW_SURFACE)
|
||||
{
|
||||
g_state.videoOps->swSurface->setActive(
|
||||
g_state.transport.handle, false);
|
||||
lgSwSurface_setActive(g_state.videoOps->swSurface,
|
||||
g_state.transport.handle, false, NULL, NULL);
|
||||
g_state.videoOps->swSurface->detach(g_state.transport.handle);
|
||||
}
|
||||
|
||||
@@ -1125,9 +1175,6 @@ static int renderThread(void * unused)
|
||||
int main_cursorThread(void * unused)
|
||||
{
|
||||
LG_RendererCursor cursorType = LG_CURSOR_COLOR;
|
||||
const uint64_t sourceGeneration = atomic_load_explicit(
|
||||
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].generation,
|
||||
memory_order_acquire);
|
||||
struct VideoSourceState * source =
|
||||
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY];
|
||||
|
||||
@@ -1143,7 +1190,10 @@ int main_cursorThread(void * unused)
|
||||
g_state.transport.handle, &pointer);
|
||||
if (status != LG_TRANSPORT_OK)
|
||||
{
|
||||
if (status == LG_TRANSPORT_TIMEOUT || status == LG_TRANSPORT_UNAVAILABLE)
|
||||
if (videoStatusRetry(status,
|
||||
g_state.videoOps->frame->setStatusListener,
|
||||
atomic_load_explicit(
|
||||
&g_state.videoPointerAvailable, memory_order_acquire)))
|
||||
{
|
||||
if (!atomic_load_explicit(
|
||||
&g_state.stopVideoThreads, memory_order_acquire) &&
|
||||
@@ -1168,11 +1218,22 @@ int main_cursorThread(void * unused)
|
||||
}
|
||||
|
||||
const bool inputAvailable = lgInput_available();
|
||||
if (!videoPayloadAccept(
|
||||
&g_state.videoPointerAvailable, &g_state.videoPointerEpoch,
|
||||
pointer.epoch,
|
||||
g_state.videoOps->frame->setStatusListener != NULL, 0))
|
||||
{
|
||||
g_state.videoOps->frame->releasePointer(
|
||||
g_state.transport.handle, &pointer);
|
||||
continue;
|
||||
}
|
||||
LG_LOCK(g_state.videoSourceLock);
|
||||
if (atomic_load_explicit(&source->generation, memory_order_acquire) !=
|
||||
sourceGeneration)
|
||||
const uint64_t sourceGeneration = atomic_load_explicit(
|
||||
&source->generation, memory_order_acquire);
|
||||
if (!sourceGeneration)
|
||||
{
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
videoPayloadRelease();
|
||||
g_state.videoOps->frame->releasePointer(
|
||||
g_state.transport.handle, &pointer);
|
||||
continue;
|
||||
@@ -1198,6 +1259,7 @@ int main_cursorThread(void * unused)
|
||||
case CURSOR_TYPE_MASKED_COLOR: cursorType = LG_CURSOR_MASKED_COLOR; break;
|
||||
default:
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
videoPayloadRelease();
|
||||
DEBUG_ERROR("Invalid cursor type");
|
||||
g_state.videoOps->frame->releasePointer(
|
||||
g_state.transport.handle, &pointer);
|
||||
@@ -1260,8 +1322,6 @@ int main_cursorThread(void * unused)
|
||||
g_cursor.guest.hy = hy;
|
||||
g_cursor.guest.valid = valid;
|
||||
}
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
|
||||
if (pointer.flags & LG_TRANSPORT_POINTER_SHAPE)
|
||||
renderQueue_sourceCursorImage(RENDER_QUEUE_SOURCE_PRIMARY,
|
||||
sourceGeneration, cursorType, pointer.width, pointer.height,
|
||||
@@ -1278,6 +1338,7 @@ int main_cursorThread(void * unused)
|
||||
if (valid)
|
||||
renderQueue_sourceCursorState(RENDER_QUEUE_SOURCE_PRIMARY,
|
||||
sourceGeneration, visible, x, y, hx, hy);
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
|
||||
if (sourceApplied)
|
||||
{
|
||||
@@ -1298,6 +1359,7 @@ int main_cursorThread(void * unused)
|
||||
(g_params.mouseRedraw || contentChanged))))
|
||||
cursorRepaintRequest();
|
||||
|
||||
videoPayloadRelease();
|
||||
g_state.videoOps->frame->releasePointer(
|
||||
g_state.transport.handle, &pointer);
|
||||
}
|
||||
@@ -1313,9 +1375,7 @@ int main_frameThread(void * unused)
|
||||
uint64_t frameSerial = 0;
|
||||
uint32_t formatVersion = 0;
|
||||
LG_RendererFormat rendererFormat;
|
||||
const uint64_t sourceGeneration = atomic_load_explicit(
|
||||
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].generation,
|
||||
memory_order_acquire);
|
||||
uint64_t sourceGeneration = 0;
|
||||
|
||||
if (g_state.useDMA)
|
||||
DEBUG_INFO("Using DMA buffer support");
|
||||
@@ -1337,7 +1397,10 @@ int main_frameThread(void * unused)
|
||||
g_state.transport.handle, g_state.useDMA, &frame);
|
||||
if (status != LG_TRANSPORT_OK)
|
||||
{
|
||||
if (status == LG_TRANSPORT_TIMEOUT || status == LG_TRANSPORT_UNAVAILABLE)
|
||||
if (videoStatusRetry(status,
|
||||
g_state.videoOps->frame->setStatusListener,
|
||||
atomic_load_explicit(
|
||||
&g_state.videoFrameAvailable, memory_order_acquire)))
|
||||
{
|
||||
if (g_state.lgr->ops.onFramePoll)
|
||||
RENDERER(onFramePoll);
|
||||
@@ -1360,24 +1423,30 @@ int main_frameThread(void * unused)
|
||||
break;
|
||||
}
|
||||
|
||||
if (frame.serial == frameSerial && g_state.formatValid &&
|
||||
!frame.scheduleOwner)
|
||||
const uint64_t currentGeneration = atomic_load_explicit(
|
||||
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].generation,
|
||||
memory_order_acquire);
|
||||
bool sourceChanged;
|
||||
if (!videoFramePayloadAccept(&frame, currentGeneration,
|
||||
g_state.videoOps->frame->setStatusListener != NULL,
|
||||
&sourceGeneration, &frameSerial, &formatVersion, &sourceChanged))
|
||||
{
|
||||
g_state.videoOps->frame->releaseFrame(g_state.transport.handle, &frame);
|
||||
g_state.videoOps->frame->releaseFrame(
|
||||
g_state.transport.handle, &frame);
|
||||
continue;
|
||||
}
|
||||
frameSerial = frame.serial;
|
||||
const uint64_t dispatchStart = nanotime();
|
||||
|
||||
const LG_TransportFrameFormat * format = frame.format;
|
||||
if (!format)
|
||||
{
|
||||
videoPayloadRelease();
|
||||
DEBUG_ERROR("Transport returned a frame without format metadata");
|
||||
g_state.videoOps->frame->releaseFrame(g_state.transport.handle, &frame);
|
||||
app_setState(APP_STATE_SHUTDOWN);
|
||||
break;
|
||||
}
|
||||
const bool formatChanged =
|
||||
const bool formatChanged = sourceChanged ||
|
||||
!g_state.formatValid || format->version != formatVersion;
|
||||
bool rendererSupportsNativeHDR = false;
|
||||
if (formatChanged)
|
||||
@@ -1455,6 +1524,7 @@ int main_frameThread(void * unused)
|
||||
|
||||
if (invalid)
|
||||
{
|
||||
videoPayloadRelease();
|
||||
DEBUG_ERROR("Unsupported frame type");
|
||||
g_state.videoOps->frame->releaseFrame(
|
||||
g_state.transport.handle, &frame);
|
||||
@@ -1462,6 +1532,9 @@ int main_frameThread(void * unused)
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (formatChanged)
|
||||
{
|
||||
DEBUG_INFO("Format: %s %ux%u (%ux%u) stride:%u pitch:%u rotation:%d hdr:%d pq:%d sdrWhite:%u nits",
|
||||
FrameTypeStr[format->type], format->frameWidth, format->frameHeight,
|
||||
format->dataWidth, format->dataHeight, format->stride, format->pitch,
|
||||
@@ -1472,6 +1545,7 @@ int main_frameThread(void * unused)
|
||||
if (!RENDERER(onFrameFormat, rendererFormat))
|
||||
{
|
||||
LG_UNLOCK(g_state.lgrLock);
|
||||
videoPayloadRelease();
|
||||
DEBUG_ERROR("Renderer failed to configure format");
|
||||
g_state.videoOps->frame->releaseFrame(
|
||||
g_state.transport.handle, &frame);
|
||||
@@ -1523,13 +1597,13 @@ int main_frameThread(void * unused)
|
||||
{
|
||||
if (formatChanged)
|
||||
LG_UNLOCK(g_state.lgrLock);
|
||||
videoPayloadRelease();
|
||||
frameTimingCancel(frameToken);
|
||||
g_state.videoOps->frame->releaseFrame(g_state.transport.handle, &frame);
|
||||
DEBUG_ERROR("Renderer onFrame returned failure");
|
||||
app_setState(APP_STATE_SHUTDOWN);
|
||||
break;
|
||||
}
|
||||
|
||||
/* A DMA snapshot can complete on the render thread immediately after it
|
||||
* is signalled below, so sample producer timing while this lease is still
|
||||
* unambiguously owned by the frame thread. */
|
||||
@@ -1563,10 +1637,17 @@ int main_frameThread(void * unused)
|
||||
memory_order_release);
|
||||
#endif
|
||||
|
||||
atomic_store_explicit(
|
||||
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].ready, true,
|
||||
memory_order_release);
|
||||
app_useVideoSource(LG_VIDEO_SOURCE_PRIMARY);
|
||||
if (atomic_load_explicit(
|
||||
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].generation,
|
||||
memory_order_acquire) == sourceGeneration &&
|
||||
atomic_load_explicit(
|
||||
&g_state.videoFrameAvailable, memory_order_acquire))
|
||||
{
|
||||
atomic_store_explicit(
|
||||
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].ready, true,
|
||||
memory_order_release);
|
||||
app_useVideoSource(LG_VIDEO_SOURCE_PRIMARY);
|
||||
}
|
||||
|
||||
if (formatChanged)
|
||||
{
|
||||
@@ -1602,6 +1683,7 @@ int main_frameThread(void * unused)
|
||||
}
|
||||
|
||||
frameTimingFinishFrame(frameToken, &timing);
|
||||
videoPayloadRelease();
|
||||
|
||||
if (!rendererOwnsFrame)
|
||||
g_state.videoOps->frame->releaseFrame(g_state.transport.handle, &frame);
|
||||
@@ -1741,9 +1823,251 @@ static void videoSourceClearCursor(LG_VideoSource source)
|
||||
if (atomic_load_explicit(
|
||||
&g_state.videoSourceApplied, memory_order_acquire) == source)
|
||||
g_cursor.guest.valid = false;
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
|
||||
renderQueue_sourceClearCursor(renderQueueSource(source));
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
}
|
||||
|
||||
static bool videoComponentStatusChanged(atomic_bool * available,
|
||||
atomic_uint_least64_t * epoch, atomic_int * reason,
|
||||
const LG_VideoComponentStatus * status)
|
||||
{
|
||||
/* Close the component before changing epochs so a consumer can never pair
|
||||
* a newly available state with a payload from the prior endpoint. */
|
||||
const bool oldAvailable = atomic_exchange_explicit(
|
||||
available, false, memory_order_acq_rel);
|
||||
const uint64_t oldEpoch = atomic_exchange_explicit(
|
||||
epoch, status->epoch, memory_order_acq_rel);
|
||||
atomic_store_explicit(reason, status->reason, memory_order_release);
|
||||
atomic_store_explicit(
|
||||
available, status->available, memory_order_release);
|
||||
return oldAvailable != status->available ||
|
||||
oldEpoch != status->epoch;
|
||||
}
|
||||
|
||||
static bool videoComponentPayloadCurrent(const atomic_bool * available,
|
||||
const atomic_uint_least64_t * epoch, uint64_t payloadEpoch,
|
||||
bool trackEpoch)
|
||||
{
|
||||
if (!atomic_load_explicit(available, memory_order_acquire))
|
||||
return false;
|
||||
return !trackEpoch || (payloadEpoch && atomic_load_explicit(
|
||||
epoch, memory_order_acquire) == payloadEpoch);
|
||||
}
|
||||
|
||||
/* Success retains the payload gate. The caller must release it after every
|
||||
* externally visible effect derived from the accepted payload is complete. */
|
||||
static bool videoPayloadAccept(const atomic_bool * available,
|
||||
const atomic_uint_least64_t * epoch, uint64_t payloadEpoch,
|
||||
bool trackEpoch, uint64_t sourceGeneration)
|
||||
{
|
||||
LG_LOCK(g_state.videoPayloadLock);
|
||||
const bool current = videoComponentPayloadCurrent(
|
||||
available, epoch, payloadEpoch, trackEpoch) &&
|
||||
(!sourceGeneration || atomic_load_explicit(
|
||||
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].generation,
|
||||
memory_order_acquire) == sourceGeneration);
|
||||
if (!current)
|
||||
LG_UNLOCK(g_state.videoPayloadLock);
|
||||
return current;
|
||||
}
|
||||
|
||||
static void videoPayloadRelease(void)
|
||||
{
|
||||
LG_UNLOCK(g_state.videoPayloadLock);
|
||||
}
|
||||
|
||||
static bool videoFramePayloadAccept(const LG_TransportFrame * frame,
|
||||
uint64_t currentGeneration, bool trackEpoch,
|
||||
uint64_t * sourceGeneration, uint64_t * frameSerial,
|
||||
uint32_t * formatVersion, bool * sourceChanged)
|
||||
{
|
||||
if (!currentGeneration || !videoPayloadAccept(
|
||||
&g_state.videoFrameAvailable, &g_state.videoFrameEpoch,
|
||||
frame->epoch, trackEpoch, currentGeneration))
|
||||
return false;
|
||||
|
||||
*sourceChanged = *sourceGeneration != currentGeneration;
|
||||
if (*sourceChanged)
|
||||
{
|
||||
*sourceGeneration = currentGeneration;
|
||||
*frameSerial = 0;
|
||||
*formatVersion = 0;
|
||||
}
|
||||
|
||||
if (!*sourceChanged && frame->serial == *frameSerial &&
|
||||
g_state.formatValid && !frame->scheduleOwner)
|
||||
{
|
||||
videoPayloadRelease();
|
||||
return false;
|
||||
}
|
||||
|
||||
*frameSerial = frame->serial;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void primaryVideoStatusChanged(void * opaque,
|
||||
const LG_VideoStatus * status)
|
||||
{
|
||||
(void)opaque;
|
||||
if (!status || !atomic_load_explicit(
|
||||
&g_state.videoStatusActive, memory_order_acquire) ||
|
||||
app_getState() != APP_STATE_RUNNING)
|
||||
return;
|
||||
|
||||
LG_LOCK(g_state.videoPayloadLock);
|
||||
if (!atomic_load_explicit(
|
||||
&g_state.videoStatusActive, memory_order_acquire) ||
|
||||
app_getState() != APP_STATE_RUNNING)
|
||||
{
|
||||
LG_UNLOCK(g_state.videoPayloadLock);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool frameWasAvailable = atomic_load_explicit(
|
||||
&g_state.videoFrameAvailable, memory_order_acquire);
|
||||
const bool first = !atomic_exchange_explicit(
|
||||
&g_state.videoStatusKnown, true, memory_order_acq_rel);
|
||||
const bool frameChanged = videoComponentStatusChanged(
|
||||
&g_state.videoFrameAvailable, &g_state.videoFrameEpoch,
|
||||
&g_state.videoFrameReason, &status->frame) || first;
|
||||
const bool pointerChanged = videoComponentStatusChanged(
|
||||
&g_state.videoPointerAvailable, &g_state.videoPointerEpoch,
|
||||
&g_state.videoPointerReason, &status->pointer) || first;
|
||||
|
||||
if (pointerChanged)
|
||||
videoSourceClearCursor(LG_VIDEO_SOURCE_PRIMARY);
|
||||
|
||||
if (frameChanged)
|
||||
{
|
||||
const uint64_t sourceGeneration = atomic_load_explicit(
|
||||
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].generation,
|
||||
memory_order_acquire);
|
||||
if (!status->frame.available || frameWasAvailable || !sourceGeneration)
|
||||
{
|
||||
videoSourceInvalidate(LG_VIDEO_SOURCE_PRIMARY);
|
||||
/* Keep an inactive generation while frames are unavailable so an
|
||||
* independent pointer component can continue updating its retained
|
||||
* state. */
|
||||
videoSourceBegin(LG_VIDEO_SOURCE_PRIMARY);
|
||||
}
|
||||
}
|
||||
LG_UNLOCK(g_state.videoPayloadLock);
|
||||
|
||||
if (frameChanged)
|
||||
g_state.videoOps->frame->cancelFrameWait(g_state.transport.handle);
|
||||
if (pointerChanged && g_state.videoOps->frame->nextPointer)
|
||||
g_state.videoOps->frame->cancelPointerWait(g_state.transport.handle);
|
||||
|
||||
if (!frameChanged)
|
||||
return;
|
||||
|
||||
if (status->frame.available)
|
||||
return;
|
||||
|
||||
if (atomic_load_explicit(&g_state.stopVideo, memory_order_acquire))
|
||||
return;
|
||||
|
||||
const bool fallbackReady = fallbackRequestVideo();
|
||||
if (atomic_load_explicit(&g_state.stopVideo, memory_order_acquire))
|
||||
{
|
||||
if (g_state.fallback)
|
||||
lgTransportFallback_requestVideoActive(g_state.fallback, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fallbackReady)
|
||||
videoSourceShowSplashIfNeeded();
|
||||
}
|
||||
|
||||
static void primaryVideoStatusStart(void)
|
||||
{
|
||||
if (!g_state.videoOps || g_state.videoOps->type != LG_VIDEO_TYPE_FRAME)
|
||||
return;
|
||||
|
||||
atomic_store_explicit(
|
||||
&g_state.videoFrameAvailable, false, memory_order_relaxed);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoPointerAvailable, false, memory_order_relaxed);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoStatusKnown, false, memory_order_relaxed);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoFrameEpoch, 0, memory_order_relaxed);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoPointerEpoch, 0, memory_order_relaxed);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoFrameReason, LG_TRANSPORT_UNAVAILABLE,
|
||||
memory_order_relaxed);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoPointerReason, LG_TRANSPORT_UNAVAILABLE,
|
||||
memory_order_relaxed);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoStatusActive, true, memory_order_release);
|
||||
|
||||
if (g_state.videoOps->frame->setStatusListener)
|
||||
{
|
||||
g_state.videoOps->frame->setStatusListener(g_state.transport.handle,
|
||||
primaryVideoStatusChanged, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
const LG_VideoStatus status =
|
||||
{
|
||||
.frame =
|
||||
{
|
||||
.available = true,
|
||||
.epoch = 1,
|
||||
.reason = LG_TRANSPORT_OK,
|
||||
},
|
||||
.pointer =
|
||||
{
|
||||
.available = g_state.videoOps->frame->nextPointer != NULL,
|
||||
.epoch = g_state.videoOps->frame->nextPointer ? 1 : 0,
|
||||
.reason = g_state.videoOps->frame->nextPointer ?
|
||||
LG_TRANSPORT_OK : LG_TRANSPORT_UNAVAILABLE,
|
||||
},
|
||||
};
|
||||
primaryVideoStatusChanged(NULL, &status);
|
||||
}
|
||||
|
||||
static void primaryVideoStatusStop(void)
|
||||
{
|
||||
if (!atomic_exchange_explicit(
|
||||
&g_state.videoStatusActive, false, memory_order_acq_rel))
|
||||
return;
|
||||
|
||||
if (g_state.videoOps && g_state.videoOps->type == LG_VIDEO_TYPE_FRAME &&
|
||||
g_state.videoOps->frame->setStatusListener)
|
||||
g_state.videoOps->frame->setStatusListener(
|
||||
g_state.transport.handle, NULL, NULL);
|
||||
|
||||
LG_LOCK(g_state.videoPayloadLock);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoFrameAvailable, false, memory_order_release);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoPointerAvailable, false, memory_order_release);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoFrameReason, LG_TRANSPORT_DISCONNECTED,
|
||||
memory_order_release);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoPointerReason, LG_TRANSPORT_DISCONNECTED,
|
||||
memory_order_release);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoStatusKnown, false, memory_order_release);
|
||||
LG_UNLOCK(g_state.videoPayloadLock);
|
||||
}
|
||||
|
||||
void main_videoStreamEnabled(void)
|
||||
{
|
||||
if (!g_state.videoOps || g_state.videoOps->type != LG_VIDEO_TYPE_FRAME ||
|
||||
!g_state.videoOps->frame->setStatusListener ||
|
||||
!atomic_load_explicit(
|
||||
&g_state.videoStatusKnown, memory_order_acquire) ||
|
||||
atomic_load_explicit(
|
||||
&g_state.videoFrameAvailable, memory_order_acquire))
|
||||
return;
|
||||
|
||||
if (!fallbackRequestVideo())
|
||||
videoSourceShowSplashIfNeeded();
|
||||
}
|
||||
|
||||
static void videoSourceReject(void * opaque, RenderQueueSource queueSource,
|
||||
@@ -1914,10 +2238,10 @@ static void videoSourceApplied(void * opaque, RenderQueueSource queueSource,
|
||||
app_refreshVideoSource();
|
||||
}
|
||||
|
||||
static bool swSurfaceEventAdmitted(LG_VideoSource source)
|
||||
static bool swSurfaceEventAccepted(LG_VideoSource source)
|
||||
{
|
||||
return source != LG_VIDEO_SOURCE_FALLBACK ||
|
||||
lgTransportFallback_admitted(g_state.fallback);
|
||||
lgTransportFallback_acceptsVideoEvents(g_state.fallback);
|
||||
}
|
||||
|
||||
static void swSurfaceConfigure(LG_VideoSource source,
|
||||
@@ -1926,7 +2250,7 @@ static void swSurfaceConfigure(LG_VideoSource source,
|
||||
struct VideoSourceState * state = &g_state.videoSource[source];
|
||||
uint64_t generation;
|
||||
LG_LOCK(g_state.videoSourceLock);
|
||||
if (!swSurfaceEventAdmitted(source))
|
||||
if (!swSurfaceEventAccepted(source))
|
||||
{
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
return;
|
||||
@@ -1984,12 +2308,12 @@ static void swSurfaceDrawFill(LG_VideoSource source, int x, int y,
|
||||
int width, int height, uint32_t color)
|
||||
{
|
||||
LG_LOCK(g_state.videoSourceLock);
|
||||
const bool admitted = swSurfaceEventAdmitted(source);
|
||||
const uint64_t generation = admitted ? atomic_load_explicit(
|
||||
const bool accepted = swSurfaceEventAccepted(source);
|
||||
const uint64_t generation = accepted ? atomic_load_explicit(
|
||||
&g_state.videoSource[source].generation, memory_order_acquire) : 0;
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
|
||||
if (!admitted)
|
||||
if (!accepted)
|
||||
return;
|
||||
|
||||
renderQueue_sourceSwSurfaceDrawFill(renderQueueSource(source), generation,
|
||||
@@ -2000,12 +2324,12 @@ static void swSurfaceDrawBitmap(LG_VideoSource source, bool topDown,
|
||||
int x, int y, int width, int height, int stride, const void * data)
|
||||
{
|
||||
LG_LOCK(g_state.videoSourceLock);
|
||||
const bool admitted = swSurfaceEventAdmitted(source);
|
||||
const uint64_t generation = admitted ? atomic_load_explicit(
|
||||
const bool accepted = swSurfaceEventAccepted(source);
|
||||
const uint64_t generation = accepted ? atomic_load_explicit(
|
||||
&g_state.videoSource[source].generation, memory_order_acquire) : 0;
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
|
||||
if (!admitted)
|
||||
if (!accepted)
|
||||
return;
|
||||
|
||||
renderQueue_sourceSwSurfaceDrawBitmap(renderQueueSource(source), generation,
|
||||
@@ -2039,7 +2363,7 @@ static void swSurfacePointer(LG_VideoSource source,
|
||||
const bool drawCursor = source != LG_VIDEO_SOURCE_PRIMARY ||
|
||||
g_cursor.draw || !lgInput_available();
|
||||
LG_LOCK(g_state.videoSourceLock);
|
||||
if (!swSurfaceEventAdmitted(source))
|
||||
if (!swSurfaceEventAccepted(source))
|
||||
{
|
||||
LG_UNLOCK(g_state.videoSourceLock);
|
||||
return;
|
||||
@@ -2163,7 +2487,9 @@ static void swSurfaceEventConfigure(void * opaque,
|
||||
|
||||
static void swSurfaceEventDestroy(void * opaque)
|
||||
{
|
||||
swSurfaceDestroy(swSurfaceSource(opaque));
|
||||
const LG_VideoSource source = swSurfaceSource(opaque);
|
||||
if (swSurfaceEventAccepted(source))
|
||||
swSurfaceDestroy(source);
|
||||
}
|
||||
|
||||
static void swSurfaceEventDrawFill(void * opaque, int x, int y,
|
||||
@@ -2196,14 +2522,29 @@ static const LG_SwSurfaceEventOps swSurfaceEvents =
|
||||
|
||||
static bool fallbackRequestVideo(void)
|
||||
{
|
||||
if (!g_state.fallback)
|
||||
if (!g_state.fallback || atomic_load_explicit(
|
||||
&g_state.stopVideo, memory_order_acquire))
|
||||
return false;
|
||||
|
||||
lgTransportFallback_requestVideoActive(g_state.fallback, true);
|
||||
if (!lgTransportFallback_admitted(g_state.fallback))
|
||||
if (atomic_load_explicit(&g_state.stopVideo, memory_order_acquire))
|
||||
{
|
||||
lgTransportFallback_requestVideoActive(g_state.fallback, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lgTransportFallback_usable(g_state.fallback) ||
|
||||
!lgTransportFallback_ready(g_state.fallback))
|
||||
return false;
|
||||
|
||||
return app_useVideoSource(LG_VIDEO_SOURCE_FALLBACK);
|
||||
const bool active = app_useVideoSource(LG_VIDEO_SOURCE_FALLBACK);
|
||||
if (atomic_load_explicit(&g_state.stopVideo, memory_order_acquire))
|
||||
{
|
||||
lgTransportFallback_requestVideoActive(g_state.fallback, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
static void fallbackConnected(void * opaque,
|
||||
@@ -2235,6 +2576,23 @@ static void fallbackDisconnected(void * opaque)
|
||||
videoSourceShowSplashIfNeeded();
|
||||
}
|
||||
|
||||
static void fallbackVideoStateChanged(void * opaque, bool ready)
|
||||
{
|
||||
(void)opaque;
|
||||
if (app_getState() == APP_STATE_SHUTDOWN)
|
||||
return;
|
||||
|
||||
if (ready && lgTransportFallback_videoRequested(g_state.fallback))
|
||||
{
|
||||
fallbackRequestVideo();
|
||||
return;
|
||||
}
|
||||
|
||||
swSurfaceDestroy(LG_VIDEO_SOURCE_FALLBACK);
|
||||
videoSourceClearCursor(LG_VIDEO_SOURCE_FALLBACK);
|
||||
app_refreshVideoSource();
|
||||
}
|
||||
|
||||
static void fallbackLost(void * opaque)
|
||||
{
|
||||
(void)opaque;
|
||||
@@ -2248,14 +2606,14 @@ static void fallbackLost(void * opaque)
|
||||
}
|
||||
}
|
||||
|
||||
static void fallbackUUIDMismatch(void * opaque, const uint8_t primary[16],
|
||||
static void fallbackEndpointMismatch(void * opaque, const uint8_t primary[16],
|
||||
const uint8_t fallback[16])
|
||||
{
|
||||
(void)opaque;
|
||||
(void)primary;
|
||||
(void)fallback;
|
||||
atomic_store_explicit(
|
||||
&g_state.fallbackUUIDMismatch, true, memory_order_release);
|
||||
&g_state.fallbackEndpointMismatch, true, memory_order_release);
|
||||
app_invalidateWindow(false);
|
||||
}
|
||||
|
||||
@@ -2264,7 +2622,8 @@ static const LG_TransportFallbackEventOps fallbackEvents =
|
||||
.connected = fallbackConnected,
|
||||
.lost = fallbackLost,
|
||||
.disconnected = fallbackDisconnected,
|
||||
.uuidMismatch = fallbackUUIDMismatch,
|
||||
.videoStateChanged = fallbackVideoStateChanged,
|
||||
.endpointMismatch = fallbackEndpointMismatch,
|
||||
};
|
||||
|
||||
static void primaryLost(void)
|
||||
@@ -2276,7 +2635,7 @@ static void primaryLost(void)
|
||||
for (;;)
|
||||
{
|
||||
unsigned int next = lost | TRANSPORT_LOST_PRIMARY;
|
||||
if (lgTransportFallback_ready(g_state.fallback))
|
||||
if (lgTransportFallback_usable(g_state.fallback))
|
||||
next &= ~TRANSPORT_LOST_FALLBACK;
|
||||
if (atomic_compare_exchange_weak_explicit(&g_state.transportLost,
|
||||
&lost, next, memory_order_acq_rel, memory_order_acquire))
|
||||
@@ -2319,7 +2678,7 @@ static void fallbackStop(void)
|
||||
static void fallbackHandleEvents(void)
|
||||
{
|
||||
if (!atomic_exchange_explicit(
|
||||
&g_state.fallbackUUIDMismatch, false, memory_order_acq_rel))
|
||||
&g_state.fallbackEndpointMismatch, false, memory_order_acq_rel))
|
||||
return;
|
||||
|
||||
app_msgBox(
|
||||
@@ -2572,6 +2931,12 @@ static const char * recoveryErrorText(LG_RecoveryError error)
|
||||
case LG_RECOVERY_ERR_NO_FALLBACK_DISPLAY:
|
||||
return "No fallback display became active";
|
||||
|
||||
case LG_RECOVERY_ERR_BUSY:
|
||||
return "A conflicting recovery request is already in progress";
|
||||
|
||||
case LG_RECOVERY_ERR_CAPACITY:
|
||||
return "Too many recovery requests are pending";
|
||||
|
||||
case LG_RECOVERY_ERR_UNSUPPORTED:
|
||||
return "Recovery is not supported by the guest driver";
|
||||
|
||||
@@ -2919,17 +3284,34 @@ static MsgBoxHandle showSpiceInputHelp(void)
|
||||
|
||||
struct TransportSessionProbe
|
||||
{
|
||||
LG_Transport * transport;
|
||||
LG_Transport * transport;
|
||||
const LG_TransportOps * ops;
|
||||
LG_TransportSession session;
|
||||
LG_TransportStatus status;
|
||||
atomic_bool done;
|
||||
LG_TransportCancelledFn cancelled;
|
||||
void * cancelOpaque;
|
||||
LG_TransportSession session;
|
||||
LG_TransportStatus status;
|
||||
atomic_bool done;
|
||||
};
|
||||
|
||||
static bool transportSessionCancelled(void * opaque)
|
||||
{
|
||||
(void)opaque;
|
||||
return app_getState() != APP_STATE_RUNNING;
|
||||
}
|
||||
|
||||
static bool primarySurfaceActivationCancelled(void * opaque)
|
||||
{
|
||||
(void)opaque;
|
||||
return app_getState() != APP_STATE_RUNNING ||
|
||||
atomic_load_explicit(&g_state.stopVideo, memory_order_acquire);
|
||||
}
|
||||
|
||||
static int transportSessionProbe(void * opaque)
|
||||
{
|
||||
struct TransportSessionProbe * probe = opaque;
|
||||
probe->status = probe->ops->connect(probe->transport, &probe->session);
|
||||
probe->status = probe->ops->connectCancellable(
|
||||
probe->transport, &probe->session,
|
||||
probe->cancelled, probe->cancelOpaque);
|
||||
atomic_store_explicit(&probe->done, true, memory_order_release);
|
||||
return 0;
|
||||
}
|
||||
@@ -3059,18 +3441,26 @@ static int lg_run(void)
|
||||
LG_RecoveryInfo initialRecovery = { 0 };
|
||||
const bool initialRecoveryValid = recoveryGetInfo(&initialRecovery);
|
||||
|
||||
g_state.videoOps =
|
||||
g_state.transport.ops->getVideoOps(g_state.transport.handle);
|
||||
g_state.videoOps = g_state.transport.ops->getVideoOps ?
|
||||
g_state.transport.ops->getVideoOps(g_state.transport.handle) : NULL;
|
||||
if (!g_state.videoOps ||
|
||||
(g_state.videoOps->type != LG_VIDEO_TYPE_FRAME &&
|
||||
g_state.videoOps->type != LG_VIDEO_TYPE_SW_SURFACE) ||
|
||||
(g_state.videoOps->type == LG_VIDEO_TYPE_FRAME &&
|
||||
!g_state.videoOps->frame) ||
|
||||
(!g_state.videoOps->frame ||
|
||||
!g_state.videoOps->frame->nextFrame ||
|
||||
!g_state.videoOps->frame->releaseFrame ||
|
||||
!g_state.videoOps->frame->cancelFrameWait ||
|
||||
((g_state.videoOps->frame->nextPointer != NULL) !=
|
||||
(g_state.videoOps->frame->releasePointer != NULL)) ||
|
||||
(g_state.videoOps->frame->nextPointer &&
|
||||
!g_state.videoOps->frame->cancelPointerWait))) ||
|
||||
(g_state.videoOps->type == LG_VIDEO_TYPE_SW_SURFACE &&
|
||||
(!g_state.videoOps->swSurface ||
|
||||
!g_state.videoOps->swSurface->attach ||
|
||||
!g_state.videoOps->swSurface->detach ||
|
||||
!g_state.videoOps->swSurface->setActive)))
|
||||
!g_state.videoOps->swSurface->setActive ||
|
||||
!g_state.videoOps->swSurface->cancelPending)))
|
||||
{
|
||||
DEBUG_ERROR("Transport does not provide a usable video source");
|
||||
return -1;
|
||||
@@ -3092,6 +3482,7 @@ static int lg_run(void)
|
||||
}
|
||||
|
||||
//setup the render command queue
|
||||
LG_LOCK_INIT(g_state.videoPayloadLock);
|
||||
LG_LOCK_INIT(g_state.videoSourceLock);
|
||||
LG_LOCK_INIT(g_state.videoSplashLock);
|
||||
renderQueue_init();
|
||||
@@ -3296,9 +3687,11 @@ restart:
|
||||
}
|
||||
|
||||
struct TransportSessionProbe probe = {
|
||||
.transport = g_state.transport.handle,
|
||||
.ops = g_state.transport.ops,
|
||||
.done = false,
|
||||
.transport = g_state.transport.handle,
|
||||
.ops = g_state.transport.ops,
|
||||
.cancelled = transportSessionCancelled,
|
||||
.cancelOpaque = NULL,
|
||||
.done = false,
|
||||
};
|
||||
LGThread * probeThread;
|
||||
if (!lgCreateThread("transportSession", transportSessionProbe, &probe,
|
||||
@@ -3490,15 +3883,19 @@ restart:
|
||||
|
||||
if (g_state.videoOps->type == LG_VIDEO_TYPE_FRAME)
|
||||
{
|
||||
videoSourceBegin(LG_VIDEO_SOURCE_PRIMARY);
|
||||
primaryVideoStatusStart();
|
||||
if (!atomic_load_explicit(&g_state.stopVideo, memory_order_acquire) &&
|
||||
!core_startVideoThreads())
|
||||
{
|
||||
primaryVideoStatusStop();
|
||||
return recoveryExit(&recoveryPrompt, -1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!g_state.videoOps->swSurface->setActive(
|
||||
g_state.transport.handle, true))
|
||||
if (!lgSwSurface_setActive(g_state.videoOps->swSurface,
|
||||
g_state.transport.handle, true,
|
||||
primarySurfaceActivationCancelled, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to activate the primary software surface");
|
||||
return recoveryExit(&recoveryPrompt, -1);
|
||||
@@ -3509,7 +3906,6 @@ restart:
|
||||
while(likely(app_getState() == APP_STATE_RUNNING))
|
||||
{
|
||||
fallbackHandleEvents();
|
||||
|
||||
if (unlikely(!g_state.transport.ops->sessionValid(
|
||||
g_state.transport.handle)))
|
||||
{
|
||||
@@ -3525,6 +3921,7 @@ restart:
|
||||
}
|
||||
|
||||
frameScheduler_stop();
|
||||
primaryVideoStatusStop();
|
||||
|
||||
if (app_getState() == APP_STATE_RESTART)
|
||||
{
|
||||
@@ -3538,8 +3935,8 @@ restart:
|
||||
if (g_state.videoOps->type == LG_VIDEO_TYPE_FRAME)
|
||||
core_stopVideoThreads();
|
||||
else
|
||||
g_state.videoOps->swSurface->setActive(
|
||||
g_state.transport.handle, false);
|
||||
lgSwSurface_setActive(g_state.videoOps->swSurface,
|
||||
g_state.transport.handle, false, NULL, NULL);
|
||||
videoSourceInvalidate(LG_VIDEO_SOURCE_PRIMARY);
|
||||
videoSourceClearCursor(LG_VIDEO_SOURCE_PRIMARY);
|
||||
lgInput_dropTransport();
|
||||
@@ -3651,6 +4048,7 @@ static void lg_shutdown(void)
|
||||
|
||||
renderQueue_setSourceFns(NULL, NULL, NULL, NULL);
|
||||
renderQueue_free();
|
||||
LG_LOCK_FREE(g_state.videoPayloadLock);
|
||||
LG_LOCK_FREE(g_state.videoSourceLock);
|
||||
LG_LOCK_FREE(g_state.videoSplashLock);
|
||||
frameScheduler_free();
|
||||
|
||||
@@ -101,11 +101,20 @@ struct AppState
|
||||
atomic_int videoSourceRequested;
|
||||
atomic_int videoSourceApplied;
|
||||
atomic_uint_least64_t videoSourceAppliedGeneration;
|
||||
atomic_bool videoStatusActive;
|
||||
atomic_bool videoStatusKnown;
|
||||
atomic_bool videoFrameAvailable;
|
||||
atomic_bool videoPointerAvailable;
|
||||
atomic_uint_least64_t videoFrameEpoch;
|
||||
atomic_uint_least64_t videoPointerEpoch;
|
||||
atomic_int videoFrameReason;
|
||||
atomic_int videoPointerReason;
|
||||
LG_Lock videoPayloadLock;
|
||||
LG_Lock videoSourceLock;
|
||||
LG_Lock videoSplashLock;
|
||||
bool videoGeometryDirty;
|
||||
|
||||
atomic_bool fallbackUUIDMismatch;
|
||||
atomic_bool fallbackEndpointMismatch;
|
||||
atomic_uint transportLost;
|
||||
|
||||
uint8_t guestUUID[16];
|
||||
@@ -357,5 +366,8 @@ extern struct AppParams g_params;
|
||||
|
||||
int main_cursorThread(void * unused);
|
||||
int main_frameThread(void * unused);
|
||||
void main_videoStreamEnabled(void);
|
||||
void main_framePresented(uint64_t frameToken, uint64_t presentTime,
|
||||
bool valid);
|
||||
|
||||
#define RENDERER(fn, ...) g_state.lgr->ops.fn(g_state.lgr, ##__VA_ARGS__)
|
||||
|
||||
@@ -238,6 +238,8 @@ static const char * const frameTimingLabels[FRAME_TIMING_STAGE_COUNT] = {
|
||||
"Ready",
|
||||
"Hold",
|
||||
"Transport",
|
||||
"Receive",
|
||||
"Provider Prep",
|
||||
"Import",
|
||||
"Dispatch",
|
||||
"Queue",
|
||||
@@ -310,6 +312,8 @@ static bool accumulateFrameTimingSample(int index, void * value_, void * udata_)
|
||||
timing->ready,
|
||||
timing->hold,
|
||||
timing->transport,
|
||||
timing->receive,
|
||||
timing->providerPrepare,
|
||||
timing->import,
|
||||
timing->dispatch,
|
||||
timing->queue,
|
||||
|
||||
@@ -54,6 +54,8 @@ typedef struct OverlayFrameTiming
|
||||
float ready;
|
||||
float hold;
|
||||
float transport;
|
||||
float receive;
|
||||
float providerPrepare;
|
||||
float import;
|
||||
float dispatch;
|
||||
float queue;
|
||||
@@ -75,6 +77,8 @@ enum OverlayFrameTimingStage
|
||||
OVERLAY_FRAME_TIMING_READY,
|
||||
OVERLAY_FRAME_TIMING_HOLD,
|
||||
OVERLAY_FRAME_TIMING_TRANSPORT,
|
||||
OVERLAY_FRAME_TIMING_RECEIVE,
|
||||
OVERLAY_FRAME_TIMING_PROVIDER_PREPARE,
|
||||
OVERLAY_FRAME_TIMING_IMPORT,
|
||||
OVERLAY_FRAME_TIMING_DISPATCH,
|
||||
OVERLAY_FRAME_TIMING_QUEUE,
|
||||
@@ -94,6 +98,9 @@ enum OverlayFrameTimingStage
|
||||
((1U << OVERLAY_FRAME_TIMING_TRANSPORT) - 1U)
|
||||
#define OVERLAY_FRAME_TIMING_VALID_TRANSPORT \
|
||||
(1U << OVERLAY_FRAME_TIMING_TRANSPORT)
|
||||
#define OVERLAY_FRAME_TIMING_VALID_PROVIDER \
|
||||
((1U << OVERLAY_FRAME_TIMING_RECEIVE) | \
|
||||
(1U << OVERLAY_FRAME_TIMING_PROVIDER_PREPARE))
|
||||
#define OVERLAY_FRAME_TIMING_VALID_PRESENT \
|
||||
(1U << OVERLAY_FRAME_TIMING_PRESENT)
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ typedef struct RenderCommand
|
||||
|
||||
RenderQueueSource source;
|
||||
uint64_t generation;
|
||||
uint64_t cursorGeneration;
|
||||
uint64_t transitionSerial;
|
||||
|
||||
enum
|
||||
@@ -47,6 +48,7 @@ typedef struct RenderCommand
|
||||
CURSOR_OP_IMAGE,
|
||||
CURSOR_OP_COLOR_TRANSFORM,
|
||||
CURSOR_OP_WHITE_LEVEL,
|
||||
CURSOR_OP_CLEAR,
|
||||
SOURCE_OP_TRANSITION,
|
||||
}
|
||||
op;
|
||||
@@ -172,6 +174,7 @@ static bool l_rendererSupportsNativeHDR;
|
||||
static LG_RendererFormat l_surfaceFormat;
|
||||
|
||||
static _Atomic(uint64_t) l_sourceGeneration[RENDER_QUEUE_SOURCE_COUNT];
|
||||
static _Atomic(uint64_t) l_cursorGeneration[RENDER_QUEUE_SOURCE_COUNT];
|
||||
static _Atomic(uint64_t) l_transitionSerial;
|
||||
static LG_Lock l_sourceLock;
|
||||
static LG_Lock l_transitionLock;
|
||||
@@ -257,6 +260,15 @@ static bool transitionCommand(const RenderCommand * cmd)
|
||||
cmd->op == SW_SURFACE_OP_CONFIGURE_TRANSITION;
|
||||
}
|
||||
|
||||
static bool cursorCommand(const RenderCommand * cmd)
|
||||
{
|
||||
return cmd->op == CURSOR_OP_STATE ||
|
||||
cmd->op == CURSOR_OP_IMAGE ||
|
||||
cmd->op == CURSOR_OP_COLOR_TRANSFORM ||
|
||||
cmd->op == CURSOR_OP_WHITE_LEVEL ||
|
||||
cmd->op == CURSOR_OP_CLEAR;
|
||||
}
|
||||
|
||||
static bool commandValid(const RenderCommand * cmd)
|
||||
{
|
||||
if (transitionCommand(cmd) &&
|
||||
@@ -266,8 +278,15 @@ static bool commandValid(const RenderCommand * cmd)
|
||||
|
||||
if (cmd->source == RENDER_QUEUE_SOURCE_NONE)
|
||||
return cmd->op == SOURCE_OP_TRANSITION;
|
||||
if (!sourceValid(cmd->source))
|
||||
return false;
|
||||
|
||||
return generationValid(cmd->source, cmd->generation);
|
||||
const bool cursorCurrent = !cursorCommand(cmd) || atomic_load_explicit(
|
||||
&l_cursorGeneration[cmd->source], memory_order_acquire) ==
|
||||
cmd->cursorGeneration;
|
||||
if (cmd->op == CURSOR_OP_CLEAR)
|
||||
return cursorCurrent;
|
||||
return generationValid(cmd->source, cmd->generation) && cursorCurrent;
|
||||
}
|
||||
|
||||
static uint64_t swSurfaceDamageArea(const FrameDamageRect * rect)
|
||||
@@ -469,6 +488,8 @@ static void setCommandSource(RenderCommand * cmd, RenderQueueSource source,
|
||||
{
|
||||
cmd->source = source;
|
||||
cmd->generation = generation;
|
||||
cmd->cursorGeneration = sourceValid(source) ? atomic_load_explicit(
|
||||
&l_cursorGeneration[source], memory_order_acquire) : 0;
|
||||
}
|
||||
|
||||
static bool copyCursorImage(RenderCommand * cmd, const void * data)
|
||||
@@ -567,6 +588,7 @@ void renderQueue_init(void)
|
||||
for (int i = 0; i < RENDER_QUEUE_SOURCE_COUNT; ++i)
|
||||
{
|
||||
atomic_store_explicit(&l_sourceGeneration[i], 0, memory_order_relaxed);
|
||||
atomic_store_explicit(&l_cursorGeneration[i], 1, memory_order_relaxed);
|
||||
LG_LOCK_INIT(l_swSurface[i].lock);
|
||||
}
|
||||
atomic_store_explicit(&l_transitionSerial, 0, memory_order_relaxed);
|
||||
@@ -686,9 +708,26 @@ void renderQueue_sourceClearCursor(RenderQueueSource source)
|
||||
return;
|
||||
|
||||
LG_LOCK(l_sourceLock);
|
||||
uint64_t cursorGeneration = atomic_load_explicit(
|
||||
&l_cursorGeneration[source], memory_order_relaxed) + 1;
|
||||
if (!cursorGeneration)
|
||||
++cursorGeneration;
|
||||
atomic_store_explicit(&l_cursorGeneration[source], cursorGeneration,
|
||||
memory_order_release);
|
||||
free(l_cursor[source].data);
|
||||
memset(&l_cursor[source], 0, sizeof(l_cursor[source]));
|
||||
const uint64_t sourceGeneration = atomic_load_explicit(
|
||||
&l_sourceGeneration[source], memory_order_acquire);
|
||||
RenderCommand * cmd = malloc(sizeof(*cmd));
|
||||
bool wake = false;
|
||||
if (cmd)
|
||||
{
|
||||
setCommandSource(cmd, source, sourceGeneration);
|
||||
cmd->op = CURSOR_OP_CLEAR;
|
||||
wake = queueCommand(cmd, RENDER_QUEUE_INVALIDATE_PARTIAL);
|
||||
}
|
||||
LG_UNLOCK(l_sourceLock);
|
||||
wakeQueue(wake);
|
||||
}
|
||||
|
||||
static bool configureSwSurface(RenderQueueSource source,
|
||||
@@ -1454,6 +1493,19 @@ bool renderQueue_process(void)
|
||||
break;
|
||||
}
|
||||
|
||||
case CURSOR_OP_CLEAR:
|
||||
if (l_appliedSource == cmd->source)
|
||||
{
|
||||
RENDERER(onMouseEvent, false, 0, 0, 0, 0);
|
||||
if (g_state.lgr->ops.onMouseColorTransform)
|
||||
g_state.lgr->ops.onMouseColorTransform(
|
||||
g_state.lgr, &l_identityColorTransform);
|
||||
if (g_state.lgr->ops.onMouseWhiteLevel)
|
||||
g_state.lgr->ops.onMouseWhiteLevel(
|
||||
g_state.lgr, LG_SDR_WHITE_LEVEL_DEFAULT);
|
||||
}
|
||||
break;
|
||||
|
||||
case SOURCE_OP_TRANSITION:
|
||||
{
|
||||
const bool swSurface =
|
||||
|
||||
84
client/src/sw_surface.c
Normal file
84
client/src/sw_surface.c
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "sw_surface.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/time.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
struct SwSurfaceCall
|
||||
{
|
||||
const LG_SwSurfaceOps * ops;
|
||||
LG_Transport * transport;
|
||||
bool active;
|
||||
bool result;
|
||||
atomic_bool done;
|
||||
};
|
||||
|
||||
static int swSurfaceCallThread(void * opaque)
|
||||
{
|
||||
struct SwSurfaceCall * call = opaque;
|
||||
call->result = call->ops->setActive(call->transport, call->active);
|
||||
atomic_store_explicit(&call->done, true, memory_order_release);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool lgSwSurface_setActive(const LG_SwSurfaceOps * ops,
|
||||
LG_Transport * transport, bool active,
|
||||
LG_TransportCancelledFn cancelled, void * opaque)
|
||||
{
|
||||
if (!ops || !ops->setActive || !ops->cancelPending || !transport)
|
||||
return false;
|
||||
|
||||
struct SwSurfaceCall call =
|
||||
{
|
||||
.ops = ops,
|
||||
.transport = transport,
|
||||
.active = active,
|
||||
};
|
||||
atomic_init(&call.done, false);
|
||||
|
||||
LGThread * thread;
|
||||
if (!lgCreateThread("surfaceState", swSurfaceCallThread, &call, &thread))
|
||||
return false;
|
||||
|
||||
bool cancelRequested = false;
|
||||
while (!atomic_load_explicit(&call.done, memory_order_acquire))
|
||||
{
|
||||
if (active && cancelled && cancelled(opaque))
|
||||
{
|
||||
cancelRequested = true;
|
||||
ops->cancelPending(transport);
|
||||
}
|
||||
nsleep(1000000U);
|
||||
}
|
||||
|
||||
if (active && cancelled && cancelled(opaque))
|
||||
cancelRequested = true;
|
||||
|
||||
if (!lgJoinThread(thread, NULL))
|
||||
{
|
||||
DEBUG_ERROR("Failed to join software surface state worker");
|
||||
return false;
|
||||
}
|
||||
if (active && cancelRequested)
|
||||
{
|
||||
/* The backend may complete successfully at the same instant cancellation
|
||||
* is observed. Leave it inactive before reporting the cancelled result. */
|
||||
if (!ops->setActive(transport, false))
|
||||
DEBUG_ERROR("Failed to deactivate a cancelled software surface");
|
||||
return false;
|
||||
}
|
||||
return call.result;
|
||||
}
|
||||
24
client/src/sw_surface.h
Normal file
24
client/src/sw_surface.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _H_LG_CLIENT_SW_SURFACE_
|
||||
#define _H_LG_CLIENT_SW_SURFACE_
|
||||
|
||||
#include "interface/transport.h"
|
||||
|
||||
/* Runs setActive on a worker and joins it before returning. Cancellation is
|
||||
* observed only while activating; deactivation is allowed to complete unless
|
||||
* an owner invokes cancelPending from another thread. */
|
||||
bool lgSwSurface_setActive(const LG_SwSurfaceOps * ops,
|
||||
LG_Transport * transport, bool active,
|
||||
LG_TransportCancelledFn cancelled, void * opaque);
|
||||
|
||||
#endif
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "interface/transport.h"
|
||||
#include "dynamic/transports.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
void lgTransport_setup(void)
|
||||
@@ -51,10 +53,39 @@ bool lgTransport_create(const char * name, LG_TransportInstance * instance)
|
||||
if (strcmp(LG_Transports[i]->name, name) != 0)
|
||||
continue;
|
||||
|
||||
if (!LG_Transports[i]->create(&instance->handle))
|
||||
const LG_TransportOps * ops = LG_Transports[i];
|
||||
if (!ops->create || !ops->destroy || !ops->connectCancellable ||
|
||||
!ops->disconnect || !ops->sessionValid)
|
||||
{
|
||||
DEBUG_ERROR("Transport %s has an incomplete session interface", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ops->create(&instance->handle))
|
||||
return false;
|
||||
|
||||
instance->ops = LG_Transports[i];
|
||||
const LG_VideoOps * video = ops->getVideoOps ?
|
||||
ops->getVideoOps(instance->handle) : NULL;
|
||||
const bool badFrame = video && video->type == LG_VIDEO_TYPE_FRAME &&
|
||||
(!video->frame || !video->frame->nextFrame ||
|
||||
!video->frame->releaseFrame || !video->frame->cancelFrameWait ||
|
||||
((video->frame->nextPointer != NULL) !=
|
||||
(video->frame->releasePointer != NULL)) ||
|
||||
(video->frame->nextPointer && !video->frame->cancelPointerWait));
|
||||
const bool badSurface = video && video->type == LG_VIDEO_TYPE_SW_SURFACE &&
|
||||
(!video->swSurface || !video->swSurface->attach ||
|
||||
!video->swSurface->detach || !video->swSurface->setActive ||
|
||||
!video->swSurface->cancelPending);
|
||||
if (video && (badFrame || badSurface ||
|
||||
(video->type != LG_VIDEO_TYPE_FRAME &&
|
||||
video->type != LG_VIDEO_TYPE_SW_SURFACE)))
|
||||
{
|
||||
DEBUG_ERROR("Transport %s has an incomplete video interface", name);
|
||||
ops->destroy(&instance->handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->ops = ops;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "audio.h"
|
||||
#include "clipboard.h"
|
||||
#include "input.h"
|
||||
#include "sw_surface.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/event.h"
|
||||
@@ -44,6 +45,7 @@ struct LG_TransportFallback
|
||||
|
||||
LG_SwSurfaceEventOps surfaceEvents;
|
||||
void * surfaceOpaque;
|
||||
bool surfaceRequested;
|
||||
LG_TransportFallbackEventOps eventOps;
|
||||
void * eventOpaque;
|
||||
|
||||
@@ -55,7 +57,8 @@ struct LG_TransportFallback
|
||||
|
||||
atomic_bool stop;
|
||||
atomic_bool ready;
|
||||
atomic_bool admitted;
|
||||
atomic_bool usable;
|
||||
atomic_bool videoAcceptingEvents;
|
||||
|
||||
LG_TransportInstance transport;
|
||||
LG_TransportSession session;
|
||||
@@ -80,28 +83,48 @@ struct LG_TransportFallback
|
||||
|
||||
static bool applyVideoRequest(LG_TransportFallback * fallback);
|
||||
|
||||
struct VideoCallCancellation
|
||||
{
|
||||
LG_TransportFallback * fallback;
|
||||
uint64_t connectionSerial;
|
||||
bool requested;
|
||||
};
|
||||
|
||||
static bool connectCancelled(void * opaque)
|
||||
{
|
||||
const LG_TransportFallback * fallback = opaque;
|
||||
return atomic_load_explicit(&fallback->stop, memory_order_acquire);
|
||||
}
|
||||
|
||||
static bool uuidMismatchLocked(const LG_TransportFallback * fallback)
|
||||
static bool videoCallCancelled(void * opaque)
|
||||
{
|
||||
const struct VideoCallCancellation * cancellation = opaque;
|
||||
LG_TransportFallback * fallback = cancellation->fallback;
|
||||
LG_LOCK_SHARED(fallback->lock);
|
||||
const bool cancelled = atomic_load_explicit(
|
||||
&fallback->stop, memory_order_acquire) || fallback->closing ||
|
||||
fallback->connectionSerial != cancellation->connectionSerial ||
|
||||
fallback->videoRequested != cancellation->requested;
|
||||
LG_UNLOCK_SHARED(fallback->lock);
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
static bool endpointMismatchLocked(const LG_TransportFallback * fallback)
|
||||
{
|
||||
return fallback->primaryUUIDValid && fallback->session.uuidValid &&
|
||||
memcmp(fallback->primaryUUID, fallback->session.uuid,
|
||||
sizeof(fallback->primaryUUID)) != 0;
|
||||
}
|
||||
|
||||
static bool uuidAdmittedLocked(const LG_TransportFallback * fallback)
|
||||
static bool endpointMatchesLocked(const LG_TransportFallback * fallback)
|
||||
{
|
||||
return !uuidMismatchLocked(fallback);
|
||||
return !endpointMismatchLocked(fallback);
|
||||
}
|
||||
|
||||
static bool recordMismatchLocked(LG_TransportFallback * fallback,
|
||||
uint8_t primary[16], uint8_t remote[16])
|
||||
{
|
||||
if (!uuidMismatchLocked(fallback))
|
||||
if (!endpointMismatchLocked(fallback))
|
||||
return false;
|
||||
|
||||
const bool duplicate = fallback->mismatchReported &&
|
||||
@@ -133,9 +156,8 @@ static void notifyConnected(LG_TransportFallback * fallback)
|
||||
LG_LOCK(fallback->eventLock);
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool report =
|
||||
atomic_load_explicit(&fallback->ready, memory_order_acquire) &&
|
||||
atomic_load_explicit(&fallback->admitted, memory_order_acquire) &&
|
||||
uuidAdmittedLocked(fallback) && !fallback->closing &&
|
||||
atomic_load_explicit(&fallback->usable, memory_order_acquire) &&
|
||||
endpointMatchesLocked(fallback) && !fallback->closing &&
|
||||
!fallback->disconnectedReported;
|
||||
fallback->connectedReported = report;
|
||||
const LG_TransportSession session = fallback->session;
|
||||
@@ -174,6 +196,26 @@ static void notifyLost(LG_TransportFallback * fallback, bool requested)
|
||||
LG_UNLOCK(fallback->eventLock);
|
||||
}
|
||||
|
||||
static void notifyVideoState(LG_TransportFallback * fallback,
|
||||
uint64_t connectionSerial, bool ready)
|
||||
{
|
||||
if (!fallback->eventOps.videoStateChanged)
|
||||
return;
|
||||
|
||||
LG_LOCK(fallback->eventLock);
|
||||
LG_LOCK_SHARED(fallback->lock);
|
||||
const bool current = fallback->connectionSerial == connectionSerial &&
|
||||
atomic_load_explicit(&fallback->ready, memory_order_acquire) == ready &&
|
||||
(!ready || (atomic_load_explicit(
|
||||
&fallback->usable, memory_order_acquire) &&
|
||||
!fallback->closing && endpointMatchesLocked(fallback) &&
|
||||
fallback->videoRequested));
|
||||
LG_UNLOCK_SHARED(fallback->lock);
|
||||
if (current)
|
||||
fallback->eventOps.videoStateChanged(fallback->eventOpaque, ready);
|
||||
LG_UNLOCK(fallback->eventLock);
|
||||
}
|
||||
|
||||
static void unpublishProviders(LG_TransportFallback * fallback, bool live)
|
||||
{
|
||||
LG_LOCK(fallback->providerLock);
|
||||
@@ -199,11 +241,13 @@ static void unpublishProviders(LG_TransportFallback * fallback, bool live)
|
||||
LG_UNLOCK(fallback->providerLock);
|
||||
}
|
||||
|
||||
static void closeVideoAdmission(LG_TransportFallback * fallback)
|
||||
static void closeConnection(LG_TransportFallback * fallback)
|
||||
{
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
atomic_store_explicit(&fallback->ready, false, memory_order_release);
|
||||
atomic_store_explicit(&fallback->admitted, false, memory_order_release);
|
||||
atomic_store_explicit(&fallback->usable, false, memory_order_release);
|
||||
atomic_store_explicit(
|
||||
&fallback->videoAcceptingEvents, false, memory_order_release);
|
||||
fallback->closing = true;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
}
|
||||
@@ -211,7 +255,7 @@ static void closeVideoAdmission(LG_TransportFallback * fallback)
|
||||
static bool cleanupConnection(LG_TransportFallback * fallback,
|
||||
bool knownDead)
|
||||
{
|
||||
closeVideoAdmission(fallback);
|
||||
closeConnection(fallback);
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool reportDisconnected = fallback->connectedReported;
|
||||
@@ -224,9 +268,11 @@ static bool cleanupConnection(LG_TransportFallback * fallback,
|
||||
if (fallback->attached)
|
||||
{
|
||||
if (live && fallback->videoActive)
|
||||
fallback->videoOps->swSurface->setActive(
|
||||
fallback->transport.handle, false);
|
||||
lgSwSurface_setActive(fallback->videoOps->swSurface,
|
||||
fallback->transport.handle, false, NULL, NULL);
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
fallback->videoActive = false;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
fallback->videoOps->swSurface->detach(fallback->transport.handle);
|
||||
fallback->attached = false;
|
||||
}
|
||||
@@ -249,10 +295,10 @@ static bool publishProviders(LG_TransportFallback * fallback)
|
||||
{
|
||||
LG_LOCK(fallback->providerLock);
|
||||
LG_LOCK_SHARED(fallback->lock);
|
||||
const bool admitted = atomic_load_explicit(
|
||||
&fallback->admitted, memory_order_acquire) &&
|
||||
uuidAdmittedLocked(fallback) && !fallback->closing;
|
||||
if (!admitted)
|
||||
const bool usable = atomic_load_explicit(
|
||||
&fallback->usable, memory_order_acquire) &&
|
||||
endpointMatchesLocked(fallback) && !fallback->closing;
|
||||
if (!usable)
|
||||
{
|
||||
LG_UNLOCK_SHARED(fallback->lock);
|
||||
LG_UNLOCK(fallback->providerLock);
|
||||
@@ -283,75 +329,47 @@ static bool publishProviders(LG_TransportFallback * fallback)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool admitConnection(LG_TransportFallback * fallback,
|
||||
static bool markConnectionUsable(LG_TransportFallback * fallback,
|
||||
bool * reportMismatch, uint8_t primaryUUID[16],
|
||||
uint8_t fallbackUUID[16])
|
||||
{
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool reject = uuidMismatchLocked(fallback);
|
||||
if (reject)
|
||||
const bool mismatch = endpointMismatchLocked(fallback);
|
||||
if (mismatch)
|
||||
*reportMismatch = recordMismatchLocked(
|
||||
fallback, primaryUUID, fallbackUUID);
|
||||
const bool stop = atomic_load_explicit(
|
||||
&fallback->stop, memory_order_acquire);
|
||||
const bool admitted = !reject && !stop && !fallback->closing;
|
||||
if (admitted)
|
||||
const bool usable = !mismatch && !stop && !fallback->closing;
|
||||
if (usable)
|
||||
atomic_store_explicit(
|
||||
&fallback->admitted, true, memory_order_release);
|
||||
&fallback->usable, true, memory_order_release);
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
return admitted;
|
||||
return usable;
|
||||
}
|
||||
|
||||
static bool publishConnection(LG_TransportFallback * fallback,
|
||||
bool * reportMismatch, uint8_t primaryUUID[16],
|
||||
uint8_t fallbackUUID[16])
|
||||
{
|
||||
if (!admitConnection(fallback, reportMismatch,
|
||||
if (!markConnectionUsable(fallback, reportMismatch,
|
||||
primaryUUID, fallbackUUID))
|
||||
return false;
|
||||
|
||||
if (!publishProviders(fallback))
|
||||
return false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool reject = uuidMismatchLocked(fallback);
|
||||
if (reject)
|
||||
*reportMismatch = recordMismatchLocked(
|
||||
fallback, primaryUUID, fallbackUUID);
|
||||
const bool admitted = uuidAdmittedLocked(fallback);
|
||||
const bool stop = atomic_load_explicit(
|
||||
&fallback->stop, memory_order_acquire);
|
||||
const bool closing = fallback->closing;
|
||||
const bool requested = fallback->videoRequested;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
if (reject || !admitted || stop || closing)
|
||||
return false;
|
||||
if (requested == fallback->videoActive)
|
||||
break;
|
||||
|
||||
if (!fallback->videoOps->swSurface->setActive(
|
||||
fallback->transport.handle, requested))
|
||||
break;
|
||||
fallback->videoActive = requested;
|
||||
}
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool finalReject = uuidMismatchLocked(fallback);
|
||||
if (finalReject)
|
||||
const bool finalMismatch = endpointMismatchLocked(fallback);
|
||||
if (finalMismatch)
|
||||
*reportMismatch = recordMismatchLocked(
|
||||
fallback, primaryUUID, fallbackUUID);
|
||||
const bool finalAdmitted = uuidAdmittedLocked(fallback);
|
||||
const bool finalMatches = endpointMatchesLocked(fallback);
|
||||
const bool finalStop = atomic_load_explicit(
|
||||
&fallback->stop, memory_order_acquire);
|
||||
if (!finalReject && finalAdmitted && !finalStop && !fallback->closing)
|
||||
{
|
||||
if (!finalMismatch && finalMatches && !finalStop && !fallback->closing)
|
||||
fallback->mismatchReported = false;
|
||||
atomic_store_explicit(&fallback->ready, true, memory_order_release);
|
||||
}
|
||||
const bool published = !finalReject && finalAdmitted && !finalStop &&
|
||||
const bool published = !finalMismatch && finalMatches && !finalStop &&
|
||||
!fallback->closing;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
return published;
|
||||
@@ -367,37 +385,13 @@ static bool connectFallback(LG_TransportFallback * fallback)
|
||||
return false;
|
||||
}
|
||||
|
||||
const LG_VideoOps * videoOps = transport.ops->getVideoOps ?
|
||||
transport.ops->getVideoOps(transport.handle) : NULL;
|
||||
if (!videoOps || videoOps->type != LG_VIDEO_TYPE_SW_SURFACE ||
|
||||
!videoOps->swSurface || !videoOps->swSurface->attach ||
|
||||
!videoOps->swSurface->detach || !videoOps->swSurface->setActive)
|
||||
{
|
||||
DEBUG_ERROR("Fallback transport %s does not provide a software surface",
|
||||
fallback->transportName);
|
||||
lgTransport_destroy(&transport);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!videoOps->swSurface->attach(transport.handle,
|
||||
&fallback->surfaceEvents, fallback->surfaceOpaque))
|
||||
{
|
||||
DEBUG_ERROR("Failed to attach fallback software surface");
|
||||
lgTransport_destroy(&transport);
|
||||
return false;
|
||||
}
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
fallback->transport = transport;
|
||||
fallback->videoOps = videoOps;
|
||||
fallback->attached = true;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
LG_TransportSession session = { 0 };
|
||||
const LG_TransportStatus status = transport.ops->connectCancellable ?
|
||||
transport.ops->connectCancellable(transport.handle, &session,
|
||||
connectCancelled, fallback) :
|
||||
transport.ops->connect(transport.handle, &session);
|
||||
const LG_TransportStatus status = transport.ops->connectCancellable(
|
||||
transport.handle, &session, connectCancelled, fallback);
|
||||
|
||||
if (status != LG_TRANSPORT_OK)
|
||||
{
|
||||
@@ -415,6 +409,26 @@ static bool connectFallback(LG_TransportFallback * fallback)
|
||||
++fallback->connectionSerial;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
const LG_VideoOps * videoOps = transport.ops->getVideoOps ?
|
||||
transport.ops->getVideoOps(transport.handle) : NULL;
|
||||
bool attached = false;
|
||||
if (fallback->surfaceRequested && videoOps &&
|
||||
videoOps->type == LG_VIDEO_TYPE_SW_SURFACE &&
|
||||
videoOps->swSurface && videoOps->swSurface->attach &&
|
||||
videoOps->swSurface->detach && videoOps->swSurface->setActive &&
|
||||
videoOps->swSurface->cancelPending)
|
||||
{
|
||||
attached = videoOps->swSurface->attach(transport.handle,
|
||||
&fallback->surfaceEvents, fallback->surfaceOpaque);
|
||||
if (!attached)
|
||||
DEBUG_WARN("Failed to attach fallback video source; continuing without it");
|
||||
}
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
fallback->videoOps = attached ? videoOps : NULL;
|
||||
fallback->attached = attached;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
bool reportMismatch = false;
|
||||
uint8_t primaryUUID[16];
|
||||
uint8_t fallbackUUID[16];
|
||||
@@ -422,13 +436,14 @@ static bool connectFallback(LG_TransportFallback * fallback)
|
||||
primaryUUID, fallbackUUID))
|
||||
{
|
||||
cleanupConnection(fallback, false);
|
||||
if (reportMismatch && fallback->eventOps.uuidMismatch)
|
||||
fallback->eventOps.uuidMismatch(
|
||||
if (reportMismatch && fallback->eventOps.endpointMismatch)
|
||||
fallback->eventOps.endpointMismatch(
|
||||
fallback->eventOpaque, primaryUUID, fallbackUUID);
|
||||
return false;
|
||||
}
|
||||
|
||||
notifyConnected(fallback);
|
||||
applyVideoRequest(fallback);
|
||||
|
||||
bool lost = false;
|
||||
while (!atomic_load_explicit(&fallback->stop, memory_order_acquire))
|
||||
@@ -439,14 +454,14 @@ static bool connectFallback(LG_TransportFallback * fallback)
|
||||
applyVideoRequest(fallback);
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool reject = uuidMismatchLocked(fallback);
|
||||
if (reject)
|
||||
const bool mismatch = endpointMismatchLocked(fallback);
|
||||
if (mismatch)
|
||||
reportMismatch = recordMismatchLocked(
|
||||
fallback, primaryUUID, fallbackUUID);
|
||||
const bool admitted = uuidAdmittedLocked(fallback);
|
||||
const bool matches = endpointMatchesLocked(fallback);
|
||||
const bool closing = fallback->closing;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
if (reject || !admitted || closing)
|
||||
if (mismatch || !matches || closing)
|
||||
break;
|
||||
if (!sessionLive(fallback))
|
||||
{
|
||||
@@ -458,8 +473,8 @@ static bool connectFallback(LG_TransportFallback * fallback)
|
||||
|
||||
const bool reportDisconnect = cleanupConnection(fallback, lost);
|
||||
|
||||
if (reportMismatch && fallback->eventOps.uuidMismatch)
|
||||
fallback->eventOps.uuidMismatch(
|
||||
if (reportMismatch && fallback->eventOps.endpointMismatch)
|
||||
fallback->eventOps.endpointMismatch(
|
||||
fallback->eventOpaque, primaryUUID, fallbackUUID);
|
||||
const bool reportLost = lost && reportDisconnect && !atomic_load_explicit(
|
||||
&fallback->stop, memory_order_acquire);
|
||||
@@ -500,7 +515,7 @@ bool lgTransportFallback_start(const char * transportName,
|
||||
return false;
|
||||
|
||||
*result = NULL;
|
||||
if (!transportName || !*transportName || !surfaceEvents ||
|
||||
if (!transportName || !*transportName ||
|
||||
!lgTransport_isValid(transportName))
|
||||
return false;
|
||||
|
||||
@@ -513,13 +528,18 @@ bool lgTransportFallback_start(const char * transportName,
|
||||
LG_LOCK_INIT(fallback->eventLock);
|
||||
atomic_init(&fallback->stop, false);
|
||||
atomic_init(&fallback->ready, false);
|
||||
atomic_init(&fallback->admitted, false);
|
||||
atomic_init(&fallback->usable, false);
|
||||
atomic_init(&fallback->videoAcceptingEvents, false);
|
||||
|
||||
fallback->transportName = strdup(transportName);
|
||||
if (!fallback->transportName)
|
||||
goto fail;
|
||||
|
||||
fallback->surfaceEvents = *surfaceEvents;
|
||||
if (surfaceEvents)
|
||||
{
|
||||
fallback->surfaceEvents = *surfaceEvents;
|
||||
fallback->surfaceRequested = true;
|
||||
}
|
||||
fallback->surfaceOpaque = surfaceOpaque;
|
||||
if (eventOps)
|
||||
fallback->eventOps = *eventOps;
|
||||
@@ -587,10 +607,17 @@ bool lgTransportFallback_ready(const LG_TransportFallback * fallback)
|
||||
&fallback->ready, memory_order_acquire);
|
||||
}
|
||||
|
||||
bool lgTransportFallback_admitted(const LG_TransportFallback * fallback)
|
||||
bool lgTransportFallback_usable(const LG_TransportFallback * fallback)
|
||||
{
|
||||
return fallback && atomic_load_explicit(
|
||||
&fallback->admitted, memory_order_acquire);
|
||||
&fallback->usable, memory_order_acquire);
|
||||
}
|
||||
|
||||
bool lgTransportFallback_acceptsVideoEvents(
|
||||
const LG_TransportFallback * fallback)
|
||||
{
|
||||
return fallback && atomic_load_explicit(
|
||||
&fallback->videoAcceptingEvents, memory_order_acquire);
|
||||
}
|
||||
|
||||
bool lgTransportFallback_videoRequested(
|
||||
@@ -615,8 +642,8 @@ static bool applyVideoRequest(LG_TransportFallback * fallback)
|
||||
bool requested;
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
if (!atomic_load_explicit(&fallback->ready, memory_order_acquire) ||
|
||||
fallback->closing)
|
||||
if (!atomic_load_explicit(&fallback->usable, memory_order_acquire) ||
|
||||
!fallback->attached || !fallback->videoOps || fallback->closing)
|
||||
{
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
return false;
|
||||
@@ -632,19 +659,49 @@ static bool applyVideoRequest(LG_TransportFallback * fallback)
|
||||
connectionSerial = fallback->connectionSerial;
|
||||
transport = fallback->transport.handle;
|
||||
surfaceOps = fallback->videoOps->swSurface;
|
||||
atomic_store_explicit(
|
||||
&fallback->ready, false, memory_order_release);
|
||||
atomic_store_explicit(&fallback->videoAcceptingEvents,
|
||||
requested, memory_order_release);
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
const bool result = surfaceOps->setActive(transport, requested);
|
||||
const struct VideoCallCancellation cancellation =
|
||||
{
|
||||
.fallback = fallback,
|
||||
.connectionSerial = connectionSerial,
|
||||
.requested = requested,
|
||||
};
|
||||
const bool result = lgSwSurface_setActive(surfaceOps, transport,
|
||||
requested, videoCallCancelled, (void *)&cancellation);
|
||||
|
||||
LG_LOCK_EXCLUSIVE(fallback->lock);
|
||||
const bool current = fallback->connectionSerial == connectionSerial;
|
||||
if (current && result)
|
||||
fallback->videoActive = requested;
|
||||
const bool accepted = current && !fallback->closing && result;
|
||||
const bool desiredCurrent = current &&
|
||||
fallback->videoRequested == requested;
|
||||
const bool usable = desiredCurrent && result &&
|
||||
atomic_load_explicit(&fallback->usable, memory_order_acquire) &&
|
||||
!atomic_load_explicit(&fallback->stop, memory_order_acquire) &&
|
||||
!fallback->closing && endpointMatchesLocked(fallback);
|
||||
if (current)
|
||||
{
|
||||
if (result)
|
||||
fallback->videoActive = requested;
|
||||
else if (requested)
|
||||
fallback->videoActive = false;
|
||||
const bool ready = usable && requested;
|
||||
atomic_store_explicit(
|
||||
&fallback->ready, ready, memory_order_release);
|
||||
atomic_store_explicit(&fallback->videoAcceptingEvents,
|
||||
ready, memory_order_release);
|
||||
}
|
||||
const bool accepted = usable;
|
||||
const bool retry = current && !fallback->closing &&
|
||||
fallback->videoRequested != requested;
|
||||
LG_UNLOCK_EXCLUSIVE(fallback->lock);
|
||||
|
||||
if (current)
|
||||
notifyVideoState(fallback, connectionSerial, accepted && requested);
|
||||
|
||||
if (!retry)
|
||||
return accepted;
|
||||
}
|
||||
@@ -662,16 +719,19 @@ void lgTransportFallback_requestVideoActive(
|
||||
lgSignalEvent(fallback->wakeEvent);
|
||||
}
|
||||
|
||||
static bool revokeAdmissionLocked(LG_TransportFallback * fallback)
|
||||
static bool clearUsableLocked(LG_TransportFallback * fallback)
|
||||
{
|
||||
atomic_store_explicit(&fallback->ready, false, memory_order_release);
|
||||
atomic_store_explicit(&fallback->admitted, false, memory_order_release);
|
||||
atomic_store_explicit(&fallback->usable, false, memory_order_release);
|
||||
atomic_store_explicit(
|
||||
&fallback->videoAcceptingEvents, false, memory_order_release);
|
||||
if (!fallback->connected)
|
||||
return false;
|
||||
|
||||
const bool reportDisconnected = fallback->connectedReported;
|
||||
fallback->connectedReported = false;
|
||||
fallback->closing = true;
|
||||
return true;
|
||||
return reportDisconnected;
|
||||
}
|
||||
|
||||
void lgTransportFallback_setPrimaryUUID(
|
||||
@@ -680,7 +740,7 @@ void lgTransportFallback_setPrimaryUUID(
|
||||
if (!fallback || !uuid)
|
||||
return;
|
||||
|
||||
bool revoked = false;
|
||||
bool mismatchTransition = false;
|
||||
bool notifyDisconnect = false;
|
||||
bool reportMismatch = false;
|
||||
uint8_t primaryUUID[16];
|
||||
@@ -694,10 +754,10 @@ void lgTransportFallback_setPrimaryUUID(
|
||||
{
|
||||
memcpy(fallback->primaryUUID, uuid, sizeof(fallback->primaryUUID));
|
||||
fallback->primaryUUIDValid = true;
|
||||
if (uuidMismatchLocked(fallback))
|
||||
if (endpointMismatchLocked(fallback))
|
||||
{
|
||||
revoked = true;
|
||||
notifyDisconnect = revokeAdmissionLocked(fallback);
|
||||
mismatchTransition = true;
|
||||
notifyDisconnect = clearUsableLocked(fallback);
|
||||
reportMismatch = recordMismatchLocked(
|
||||
fallback, primaryUUID, fallbackUUID);
|
||||
}
|
||||
@@ -706,11 +766,11 @@ void lgTransportFallback_setPrimaryUUID(
|
||||
|
||||
if (changed)
|
||||
lgSignalEvent(fallback->wakeEvent);
|
||||
if (revoked)
|
||||
if (mismatchTransition)
|
||||
unpublishProviders(fallback, false);
|
||||
notifyDisconnected(fallback, notifyDisconnect);
|
||||
if (reportMismatch && fallback->eventOps.uuidMismatch)
|
||||
fallback->eventOps.uuidMismatch(
|
||||
if (reportMismatch && fallback->eventOps.endpointMismatch)
|
||||
fallback->eventOps.endpointMismatch(
|
||||
fallback->eventOpaque, primaryUUID, fallbackUUID);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,12 @@ typedef struct LG_TransportFallbackEventOps
|
||||
{
|
||||
/* The session is valid only for the duration of this callback. */
|
||||
void (*connected)(void * opaque, const LG_TransportSession * session);
|
||||
/* Called only when an admitted session is unexpectedly lost. */
|
||||
/* Called only when a usable session is unexpectedly lost. */
|
||||
void (*lost)(void * opaque);
|
||||
void (*disconnected)(void * opaque);
|
||||
void (*uuidMismatch)(void * opaque, const uint8_t primary[16],
|
||||
/* Reports completion of an optional video state request. */
|
||||
void (*videoStateChanged)(void * opaque, bool ready);
|
||||
void (*endpointMismatch)(void * opaque, const uint8_t primary[16],
|
||||
const uint8_t fallback[16]);
|
||||
}
|
||||
LG_TransportFallbackEventOps;
|
||||
@@ -47,8 +49,13 @@ bool lgTransportFallback_start(const char * transportName,
|
||||
LG_TransportFallback ** result);
|
||||
void lgTransportFallback_stop(LG_TransportFallback ** fallback);
|
||||
|
||||
/* ready reports optional video availability; usable reports session services. */
|
||||
bool lgTransportFallback_ready(const LG_TransportFallback * fallback);
|
||||
bool lgTransportFallback_admitted(const LG_TransportFallback * fallback);
|
||||
bool lgTransportFallback_usable(const LG_TransportFallback * fallback);
|
||||
/* True while video callbacks belong to the current activation attempt or
|
||||
* confirmed active state. */
|
||||
bool lgTransportFallback_acceptsVideoEvents(
|
||||
const LG_TransportFallback * fallback);
|
||||
bool lgTransportFallback_videoRequested(
|
||||
LG_TransportFallback * fallback);
|
||||
/* The requested state is retained across reconnects. */
|
||||
|
||||
@@ -4,6 +4,20 @@ add_executable(font-tests
|
||||
font_test.c
|
||||
../src/font.c
|
||||
)
|
||||
|
||||
add_executable(sw-surface-tests
|
||||
sw_surface_test.c
|
||||
../src/sw_surface.c
|
||||
)
|
||||
target_include_directories(sw-surface-tests PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
|
||||
)
|
||||
target_link_libraries(sw-surface-tests
|
||||
${EXE_FLAGS}
|
||||
lg_common
|
||||
)
|
||||
add_test(NAME sw-surface-tests COMMAND sw-surface-tests)
|
||||
set_tests_properties(sw-surface-tests PROPERTIES TIMEOUT 10)
|
||||
target_compile_definitions(font-tests PRIVATE
|
||||
FONT_TEST_FILE="${PROJECT_TOP}/repos/gui/cimgui/imgui/misc/fonts/DroidSans.ttf"
|
||||
)
|
||||
@@ -427,6 +441,7 @@ endforeach()
|
||||
add_executable(transport-fallback-tests
|
||||
transport_fallback_test.c
|
||||
../src/transport_fallback.c
|
||||
../src/sw_surface.c
|
||||
)
|
||||
target_include_directories(transport-fallback-tests PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
|
||||
@@ -444,9 +459,18 @@ set(TRANSPORT_FALLBACK_CASES
|
||||
graceful
|
||||
dead
|
||||
mismatch
|
||||
revoke
|
||||
missing-uuid
|
||||
late-mismatch
|
||||
retry
|
||||
cancel
|
||||
no-video
|
||||
attach-fail
|
||||
active-fail
|
||||
activation-cancel
|
||||
activation-request-cancel
|
||||
deactivation-completes
|
||||
activation-mismatch
|
||||
pre-requested-activation
|
||||
)
|
||||
foreach(name IN LISTS TRANSPORT_FALLBACK_CASES)
|
||||
add_test(NAME transport-fallback-${name}
|
||||
@@ -482,6 +506,7 @@ set(SPICE_CASES
|
||||
ready-drop
|
||||
session-order
|
||||
surface-active
|
||||
surface-cancel
|
||||
surface-events
|
||||
surface-detach
|
||||
)
|
||||
@@ -494,6 +519,30 @@ foreach(name IN LISTS SPICE_CASES)
|
||||
)
|
||||
endforeach()
|
||||
|
||||
add_executable(purespice-connect-cancel-tests
|
||||
purespice_connect_cancel_test.c
|
||||
)
|
||||
target_compile_definitions(purespice PRIVATE PURESPICE_TESTING)
|
||||
target_compile_definitions(purespice-connect-cancel-tests PRIVATE
|
||||
PURESPICE_TESTING
|
||||
)
|
||||
target_include_directories(purespice-connect-cancel-tests PRIVATE
|
||||
"${PROJECT_TOP}/repos/PureSpice/include"
|
||||
"${PROJECT_TOP}/repos/PureSpice/src"
|
||||
"$<TARGET_PROPERTY:purespice,INCLUDE_DIRECTORIES>"
|
||||
)
|
||||
target_link_libraries(purespice-connect-cancel-tests
|
||||
${EXE_FLAGS}
|
||||
purespice
|
||||
pthread
|
||||
)
|
||||
add_test(NAME purespice-connect-cancel
|
||||
COMMAND purespice-connect-cancel-tests
|
||||
)
|
||||
set_tests_properties(purespice-connect-cancel PROPERTIES
|
||||
TIMEOUT 5
|
||||
)
|
||||
|
||||
add_executable(frame-scheduler-tests
|
||||
frame_scheduler_test.c
|
||||
../src/frame_scheduler.c
|
||||
@@ -534,6 +583,7 @@ add_executable(frame-timing-tests
|
||||
target_compile_definitions(frame-timing-tests PRIVATE
|
||||
CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1
|
||||
)
|
||||
target_compile_options(frame-timing-tests PRIVATE -O1)
|
||||
target_include_directories(frame-timing-tests PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
|
||||
"${PROJECT_TOP}/repos/gui/cimgui"
|
||||
@@ -547,11 +597,18 @@ set(FRAME_TIMING_CASES
|
||||
lifecycle
|
||||
order
|
||||
untracked
|
||||
accounting
|
||||
provider-unavailable
|
||||
timeout
|
||||
fifo
|
||||
retire
|
||||
feedback
|
||||
wrap
|
||||
component-epoch
|
||||
payload-gate
|
||||
frame-payload-bookkeeping
|
||||
connect-cancel
|
||||
recovery-errors
|
||||
)
|
||||
foreach(name IN LISTS FRAME_TIMING_CASES)
|
||||
add_test(NAME frame-timing-${name}
|
||||
@@ -662,6 +719,7 @@ set(RENDER_QUEUE_CASES
|
||||
payload
|
||||
validation
|
||||
cache
|
||||
cursor-replacement
|
||||
)
|
||||
foreach(name IN LISTS RENDER_QUEUE_CASES)
|
||||
add_test(NAME render-queue-${name}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#undef main
|
||||
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#define TEST_ALARM_S 5U
|
||||
|
||||
@@ -39,6 +40,202 @@ struct Feedback
|
||||
};
|
||||
|
||||
static struct Feedback feed;
|
||||
static unsigned int rawConnectCalls;
|
||||
static unsigned int cancellableConnectCalls;
|
||||
static bool connectSawCancellation;
|
||||
|
||||
uint64_t renderQueue_sourceBegin(RenderQueueSource source)
|
||||
{
|
||||
(void)source;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void renderQueue_sourceInvalidate(RenderQueueSource source,
|
||||
uint64_t generation)
|
||||
{
|
||||
(void)source;
|
||||
(void)generation;
|
||||
}
|
||||
|
||||
void renderQueue_sourceClearCursor(RenderQueueSource source)
|
||||
{
|
||||
(void)source;
|
||||
}
|
||||
|
||||
bool renderQueue_sourceSwSurfaceConfigure(RenderQueueSource source,
|
||||
uint64_t generation, int width, int height)
|
||||
{
|
||||
(void)source;
|
||||
(void)generation;
|
||||
(void)width;
|
||||
(void)height;
|
||||
return true;
|
||||
}
|
||||
|
||||
void renderQueue_sourceSwSurfaceDrawFill(RenderQueueSource source,
|
||||
uint64_t generation, int x, int y, int width, int height,
|
||||
uint32_t color)
|
||||
{
|
||||
(void)source;
|
||||
(void)generation;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)width;
|
||||
(void)height;
|
||||
(void)color;
|
||||
}
|
||||
|
||||
void renderQueue_sourceSwSurfaceDrawBitmap(RenderQueueSource source,
|
||||
uint64_t generation, int x, int y, int width, int height, int stride,
|
||||
const void * data, bool topDown)
|
||||
{
|
||||
(void)source;
|
||||
(void)generation;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)width;
|
||||
(void)height;
|
||||
(void)stride;
|
||||
(void)data;
|
||||
(void)topDown;
|
||||
}
|
||||
|
||||
void renderQueue_sourceCursorState(RenderQueueSource source,
|
||||
uint64_t generation, bool visible, int x, int y, int hx, int hy)
|
||||
{
|
||||
(void)source;
|
||||
(void)generation;
|
||||
(void)visible;
|
||||
(void)x;
|
||||
(void)y;
|
||||
(void)hx;
|
||||
(void)hy;
|
||||
}
|
||||
|
||||
void renderQueue_sourceCursorImage(RenderQueueSource source,
|
||||
uint64_t generation, LG_RendererCursor type,
|
||||
int width, int height, int pitch, const void * data)
|
||||
{
|
||||
(void)source;
|
||||
(void)generation;
|
||||
(void)type;
|
||||
(void)width;
|
||||
(void)height;
|
||||
(void)pitch;
|
||||
(void)data;
|
||||
}
|
||||
|
||||
void renderQueue_sourceCursorColorTransform(RenderQueueSource source,
|
||||
uint64_t generation, const LGColorTransform * transform)
|
||||
{
|
||||
(void)source;
|
||||
(void)generation;
|
||||
(void)transform;
|
||||
}
|
||||
|
||||
void renderQueue_sourceCursorWhiteLevel(RenderQueueSource source,
|
||||
uint64_t generation, uint32_t sdrWhiteLevel)
|
||||
{
|
||||
(void)source;
|
||||
(void)generation;
|
||||
(void)sdrWhiteLevel;
|
||||
}
|
||||
|
||||
void overlaySplash_show(bool show)
|
||||
{
|
||||
(void)show;
|
||||
}
|
||||
|
||||
bool lgTransportFallback_usable(const LG_TransportFallback * fallback)
|
||||
{
|
||||
(void)fallback;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool lgTransportFallback_ready(const LG_TransportFallback * fallback)
|
||||
{
|
||||
(void)fallback;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool lgTransportFallback_acceptsVideoEvents(
|
||||
const LG_TransportFallback * fallback)
|
||||
{
|
||||
(void)fallback;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool lgTransportFallback_videoRequested(LG_TransportFallback * fallback)
|
||||
{
|
||||
(void)fallback;
|
||||
return false;
|
||||
}
|
||||
|
||||
void lgTransportFallback_requestVideoActive(
|
||||
LG_TransportFallback * fallback, bool active)
|
||||
{
|
||||
(void)fallback;
|
||||
(void)active;
|
||||
}
|
||||
|
||||
bool lgInput_available(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool core_inputEnabled(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void core_alignToGuest(void) {}
|
||||
void core_handleGuestMouseUpdate(void) {}
|
||||
void core_setTitle(const char * title) { (void)title; }
|
||||
|
||||
enum RunState app_getState(void)
|
||||
{
|
||||
return atomic_load_explicit(&p_appState, memory_order_acquire);
|
||||
}
|
||||
|
||||
void app_setState(enum RunState state)
|
||||
{
|
||||
atomic_store_explicit(&p_appState, state, memory_order_release);
|
||||
}
|
||||
|
||||
void app_invalidateWindow(bool full)
|
||||
{
|
||||
(void)full;
|
||||
}
|
||||
|
||||
void app_refreshVideoSource(void) {}
|
||||
void app_updateMouseState(void) {}
|
||||
|
||||
static bool cancelled(void * opaque)
|
||||
{
|
||||
(void)opaque;
|
||||
return true;
|
||||
}
|
||||
|
||||
static LG_TransportStatus rawConnect(LG_Transport * transport,
|
||||
LG_TransportSession * session)
|
||||
{
|
||||
(void)transport;
|
||||
(void)session;
|
||||
++rawConnectCalls;
|
||||
return LG_TRANSPORT_ERROR;
|
||||
}
|
||||
|
||||
static LG_TransportStatus cancellableConnect(LG_Transport * transport,
|
||||
LG_TransportSession * session, LG_TransportCancelledFn cancelled,
|
||||
void * opaque)
|
||||
{
|
||||
(void)transport;
|
||||
(void)session;
|
||||
++cancellableConnectCalls;
|
||||
connectSawCancellation = cancelled && cancelled(opaque);
|
||||
return connectSawCancellation ? LG_TRANSPORT_DISCONNECTED :
|
||||
LG_TRANSPORT_OK;
|
||||
}
|
||||
|
||||
void frameScheduler_feedback(uint64_t frameSerial, uint32_t generation,
|
||||
uint32_t scheduleEpoch, uint32_t deadlineSerial,
|
||||
@@ -57,6 +254,7 @@ static LG_TransportFrameTiming srcTiming(bool valid, bool phase)
|
||||
return (LG_TransportFrameTiming)
|
||||
{
|
||||
.valid = valid,
|
||||
.providerValid = true,
|
||||
.phaseValid = phase,
|
||||
.scheduleGeneration = 2,
|
||||
.scheduleEpoch = 3,
|
||||
@@ -66,7 +264,9 @@ static LG_TransportFrameTiming srcTiming(bool valid, bool phase)
|
||||
.copyTime = 13,
|
||||
.readyTime = 14,
|
||||
.holdTime = 15,
|
||||
.readyLeadTime = 100,
|
||||
.readyLeadTime = 110,
|
||||
.receiveTime = 2,
|
||||
.prepareTime = 3,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -130,7 +330,7 @@ static void testLifecycle(void)
|
||||
CHECK(frameTimingQueuedToken() == first);
|
||||
CHECK(frameTimingRecord(first)->dispatchTime == 35);
|
||||
frameTimingCancel(first);
|
||||
app_handleFramePresented(first, 200, true);
|
||||
main_framePresented(first, 200, true);
|
||||
CHECK(frameTimingRecord(first)->token == LG_RENDERER_FRAME_TOKEN_NONE);
|
||||
|
||||
const LG_RendererFrameToken second = frameTimingReserve();
|
||||
@@ -149,7 +349,7 @@ static void testOrder(void)
|
||||
const LG_RendererFrameToken token = frameTimingReserve();
|
||||
queue(token, &timing, 55);
|
||||
|
||||
app_handleFramePresented(token, 500, true);
|
||||
main_framePresented(token, 500, true);
|
||||
const LG_RendererFrameTiming render = dstTiming(token, true);
|
||||
frameTimingFinishRender(&render, 200, 7, 300, token);
|
||||
frameTimingPublishReady();
|
||||
@@ -161,12 +361,15 @@ static void testOrder(void)
|
||||
const OverlayFrameTiming out = take();
|
||||
CHECK(out.validMask == OVERLAY_FRAME_TIMING_VALID_ALL);
|
||||
CHECK(near(out.capture, 0.000011f));
|
||||
CHECK(near(out.transport, 0.000010f));
|
||||
CHECK(near(out.receive, 0.000002f));
|
||||
CHECK(near(out.providerPrepare, 0.000003f));
|
||||
CHECK(near(out.dispatch, 0.000035f));
|
||||
CHECK(near(out.queue, 0.000050f));
|
||||
CHECK(near(out.present, 0.000500f));
|
||||
|
||||
frameTimingFinishFrame(token, &timing);
|
||||
app_handleFramePresented(token, 600, true);
|
||||
main_framePresented(token, 600, true);
|
||||
frameTimingFinishRender(&render, 200, 7, 300, token);
|
||||
frameTimingPublishReady();
|
||||
CHECK(ringbuffer_getCount(g_state.frameLatency) == 0);
|
||||
@@ -188,11 +391,59 @@ static void testUntracked(void)
|
||||
const OverlayFrameTiming out = take();
|
||||
CHECK(!(out.validMask & OVERLAY_FRAME_TIMING_VALID_PRODUCER));
|
||||
CHECK(!(out.validMask & OVERLAY_FRAME_TIMING_VALID_TRANSPORT));
|
||||
CHECK((out.validMask & OVERLAY_FRAME_TIMING_VALID_PROVIDER) ==
|
||||
OVERLAY_FRAME_TIMING_VALID_PROVIDER);
|
||||
CHECK(!(out.validMask & OVERLAY_FRAME_TIMING_VALID_PRESENT));
|
||||
CHECK(near(out.receive, 0.000002f));
|
||||
CHECK(near(out.providerPrepare, 0.000003f));
|
||||
CHECK(out.present == 0.0f);
|
||||
finish();
|
||||
}
|
||||
|
||||
static void testAccounting(void)
|
||||
{
|
||||
start();
|
||||
LG_TransportFrameTiming timing = srcTiming(true, true);
|
||||
timing.readyLeadTime = 1;
|
||||
timing.receiveTime = UINT64_MAX;
|
||||
timing.prepareTime = UINT64_MAX;
|
||||
const LG_RendererFrameToken token = frameTimingReserve();
|
||||
frameTimingQueue(token, 67, &timing, UINT64_MAX, UINT64_MAX, 100, 150);
|
||||
frameTimingFinishFrame(token, &timing);
|
||||
const LG_RendererFrameTiming render = dstTiming(token, false);
|
||||
frameTimingFinishRender(&render, 200, 7, 300, token);
|
||||
frameTimingPublishReady();
|
||||
CHECK(ringbuffer_getCount(g_state.frameLatency) == 1);
|
||||
const OverlayFrameTiming out = take();
|
||||
CHECK(out.validMask & OVERLAY_FRAME_TIMING_VALID_TRANSPORT);
|
||||
CHECK((out.validMask & OVERLAY_FRAME_TIMING_VALID_PROVIDER) ==
|
||||
OVERLAY_FRAME_TIMING_VALID_PROVIDER);
|
||||
CHECK(out.transport == 0.0f);
|
||||
CHECK(out.import ==
|
||||
(float)frameTimingAdd(UINT64_MAX, UINT64_MAX) * 1e-6f);
|
||||
finish();
|
||||
}
|
||||
|
||||
static void testProviderUnavailable(void)
|
||||
{
|
||||
start();
|
||||
LG_TransportFrameTiming timing = srcTiming(true, true);
|
||||
timing.providerValid = false;
|
||||
const LG_RendererFrameToken token = frameTimingReserve();
|
||||
queue(token, &timing, 68);
|
||||
frameTimingFinishFrame(token, &timing);
|
||||
const LG_RendererFrameTiming render = dstTiming(token, false);
|
||||
frameTimingFinishRender(&render, 200, 7, 300, token);
|
||||
frameTimingPublishReady();
|
||||
CHECK(ringbuffer_getCount(g_state.frameLatency) == 1);
|
||||
const OverlayFrameTiming out = take();
|
||||
CHECK(out.validMask & OVERLAY_FRAME_TIMING_VALID_PRODUCER);
|
||||
CHECK(out.validMask & OVERLAY_FRAME_TIMING_VALID_TRANSPORT);
|
||||
CHECK(!(out.validMask & OVERLAY_FRAME_TIMING_VALID_PROVIDER));
|
||||
CHECK(near(out.transport, 0.000015f));
|
||||
finish();
|
||||
}
|
||||
|
||||
static void testTimeout(void)
|
||||
{
|
||||
start();
|
||||
@@ -321,6 +572,187 @@ static void testWrap(void)
|
||||
finish();
|
||||
}
|
||||
|
||||
static void testComponentEpoch(void)
|
||||
{
|
||||
atomic_bool available = true;
|
||||
atomic_uint_least64_t epoch = 7;
|
||||
atomic_int reason = LG_TRANSPORT_OK;
|
||||
CHECK(videoComponentPayloadCurrent(&available, &epoch, 7, true));
|
||||
CHECK(!videoComponentPayloadCurrent(&available, &epoch, 6, true));
|
||||
CHECK(videoComponentPayloadCurrent(&available, &epoch, 0, false));
|
||||
|
||||
const LG_VideoComponentStatus unavailable =
|
||||
{
|
||||
.available = false,
|
||||
.epoch = 8,
|
||||
.reason = LG_TRANSPORT_ERROR,
|
||||
};
|
||||
CHECK(videoComponentStatusChanged(
|
||||
&available, &epoch, &reason, &unavailable));
|
||||
CHECK(!videoComponentPayloadCurrent(&available, &epoch, 7, true));
|
||||
CHECK(!videoComponentPayloadCurrent(&available, &epoch, 0, false));
|
||||
CHECK(atomic_load(&reason) == LG_TRANSPORT_ERROR);
|
||||
|
||||
const LG_VideoComponentStatus replacement =
|
||||
{
|
||||
.available = true,
|
||||
.epoch = 9,
|
||||
.reason = LG_TRANSPORT_OK,
|
||||
};
|
||||
CHECK(videoComponentStatusChanged(
|
||||
&available, &epoch, &reason, &replacement));
|
||||
CHECK(!videoComponentPayloadCurrent(&available, &epoch, 7, true));
|
||||
CHECK(videoComponentPayloadCurrent(&available, &epoch, 9, true));
|
||||
}
|
||||
|
||||
struct PayloadGateTrace
|
||||
{
|
||||
const atomic_bool * available;
|
||||
const atomic_uint_least64_t * epoch;
|
||||
uint64_t payloadEpoch;
|
||||
uint64_t sourceGeneration;
|
||||
atomic_bool entered;
|
||||
atomic_bool release;
|
||||
atomic_bool accepted;
|
||||
atomic_bool changed;
|
||||
};
|
||||
|
||||
static void * holdPayload(void * opaque)
|
||||
{
|
||||
struct PayloadGateTrace * trace = opaque;
|
||||
const bool accepted = videoPayloadAccept(trace->available, trace->epoch,
|
||||
trace->payloadEpoch, true, trace->sourceGeneration);
|
||||
atomic_store_explicit(&trace->accepted, accepted, memory_order_release);
|
||||
if (!accepted)
|
||||
return NULL;
|
||||
|
||||
atomic_store_explicit(&trace->entered, true, memory_order_release);
|
||||
while (!atomic_load_explicit(&trace->release, memory_order_acquire))
|
||||
usleep(1000);
|
||||
videoPayloadRelease();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void * changePayload(void * opaque)
|
||||
{
|
||||
struct PayloadGateTrace * trace = opaque;
|
||||
LG_LOCK(g_state.videoPayloadLock);
|
||||
atomic_store_explicit((atomic_bool *)trace->available,
|
||||
false, memory_order_release);
|
||||
atomic_store_explicit((atomic_uint_least64_t *)trace->epoch,
|
||||
trace->payloadEpoch + 1, memory_order_release);
|
||||
LG_UNLOCK(g_state.videoPayloadLock);
|
||||
atomic_store_explicit(&trace->changed, true, memory_order_release);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void testPayloadGate(void)
|
||||
{
|
||||
memset(&g_state, 0, sizeof(g_state));
|
||||
LG_LOCK_INIT(g_state.videoPayloadLock);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].generation,
|
||||
5, memory_order_relaxed);
|
||||
atomic_bool available = true;
|
||||
atomic_uint_least64_t epoch = 7;
|
||||
struct PayloadGateTrace trace =
|
||||
{
|
||||
.available = &available,
|
||||
.epoch = &epoch,
|
||||
.payloadEpoch = 7,
|
||||
.sourceGeneration = 5,
|
||||
};
|
||||
pthread_t payloadThread;
|
||||
pthread_t statusThread;
|
||||
CHECK(pthread_create(&payloadThread, NULL, holdPayload, &trace) == 0);
|
||||
while (!atomic_load_explicit(&trace.entered, memory_order_acquire))
|
||||
usleep(1000);
|
||||
CHECK(pthread_create(&statusThread, NULL, changePayload, &trace) == 0);
|
||||
usleep(10000);
|
||||
CHECK(!atomic_load_explicit(&trace.changed, memory_order_acquire));
|
||||
atomic_store_explicit(&trace.release, true, memory_order_release);
|
||||
CHECK(pthread_join(payloadThread, NULL) == 0);
|
||||
CHECK(pthread_join(statusThread, NULL) == 0);
|
||||
CHECK(atomic_load_explicit(&trace.accepted, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(&trace.changed, memory_order_acquire));
|
||||
CHECK(!videoPayloadAccept(&available, &epoch, 7, true, 5));
|
||||
LG_LOCK_FREE(g_state.videoPayloadLock);
|
||||
}
|
||||
|
||||
static void testFramePayloadBookkeeping(void)
|
||||
{
|
||||
memset(&g_state, 0, sizeof(g_state));
|
||||
LG_LOCK_INIT(g_state.videoPayloadLock);
|
||||
atomic_store_explicit(&g_state.videoFrameAvailable,
|
||||
true, memory_order_relaxed);
|
||||
atomic_store_explicit(&g_state.videoFrameEpoch, 7, memory_order_relaxed);
|
||||
atomic_store_explicit(
|
||||
&g_state.videoSource[LG_VIDEO_SOURCE_PRIMARY].generation,
|
||||
5, memory_order_relaxed);
|
||||
g_state.formatValid = true;
|
||||
|
||||
uint64_t sourceGeneration = 4;
|
||||
uint64_t frameSerial = 41;
|
||||
uint32_t formatVersion = 3;
|
||||
bool sourceChanged = false;
|
||||
LG_TransportFrame frame =
|
||||
{
|
||||
.serial = 42,
|
||||
.epoch = 6,
|
||||
};
|
||||
|
||||
CHECK(!videoFramePayloadAccept(&frame, 5, true,
|
||||
&sourceGeneration, &frameSerial, &formatVersion, &sourceChanged));
|
||||
CHECK(sourceGeneration == 4);
|
||||
CHECK(frameSerial == 41);
|
||||
CHECK(formatVersion == 3);
|
||||
|
||||
frame.epoch = 7;
|
||||
CHECK(videoFramePayloadAccept(&frame, 5, true,
|
||||
&sourceGeneration, &frameSerial, &formatVersion, &sourceChanged));
|
||||
CHECK(sourceChanged);
|
||||
CHECK(sourceGeneration == 5);
|
||||
CHECK(frameSerial == 42);
|
||||
CHECK(formatVersion == 0);
|
||||
videoPayloadRelease();
|
||||
|
||||
CHECK(!videoFramePayloadAccept(&frame, 5, true,
|
||||
&sourceGeneration, &frameSerial, &formatVersion, &sourceChanged));
|
||||
LG_LOCK_FREE(g_state.videoPayloadLock);
|
||||
}
|
||||
|
||||
static void testConnectCancellation(void)
|
||||
{
|
||||
rawConnectCalls = 0;
|
||||
cancellableConnectCalls = 0;
|
||||
connectSawCancellation = false;
|
||||
const LG_TransportOps ops =
|
||||
{
|
||||
.connect = rawConnect,
|
||||
.connectCancellable = cancellableConnect,
|
||||
};
|
||||
struct TransportSessionProbe probe =
|
||||
{
|
||||
.ops = &ops,
|
||||
.cancelled = cancelled,
|
||||
.done = false,
|
||||
};
|
||||
CHECK(transportSessionProbe(&probe) == 0);
|
||||
CHECK(atomic_load_explicit(&probe.done, memory_order_acquire));
|
||||
CHECK(probe.status == LG_TRANSPORT_DISCONNECTED);
|
||||
CHECK(rawConnectCalls == 0);
|
||||
CHECK(cancellableConnectCalls == 1);
|
||||
CHECK(connectSawCancellation);
|
||||
}
|
||||
|
||||
static void testRecoveryErrors(void)
|
||||
{
|
||||
CHECK(strcmp(recoveryErrorText(LG_RECOVERY_ERR_BUSY),
|
||||
"A conflicting recovery request is already in progress") == 0);
|
||||
CHECK(strcmp(recoveryErrorText(LG_RECOVERY_ERR_CAPACITY),
|
||||
"Too many recovery requests are pending") == 0);
|
||||
}
|
||||
|
||||
struct Test
|
||||
{
|
||||
const char * name;
|
||||
@@ -332,11 +764,18 @@ static const struct Test tests[] =
|
||||
{ "lifecycle", testLifecycle },
|
||||
{ "order" , testOrder },
|
||||
{ "untracked", testUntracked },
|
||||
{ "accounting", testAccounting },
|
||||
{ "provider-unavailable", testProviderUnavailable },
|
||||
{ "timeout" , testTimeout },
|
||||
{ "fifo" , testFifo },
|
||||
{ "retire" , testRetire },
|
||||
{ "feedback" , testFeedback },
|
||||
{ "wrap" , testWrap },
|
||||
{ "component-epoch", testComponentEpoch },
|
||||
{ "payload-gate", testPayloadGate },
|
||||
{ "frame-payload-bookkeeping", testFramePayloadBookkeeping },
|
||||
{ "connect-cancel", testConnectCancellation },
|
||||
{ "recovery-errors", testRecoveryErrors },
|
||||
};
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "interface/transport.h"
|
||||
|
||||
#include "common/KVMFR.h"
|
||||
#include "common/KVMFRRecovery.h"
|
||||
#include "common/LGMPConfig.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
@@ -40,8 +41,31 @@
|
||||
#define TEST_SHM_SIZE (2U * 1024U * 1024U)
|
||||
#define TEST_TIMEOUT 20U
|
||||
#define WAIT_TIMEOUT (TEST_TIMEOUT + 100U)
|
||||
#define POLL_INTERVAL 50000U
|
||||
|
||||
extern const LG_TransportOps LGT_LGMP;
|
||||
extern void lgmp_testSetVideoStatusDispatchGate(
|
||||
LG_Transport * target, atomic_bool * waiting, atomic_bool * hold);
|
||||
extern LG_RecoveryError lgmp_testRecoveryError(uint32_t error);
|
||||
extern bool lgmp_testRecoveryProbeCancellation(
|
||||
LG_TransportCancelledFn cancelled, void * opaque);
|
||||
|
||||
static bool cancelled(void * opaque)
|
||||
{
|
||||
return *(const bool *)opaque;
|
||||
}
|
||||
|
||||
struct CancelAfter
|
||||
{
|
||||
unsigned int calls;
|
||||
unsigned int limit;
|
||||
};
|
||||
|
||||
static bool cancelAfter(void * opaque)
|
||||
{
|
||||
struct CancelAfter * state = opaque;
|
||||
return ++state->calls >= state->limit;
|
||||
}
|
||||
|
||||
#define CHECK(x) \
|
||||
do \
|
||||
@@ -70,14 +94,164 @@ static bool waitForQueuesEmpty(PLGMPHost host, PLGMPHostQueue frameQueue,
|
||||
return false;
|
||||
}
|
||||
|
||||
struct VideoStatusTrace;
|
||||
|
||||
struct WaitState
|
||||
{
|
||||
LG_Transport * transport;
|
||||
const LG_FrameOps * ops;
|
||||
LG_TransportStatus status;
|
||||
atomic_bool done;
|
||||
bool frame;
|
||||
};
|
||||
|
||||
struct UnregisterState
|
||||
{
|
||||
LG_Transport * transport;
|
||||
const LG_FrameOps * ops;
|
||||
atomic_bool started;
|
||||
atomic_bool done;
|
||||
};
|
||||
|
||||
struct VideoStatusTrace
|
||||
{
|
||||
atomic_uint count;
|
||||
atomic_uint_fast64_t frameEpoch;
|
||||
atomic_uint_fast64_t pointerEpoch;
|
||||
atomic_int frameReason;
|
||||
atomic_int pointerReason;
|
||||
atomic_bool frameAvailable;
|
||||
atomic_bool pointerAvailable;
|
||||
atomic_bool active;
|
||||
atomic_bool overlap;
|
||||
atomic_bool block;
|
||||
atomic_bool entered;
|
||||
atomic_bool release;
|
||||
atomic_bool * waitBeforeReplace;
|
||||
atomic_bool unregisterDone;
|
||||
atomic_bool replaceDone;
|
||||
LG_Transport * unregisterTransport;
|
||||
LG_Transport ** destroyTransport;
|
||||
const LG_FrameOps * unregisterOps;
|
||||
struct VideoStatusTrace * registerTrace;
|
||||
struct VideoStatusTrace * replaceTrace;
|
||||
unsigned int registerAtCount;
|
||||
LG_Transport * transport;
|
||||
const LG_FrameOps * ops;
|
||||
};
|
||||
|
||||
static void videoStatusChanged(void * opaque,
|
||||
const LG_VideoStatus * status)
|
||||
{
|
||||
struct VideoStatusTrace * trace = opaque;
|
||||
if (atomic_exchange_explicit(&trace->active, true,
|
||||
memory_order_acq_rel))
|
||||
atomic_store_explicit(&trace->overlap, true, memory_order_release);
|
||||
|
||||
atomic_store_explicit(&trace->frameEpoch,
|
||||
status->frame.epoch, memory_order_relaxed);
|
||||
atomic_store_explicit(&trace->pointerEpoch,
|
||||
status->pointer.epoch, memory_order_relaxed);
|
||||
atomic_store_explicit(&trace->frameReason,
|
||||
status->frame.reason, memory_order_relaxed);
|
||||
atomic_store_explicit(&trace->pointerReason,
|
||||
status->pointer.reason, memory_order_relaxed);
|
||||
atomic_store_explicit(&trace->frameAvailable,
|
||||
status->frame.available, memory_order_relaxed);
|
||||
atomic_store_explicit(&trace->pointerAvailable,
|
||||
status->pointer.available, memory_order_relaxed);
|
||||
const unsigned int count = atomic_fetch_add_explicit(
|
||||
&trace->count, 1, memory_order_release) + 1;
|
||||
|
||||
if (trace->unregisterTransport)
|
||||
{
|
||||
trace->unregisterOps->setStatusListener(
|
||||
trace->unregisterTransport, NULL, NULL);
|
||||
atomic_store_explicit(
|
||||
&trace->unregisterDone, true, memory_order_release);
|
||||
}
|
||||
|
||||
if (trace->destroyTransport)
|
||||
{
|
||||
LGT_LGMP.destroy(trace->destroyTransport);
|
||||
atomic_store_explicit(
|
||||
&trace->unregisterDone, true, memory_order_release);
|
||||
}
|
||||
|
||||
if (trace->registerTrace &&
|
||||
(!trace->registerAtCount || count >= trace->registerAtCount))
|
||||
trace->registerTrace->ops->setStatusListener(
|
||||
trace->registerTrace->transport, videoStatusChanged,
|
||||
trace->registerTrace);
|
||||
|
||||
if (atomic_load_explicit(&trace->block, memory_order_acquire))
|
||||
{
|
||||
atomic_store_explicit(&trace->entered, true, memory_order_release);
|
||||
while (!atomic_load_explicit(&trace->release, memory_order_acquire))
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
if (trace->replaceTrace)
|
||||
{
|
||||
if (trace->waitBeforeReplace)
|
||||
while (!atomic_load_explicit(
|
||||
trace->waitBeforeReplace, memory_order_acquire))
|
||||
usleep(1000);
|
||||
trace->replaceTrace->ops->setStatusListener(
|
||||
trace->replaceTrace->transport, NULL, NULL);
|
||||
trace->replaceTrace->ops->setStatusListener(
|
||||
trace->replaceTrace->transport, videoStatusChanged,
|
||||
trace->replaceTrace);
|
||||
atomic_store_explicit(
|
||||
&trace->replaceDone, true, memory_order_release);
|
||||
}
|
||||
|
||||
atomic_store_explicit(&trace->active, false, memory_order_release);
|
||||
}
|
||||
|
||||
static bool waitStatusCount(
|
||||
const struct VideoStatusTrace * trace, unsigned int count)
|
||||
{
|
||||
for (unsigned i = 0; i < WAIT_TIMEOUT; ++i)
|
||||
{
|
||||
if (atomic_load_explicit(&trace->count, memory_order_acquire) >= count)
|
||||
return true;
|
||||
usleep(1000);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void * disconnectVideo(void * opaque)
|
||||
{
|
||||
struct VideoStatusTrace * trace = opaque;
|
||||
LGT_LGMP.disconnect(trace->transport);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void * disconnectVideoSignaled(void * opaque)
|
||||
{
|
||||
struct VideoStatusTrace * trace = opaque;
|
||||
LGT_LGMP.disconnect(trace->transport);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void * registerVideoStatus(void * opaque)
|
||||
{
|
||||
struct VideoStatusTrace * trace = opaque;
|
||||
trace->ops->setStatusListener(
|
||||
trace->transport, videoStatusChanged, trace);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void * unregisterVideoStatus(void * opaque)
|
||||
{
|
||||
struct UnregisterState * state = opaque;
|
||||
atomic_store_explicit(&state->started, true, memory_order_release);
|
||||
state->ops->setStatusListener(state->transport, NULL, NULL);
|
||||
atomic_store_explicit(&state->done, true, memory_order_release);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void * waitForVideo(void * opaque)
|
||||
{
|
||||
struct WaitState * state = opaque;
|
||||
@@ -92,6 +266,7 @@ static void * waitForVideo(void * opaque)
|
||||
LG_TransportPointer pointer;
|
||||
state->status = state->ops->nextPointer(state->transport, &pointer);
|
||||
}
|
||||
atomic_store_explicit(&state->done, true, memory_order_release);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -152,12 +327,31 @@ int main(void)
|
||||
PLGMPHostQueue frameQueue = NULL;
|
||||
PLGMPHostQueue pointerQueue = NULL;
|
||||
PLGMPMemory frameMemory = NULL;
|
||||
PLGMPMemory malformedFrameMemory = NULL;
|
||||
PLGMPMemory pointerMemory = NULL;
|
||||
PLGMPMemory malformedPointerMemory = NULL;
|
||||
LG_Transport * transport = NULL;
|
||||
LG_Transport * secondTransport = NULL;
|
||||
LG_Transport * thirdTransport = NULL;
|
||||
LG_Transport * nestedTransport = NULL;
|
||||
LG_Transport * admittedTransport = NULL;
|
||||
const LG_FrameOps * frameOps = NULL;
|
||||
struct VideoStatusTrace videoTrace;
|
||||
struct VideoStatusTrace secondVideoTrace;
|
||||
memset(&videoTrace, 0, sizeof(videoTrace));
|
||||
memset(&secondVideoTrace, 0, sizeof(secondVideoTrace));
|
||||
|
||||
debug_init();
|
||||
|
||||
CHECK(lgmp_testRecoveryError(KVMFR_R_ERR_BUSY) ==
|
||||
LG_RECOVERY_ERR_BUSY);
|
||||
CHECK(lgmp_testRecoveryError(KVMFR_R_ERR_CAPACITY) ==
|
||||
LG_RECOVERY_ERR_CAPACITY);
|
||||
struct CancelAfter cancelDuringProbe = { .limit = 3 };
|
||||
CHECK(!lgmp_testRecoveryProbeCancellation(
|
||||
cancelAfter, &cancelDuringProbe));
|
||||
CHECK(cancelDuringProbe.calls >= cancelDuringProbe.limit);
|
||||
|
||||
fd = mkstemp(path);
|
||||
CHECK(fd >= 0);
|
||||
pathExists = true;
|
||||
@@ -185,9 +379,6 @@ int main(void)
|
||||
.numMessages = LGMP_Q_POINTER_LEN,
|
||||
.subTimeout = TEST_TIMEOUT,
|
||||
};
|
||||
CHECK(lgmpHostQueueNew(host, frameConfig, &frameQueue) == LGMP_OK);
|
||||
CHECK(lgmpHostQueueNew(host, pointerConfig, &pointerQueue) == LGMP_OK);
|
||||
|
||||
const uint32_t frameSize =
|
||||
sizeof(KVMFRFrame) + sizeof(FrameBuffer) + sizeof(uint32_t);
|
||||
CHECK(lgmpHostMemAlloc(host, frameSize, &frameMemory) == LGMP_OK);
|
||||
@@ -215,9 +406,16 @@ int main(void)
|
||||
FrameBuffer * framebuffer = (FrameBuffer *)((uint8_t *)wireFrame +
|
||||
wireFrame->offset);
|
||||
atomic_store(&framebuffer->wp, sizeof(uint32_t));
|
||||
CHECK(lgmpHostMemAlloc(host, sizeof(KVMFRFrame) - 1,
|
||||
&malformedFrameMemory) == LGMP_OK);
|
||||
|
||||
CHECK(lgmpHostMemAlloc(host, sizeof(KVMFRCursor), &pointerMemory) ==
|
||||
LGMP_OK);
|
||||
CHECK(lgmpHostMemAlloc(host, sizeof(KVMFRCursor),
|
||||
&malformedPointerMemory) == LGMP_OK);
|
||||
KVMFRCursor * malformedPointer = lgmpHostMemPtr(malformedPointerMemory);
|
||||
malformedPointer->height = 2;
|
||||
malformedPointer->pitch = UINT32_MAX;
|
||||
KVMFRCursor * wirePointer = lgmpHostMemPtr(pointerMemory);
|
||||
wirePointer->x = 1;
|
||||
wirePointer->y = 1;
|
||||
@@ -225,8 +423,8 @@ int main(void)
|
||||
LGT_LGMP.setup();
|
||||
option_set_string("lgmp", "shmDevice", path);
|
||||
option_set_bool("lgmp", "allowDMA", false);
|
||||
option_set_int("lgmp", "framePollInterval", 5000000);
|
||||
option_set_int("lgmp", "cursorPollInterval", 5000000);
|
||||
option_set_int("lgmp", "framePollInterval", POLL_INTERVAL);
|
||||
option_set_int("lgmp", "cursorPollInterval", POLL_INTERVAL);
|
||||
CHECK(LGT_LGMP.create(&transport));
|
||||
const LG_VideoOps * videoOps = LGT_LGMP.getVideoOps(transport);
|
||||
CHECK(videoOps);
|
||||
@@ -235,20 +433,92 @@ int main(void)
|
||||
frameOps = videoOps->frame;
|
||||
CHECK(frameOps->cancelFrameWait);
|
||||
CHECK(frameOps->cancelPointerWait);
|
||||
CHECK(frameOps->setStatusListener);
|
||||
|
||||
CHECK(unlink(path) == 0);
|
||||
pathExists = false;
|
||||
CHECK(LGT_LGMP.create(&secondTransport));
|
||||
const LG_VideoOps * secondVideoOps =
|
||||
LGT_LGMP.getVideoOps(secondTransport);
|
||||
CHECK(secondVideoOps && secondVideoOps->type == LG_VIDEO_TYPE_FRAME);
|
||||
CHECK(secondVideoOps->frame && secondVideoOps->frame->setStatusListener);
|
||||
|
||||
CHECK(LGT_LGMP.create(&thirdTransport));
|
||||
const LG_VideoOps * thirdVideoOps =
|
||||
LGT_LGMP.getVideoOps(thirdTransport);
|
||||
CHECK(thirdVideoOps && thirdVideoOps->type == LG_VIDEO_TYPE_FRAME);
|
||||
CHECK(thirdVideoOps->frame && thirdVideoOps->frame->setStatusListener);
|
||||
|
||||
CHECK(LGT_LGMP.create(&nestedTransport));
|
||||
const LG_VideoOps * nestedVideoOps =
|
||||
LGT_LGMP.getVideoOps(nestedTransport);
|
||||
CHECK(nestedVideoOps && nestedVideoOps->type == LG_VIDEO_TYPE_FRAME);
|
||||
CHECK(nestedVideoOps->frame && nestedVideoOps->frame->setStatusListener);
|
||||
|
||||
CHECK(LGT_LGMP.create(&admittedTransport));
|
||||
const LG_VideoOps * admittedVideoOps =
|
||||
LGT_LGMP.getVideoOps(admittedTransport);
|
||||
CHECK(admittedVideoOps && admittedVideoOps->type == LG_VIDEO_TYPE_FRAME);
|
||||
CHECK(admittedVideoOps->frame &&
|
||||
admittedVideoOps->frame->setStatusListener);
|
||||
|
||||
usleep(300000);
|
||||
CHECK(lgmpHostProcess(host) == LGMP_OK);
|
||||
|
||||
LG_TransportSession transportSession;
|
||||
CHECK(LGT_LGMP.connect(transport, &transportSession) == LG_TRANSPORT_OK);
|
||||
bool cancelConnect = true;
|
||||
CHECK(LGT_LGMP.connectCancellable(transport, &transportSession,
|
||||
cancelled, &cancelConnect) == LG_TRANSPORT_DISCONNECTED);
|
||||
CHECK(!LGT_LGMP.sessionValid(transport));
|
||||
CHECK(LGT_LGMP.connectCancellable(
|
||||
transport, &transportSession, NULL, NULL) == LG_TRANSPORT_OK);
|
||||
LG_TransportSession secondSession;
|
||||
CHECK(LGT_LGMP.connectCancellable(
|
||||
secondTransport, &secondSession, NULL, NULL) == LG_TRANSPORT_OK);
|
||||
LG_TransportSession thirdSession;
|
||||
CHECK(LGT_LGMP.connectCancellable(
|
||||
thirdTransport, &thirdSession, NULL, NULL) == LG_TRANSPORT_OK);
|
||||
LG_TransportSession nestedSession;
|
||||
CHECK(LGT_LGMP.connectCancellable(
|
||||
nestedTransport, &nestedSession, NULL, NULL) == LG_TRANSPORT_OK);
|
||||
LG_TransportSession admittedSession;
|
||||
CHECK(LGT_LGMP.connectCancellable(
|
||||
admittedTransport, &admittedSession, NULL, NULL) == LG_TRANSPORT_OK);
|
||||
videoTrace.transport = transport;
|
||||
videoTrace.ops = frameOps;
|
||||
frameOps->setStatusListener(
|
||||
transport, videoStatusChanged, &videoTrace);
|
||||
CHECK(atomic_load_explicit(&videoTrace.count, memory_order_acquire) == 1);
|
||||
CHECK(!atomic_load_explicit(
|
||||
&videoTrace.frameAvailable, memory_order_acquire));
|
||||
CHECK(!atomic_load_explicit(
|
||||
&videoTrace.pointerAvailable, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.frameReason, memory_order_acquire) ==
|
||||
LG_TRANSPORT_UNAVAILABLE);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerReason, memory_order_acquire) ==
|
||||
LG_TRANSPORT_UNAVAILABLE);
|
||||
|
||||
CHECK(lgmpHostQueueNew(host, frameConfig, &frameQueue) == LGMP_OK);
|
||||
CHECK(lgmpHostQueueNew(host, pointerConfig, &pointerQueue) == LGMP_OK);
|
||||
|
||||
LG_TransportFrame frame;
|
||||
LG_TransportPointer pointer;
|
||||
CHECK(checkWaitCancellation(
|
||||
transport, frameOps, frameQueue, pointerQueue));
|
||||
CHECK(waitStatusCount(&videoTrace, 3));
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.frameAvailable, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerAvailable, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.frameReason, memory_order_acquire) == LG_TRANSPORT_OK);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerReason, memory_order_acquire) == LG_TRANSPORT_OK);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.frameEpoch, memory_order_acquire) != 0);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerEpoch, memory_order_acquire) != 0);
|
||||
CHECK(!atomic_load_explicit(&videoTrace.overlap, memory_order_acquire));
|
||||
CHECK(lgmpHostQueueHasSubs(frameQueue));
|
||||
CHECK(lgmpHostQueueHasSubs(pointerQueue));
|
||||
CHECK(lgmpHostQueueNewSubs(frameQueue) == 1);
|
||||
@@ -258,14 +528,130 @@ int main(void)
|
||||
CHECK(lgmpHostQueuePost(pointerQueue, CURSOR_FLAG_POSITION,
|
||||
pointerMemory) == LGMP_OK);
|
||||
CHECK(frameOps->nextFrame(transport, false, &frame) == LG_TRANSPORT_OK);
|
||||
CHECK(frame.epoch == atomic_load_explicit(
|
||||
&videoTrace.frameEpoch, memory_order_acquire));
|
||||
LG_TransportFrameTiming timing;
|
||||
frameOps->getFrameTiming(transport, &frame, &timing);
|
||||
CHECK(timing.captureTime == wireFrame->captureTime);
|
||||
CHECK(timing.postProcessTime == wireFrame->postProcessTime);
|
||||
CHECK(timing.copyTime == wireFrame->copyTime);
|
||||
CHECK(timing.readyTime == wireFrame->readyTime);
|
||||
CHECK(timing.providerValid);
|
||||
frameOps->releaseFrame(transport, &frame);
|
||||
|
||||
wireFrame->frameSerial = 2;
|
||||
wireFrame->timingSerial = wireFrame->frameSerial;
|
||||
wireFrame->timingValid = 0;
|
||||
CHECK(lgmpHostQueuePost(frameQueue, 0, frameMemory) == LGMP_OK);
|
||||
CHECK(frameOps->nextFrame(transport, false, &frame) == LG_TRANSPORT_OK);
|
||||
/* Work which began before producer timing became coherent is not reported
|
||||
* as an independent provider stage. */
|
||||
wireFrame->timingValid = 1;
|
||||
frameOps->getFrameTiming(transport, &frame, &timing);
|
||||
CHECK(timing.valid);
|
||||
CHECK(!timing.providerValid);
|
||||
CHECK(timing.receiveTime == 0);
|
||||
CHECK(timing.prepareTime == 0);
|
||||
frameOps->releaseFrame(transport, &frame);
|
||||
CHECK(frameOps->nextPointer(transport, &pointer) == LG_TRANSPORT_OK);
|
||||
CHECK(pointer.epoch == atomic_load_explicit(
|
||||
&videoTrace.pointerEpoch, memory_order_acquire));
|
||||
frameOps->releasePointer(transport, &pointer);
|
||||
|
||||
const uint64_t frameEpoch = atomic_load_explicit(
|
||||
&videoTrace.frameEpoch, memory_order_acquire);
|
||||
const uint64_t pointerEpoch = atomic_load_explicit(
|
||||
&videoTrace.pointerEpoch, memory_order_acquire);
|
||||
CHECK(lgmpHostQueuePost(frameQueue, 0, malformedFrameMemory) == LGMP_OK);
|
||||
frameOps->cancelFrameWait(transport);
|
||||
CHECK(frameOps->nextFrame(transport, false, &frame) == LG_TRANSPORT_ERROR);
|
||||
CHECK(!atomic_load_explicit(
|
||||
&videoTrace.frameAvailable, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerAvailable, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.frameEpoch, memory_order_acquire) != frameEpoch);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerEpoch, memory_order_acquire) == pointerEpoch);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.frameReason, memory_order_acquire) ==
|
||||
LG_TRANSPORT_ERROR);
|
||||
|
||||
CHECK(lgmpHostQueuePost(frameQueue, 0, malformedFrameMemory) == LGMP_OK);
|
||||
struct WaitState frameErrorWait =
|
||||
{
|
||||
.transport = transport,
|
||||
.ops = frameOps,
|
||||
.status = LG_TRANSPORT_OK,
|
||||
.frame = true,
|
||||
};
|
||||
pthread_t frameErrorThread;
|
||||
CHECK(pthread_create(&frameErrorThread, NULL,
|
||||
waitForVideo, &frameErrorWait) == 0);
|
||||
CHECK(waitForQueuesEmpty(host, frameQueue, pointerQueue));
|
||||
usleep(POLL_INTERVAL / 10U);
|
||||
CHECK(!atomic_load_explicit(
|
||||
&frameErrorWait.done, memory_order_acquire));
|
||||
frameOps->cancelFrameWait(transport);
|
||||
CHECK(pthread_join(frameErrorThread, NULL) == 0);
|
||||
CHECK(atomic_load_explicit(&frameErrorWait.done, memory_order_acquire));
|
||||
CHECK(frameErrorWait.status == LG_TRANSPORT_ERROR);
|
||||
|
||||
wireFrame->frameSerial = 3;
|
||||
wireFrame->timingSerial = wireFrame->frameSerial;
|
||||
CHECK(lgmpHostQueuePost(frameQueue, 0, frameMemory) == LGMP_OK);
|
||||
CHECK(frameOps->nextFrame(transport, false, &frame) == LG_TRANSPORT_OK);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.frameAvailable, memory_order_acquire));
|
||||
frameOps->releaseFrame(transport, &frame);
|
||||
|
||||
const uint64_t frameEpochAfterRecovery = atomic_load_explicit(
|
||||
&videoTrace.frameEpoch, memory_order_acquire);
|
||||
const uint64_t pointerEpochBeforeLoss = atomic_load_explicit(
|
||||
&videoTrace.pointerEpoch, memory_order_acquire);
|
||||
CHECK(lgmpHostQueuePost(pointerQueue, CURSOR_FLAG_SHAPE,
|
||||
malformedPointerMemory) == LGMP_OK);
|
||||
frameOps->cancelPointerWait(transport);
|
||||
CHECK(frameOps->nextPointer(transport, &pointer) == LG_TRANSPORT_ERROR);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.frameAvailable, memory_order_acquire));
|
||||
CHECK(!atomic_load_explicit(
|
||||
&videoTrace.pointerAvailable, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.frameEpoch, memory_order_acquire) ==
|
||||
frameEpochAfterRecovery);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerEpoch, memory_order_acquire) !=
|
||||
pointerEpochBeforeLoss);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerReason, memory_order_acquire) ==
|
||||
LG_TRANSPORT_ERROR);
|
||||
|
||||
CHECK(lgmpHostQueuePost(pointerQueue, CURSOR_FLAG_SHAPE,
|
||||
malformedPointerMemory) == LGMP_OK);
|
||||
struct WaitState pointerErrorWait =
|
||||
{
|
||||
.transport = transport,
|
||||
.ops = frameOps,
|
||||
.status = LG_TRANSPORT_OK,
|
||||
};
|
||||
pthread_t pointerErrorThread;
|
||||
CHECK(pthread_create(&pointerErrorThread, NULL,
|
||||
waitForVideo, &pointerErrorWait) == 0);
|
||||
CHECK(waitForQueuesEmpty(host, frameQueue, pointerQueue));
|
||||
usleep(POLL_INTERVAL / 10U);
|
||||
CHECK(!atomic_load_explicit(
|
||||
&pointerErrorWait.done, memory_order_acquire));
|
||||
frameOps->cancelPointerWait(transport);
|
||||
CHECK(pthread_join(pointerErrorThread, NULL) == 0);
|
||||
CHECK(atomic_load_explicit(&pointerErrorWait.done, memory_order_acquire));
|
||||
CHECK(pointerErrorWait.status == LG_TRANSPORT_ERROR);
|
||||
|
||||
CHECK(lgmpHostQueuePost(pointerQueue, CURSOR_FLAG_POSITION,
|
||||
pointerMemory) == LGMP_OK);
|
||||
CHECK(frameOps->nextPointer(transport, &pointer) == LG_TRANSPORT_OK);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerAvailable, memory_order_acquire));
|
||||
frameOps->releasePointer(transport, &pointer);
|
||||
|
||||
/*
|
||||
@@ -306,7 +692,7 @@ int main(void)
|
||||
* Also recover when the host has already marked the cached handles bad.
|
||||
* LGMP leaves a timed-out handle non-NULL when unsubscribe fails.
|
||||
*/
|
||||
wireFrame->frameSerial = 2;
|
||||
wireFrame->frameSerial = 4;
|
||||
wireFrame->timingSerial = wireFrame->frameSerial;
|
||||
CHECK(lgmpHostQueuePost(frameQueue, 0, frameMemory) == LGMP_OK);
|
||||
CHECK(lgmpHostQueuePost(pointerQueue, CURSOR_FLAG_POSITION,
|
||||
@@ -319,9 +705,19 @@ int main(void)
|
||||
frameOps->stopFrame(transport);
|
||||
frameOps->stopPointer(transport);
|
||||
frameOps->cancelFrameWait(transport);
|
||||
const uint64_t oldFrameEpoch = atomic_load_explicit(
|
||||
&videoTrace.frameEpoch, memory_order_acquire);
|
||||
const uint64_t oldPointerEpoch = atomic_load_explicit(
|
||||
&videoTrace.pointerEpoch, memory_order_acquire);
|
||||
CHECK(frameOps->nextFrame(transport, false, &frame) == LG_TRANSPORT_TIMEOUT);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.frameEpoch, memory_order_acquire) != oldFrameEpoch);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerEpoch, memory_order_acquire) == oldPointerEpoch);
|
||||
frameOps->cancelPointerWait(transport);
|
||||
CHECK(frameOps->nextPointer(transport, &pointer) == LG_TRANSPORT_TIMEOUT);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerEpoch, memory_order_acquire) != oldPointerEpoch);
|
||||
CHECK(lgmpHostQueueNewSubs(frameQueue) == 1);
|
||||
CHECK(lgmpHostQueueNewSubs(pointerQueue) == 1);
|
||||
|
||||
@@ -329,18 +725,306 @@ int main(void)
|
||||
CHECK(lgmpHostQueuePost(frameQueue, 0, frameMemory) == LGMP_OK);
|
||||
CHECK(lgmpHostQueuePost(pointerQueue, CURSOR_FLAG_POSITION,
|
||||
pointerMemory) == LGMP_OK);
|
||||
const bool pointerBeforeFrameRecovery = atomic_load_explicit(
|
||||
&videoTrace.pointerAvailable, memory_order_acquire);
|
||||
CHECK(frameOps->nextFrame(transport, false, &frame) == LG_TRANSPORT_OK);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.frameAvailable, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerAvailable, memory_order_acquire) ==
|
||||
pointerBeforeFrameRecovery);
|
||||
frameOps->releaseFrame(transport, &frame);
|
||||
CHECK(frameOps->nextPointer(transport, &pointer) == LG_TRANSPORT_OK);
|
||||
CHECK(atomic_load_explicit(
|
||||
&videoTrace.pointerAvailable, memory_order_acquire));
|
||||
frameOps->releasePointer(transport, &pointer);
|
||||
|
||||
atomic_store_explicit(&videoTrace.block, true, memory_order_release);
|
||||
pthread_t disconnectThread;
|
||||
pthread_t crossUnregisterThread;
|
||||
CHECK(pthread_create(&disconnectThread, NULL,
|
||||
disconnectVideo, &videoTrace) == 0);
|
||||
for (unsigned i = 0; i < WAIT_TIMEOUT && !atomic_load_explicit(
|
||||
&videoTrace.entered, memory_order_acquire); ++i)
|
||||
usleep(1000);
|
||||
CHECK(atomic_load_explicit(&videoTrace.entered, memory_order_acquire));
|
||||
secondVideoTrace.transport = secondTransport;
|
||||
secondVideoTrace.ops = secondVideoOps->frame;
|
||||
secondVideoTrace.unregisterTransport = transport;
|
||||
secondVideoTrace.unregisterOps = frameOps;
|
||||
CHECK(pthread_create(&crossUnregisterThread, NULL,
|
||||
registerVideoStatus, &secondVideoTrace) == 0);
|
||||
usleep(POLL_INTERVAL);
|
||||
CHECK(atomic_load_explicit(
|
||||
&secondVideoTrace.count, memory_order_acquire) == 0);
|
||||
CHECK(!atomic_load_explicit(&secondVideoTrace.unregisterDone,
|
||||
memory_order_acquire));
|
||||
atomic_store_explicit(&videoTrace.release, true, memory_order_release);
|
||||
CHECK(pthread_join(disconnectThread, NULL) == 0);
|
||||
CHECK(pthread_join(crossUnregisterThread, NULL) == 0);
|
||||
CHECK(waitStatusCount(&secondVideoTrace, 1));
|
||||
CHECK(atomic_load_explicit(&secondVideoTrace.unregisterDone,
|
||||
memory_order_acquire));
|
||||
CHECK(!atomic_load_explicit(&videoTrace.overlap, memory_order_acquire));
|
||||
CHECK(!atomic_load_explicit(
|
||||
&secondVideoTrace.overlap, memory_order_acquire));
|
||||
frameOps->setStatusListener(transport, NULL, NULL);
|
||||
secondVideoOps->frame->setStatusListener(secondTransport, NULL, NULL);
|
||||
|
||||
/* A callback can unregister another instance while a callback for that
|
||||
* instance is waiting to dispatch. The queued callback revalidates its
|
||||
* listener and is skipped. */
|
||||
struct VideoStatusTrace queuedTrace;
|
||||
struct VideoStatusTrace nestedTrace;
|
||||
struct VideoStatusTrace replacementTrace;
|
||||
memset(&queuedTrace, 0, sizeof(queuedTrace));
|
||||
memset(&nestedTrace, 0, sizeof(nestedTrace));
|
||||
memset(&replacementTrace, 0, sizeof(replacementTrace));
|
||||
queuedTrace.transport = transport;
|
||||
queuedTrace.ops = frameOps;
|
||||
nestedTrace.transport = secondTransport;
|
||||
nestedTrace.ops = secondVideoOps->frame;
|
||||
nestedTrace.unregisterTransport = transport;
|
||||
nestedTrace.unregisterOps = frameOps;
|
||||
replacementTrace.transport = secondTransport;
|
||||
replacementTrace.ops = secondVideoOps->frame;
|
||||
nestedTrace.registerTrace = &replacementTrace;
|
||||
frameOps->setStatusListener(
|
||||
transport, videoStatusChanged, &queuedTrace);
|
||||
const unsigned int queuedCount = atomic_load_explicit(
|
||||
&queuedTrace.count, memory_order_acquire);
|
||||
secondVideoOps->frame->setStatusListener(
|
||||
secondTransport, videoStatusChanged, &nestedTrace);
|
||||
CHECK(atomic_load_explicit(
|
||||
&nestedTrace.unregisterDone, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(
|
||||
&replacementTrace.count, memory_order_acquire) == 1);
|
||||
LGT_LGMP.disconnect(transport);
|
||||
CHECK(atomic_load_explicit(
|
||||
&queuedTrace.count, memory_order_acquire) == queuedCount);
|
||||
|
||||
/* Re-registering the identical callback pair creates a new listener
|
||||
* lifetime. A publication queued for the prior lifetime must not be
|
||||
* delivered after that replacement. */
|
||||
struct VideoStatusTrace gateTrace;
|
||||
struct VideoStatusTrace abaTrace;
|
||||
memset(&gateTrace, 0, sizeof(gateTrace));
|
||||
memset(&abaTrace, 0, sizeof(abaTrace));
|
||||
gateTrace.transport = transport;
|
||||
gateTrace.ops = frameOps;
|
||||
abaTrace.transport = secondTransport;
|
||||
abaTrace.ops = secondVideoOps->frame;
|
||||
frameOps->setStatusListener(transport, NULL, NULL);
|
||||
secondVideoOps->frame->setStatusListener(
|
||||
secondTransport, videoStatusChanged, &abaTrace);
|
||||
const unsigned int abaCount = atomic_load_explicit(
|
||||
&abaTrace.count, memory_order_acquire);
|
||||
gateTrace.block = true;
|
||||
atomic_bool abaWaiting = false;
|
||||
gateTrace.replaceTrace = &abaTrace;
|
||||
gateTrace.waitBeforeReplace = &abaWaiting;
|
||||
pthread_t gateThread;
|
||||
CHECK(pthread_create(&gateThread, NULL,
|
||||
registerVideoStatus, &gateTrace) == 0);
|
||||
for (unsigned i = 0; i < WAIT_TIMEOUT && !atomic_load_explicit(
|
||||
&gateTrace.entered, memory_order_acquire); ++i)
|
||||
usleep(1000);
|
||||
CHECK(atomic_load_explicit(&gateTrace.entered, memory_order_acquire));
|
||||
atomic_bool abaHold = true;
|
||||
lgmp_testSetVideoStatusDispatchGate(
|
||||
secondTransport, &abaWaiting, &abaHold);
|
||||
pthread_t abaPublishThread;
|
||||
CHECK(pthread_create(&abaPublishThread, NULL,
|
||||
disconnectVideoSignaled, &abaTrace) == 0);
|
||||
for (unsigned i = 0; i < WAIT_TIMEOUT && !atomic_load_explicit(
|
||||
&abaWaiting, memory_order_acquire); ++i)
|
||||
usleep(1000);
|
||||
CHECK(atomic_load_explicit(&abaWaiting, memory_order_acquire));
|
||||
atomic_store_explicit(&gateTrace.release, true, memory_order_release);
|
||||
usleep(POLL_INTERVAL);
|
||||
CHECK(atomic_load_explicit(
|
||||
&gateTrace.replaceDone, memory_order_acquire));
|
||||
|
||||
/* External unregister waits for the stale accepted dispatch to drain. */
|
||||
struct UnregisterState unregisterState =
|
||||
{
|
||||
.transport = secondTransport,
|
||||
.ops = secondVideoOps->frame,
|
||||
};
|
||||
pthread_t unregisterThread;
|
||||
CHECK(pthread_create(&unregisterThread, NULL,
|
||||
unregisterVideoStatus, &unregisterState) == 0);
|
||||
for (unsigned i = 0; i < WAIT_TIMEOUT && !atomic_load_explicit(
|
||||
&unregisterState.started, memory_order_acquire); ++i)
|
||||
usleep(1000);
|
||||
CHECK(atomic_load_explicit(
|
||||
&unregisterState.started, memory_order_acquire));
|
||||
usleep(POLL_INTERVAL);
|
||||
CHECK(!atomic_load_explicit(
|
||||
&unregisterState.done, memory_order_acquire));
|
||||
atomic_store_explicit(&abaHold, false, memory_order_release);
|
||||
CHECK(pthread_join(gateThread, NULL) == 0);
|
||||
CHECK(pthread_join(abaPublishThread, NULL) == 0);
|
||||
CHECK(pthread_join(unregisterThread, NULL) == 0);
|
||||
CHECK(atomic_load_explicit(&gateTrace.replaceDone, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(&unregisterState.done, memory_order_acquire));
|
||||
lgmp_testSetVideoStatusDispatchGate(NULL, NULL, NULL);
|
||||
CHECK(atomic_load_explicit(
|
||||
&abaTrace.count, memory_order_acquire) == abaCount + 1);
|
||||
|
||||
/* Symmetric cross-instance unregisters are safe when both registrations
|
||||
* start together. Both callbacks run sequentially and complete their
|
||||
* cross-instance unregister. */
|
||||
struct VideoStatusTrace symmetricA;
|
||||
struct VideoStatusTrace symmetricB;
|
||||
memset(&symmetricA, 0, sizeof(symmetricA));
|
||||
memset(&symmetricB, 0, sizeof(symmetricB));
|
||||
symmetricA.transport = transport;
|
||||
symmetricA.ops = frameOps;
|
||||
symmetricA.unregisterTransport = secondTransport;
|
||||
symmetricA.unregisterOps = secondVideoOps->frame;
|
||||
symmetricB.transport = secondTransport;
|
||||
symmetricB.ops = secondVideoOps->frame;
|
||||
symmetricB.unregisterTransport = transport;
|
||||
symmetricB.unregisterOps = frameOps;
|
||||
pthread_t symmetricThreadA;
|
||||
pthread_t symmetricThreadB;
|
||||
CHECK(pthread_create(&symmetricThreadA, NULL,
|
||||
registerVideoStatus, &symmetricA) == 0);
|
||||
CHECK(pthread_create(&symmetricThreadB, NULL,
|
||||
registerVideoStatus, &symmetricB) == 0);
|
||||
CHECK(pthread_join(symmetricThreadA, NULL) == 0);
|
||||
CHECK(pthread_join(symmetricThreadB, NULL) == 0);
|
||||
CHECK(atomic_load_explicit(
|
||||
&symmetricA.count, memory_order_acquire) == 1);
|
||||
CHECK(atomic_load_explicit(
|
||||
&symmetricB.count, memory_order_acquire) == 1);
|
||||
CHECK(atomic_load_explicit(
|
||||
&symmetricA.unregisterDone, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(
|
||||
&symmetricB.unregisterDone, memory_order_acquire));
|
||||
|
||||
/* A callback may destroy the instance on which an admitted callback is
|
||||
* active. Physical teardown is deferred until that callback retires. */
|
||||
frameOps->setStatusListener(transport, NULL, NULL);
|
||||
secondVideoOps->frame->setStatusListener(secondTransport, NULL, NULL);
|
||||
admittedVideoOps->frame->setStatusListener(admittedTransport, NULL, NULL);
|
||||
struct VideoStatusTrace admittedDestroy;
|
||||
struct VideoStatusTrace admittedOuter;
|
||||
memset(&admittedDestroy, 0, sizeof(admittedDestroy));
|
||||
memset(&admittedOuter, 0, sizeof(admittedOuter));
|
||||
admittedDestroy.transport = secondTransport;
|
||||
admittedDestroy.ops = secondVideoOps->frame;
|
||||
admittedDestroy.destroyTransport = &admittedTransport;
|
||||
admittedOuter.transport = admittedTransport;
|
||||
admittedOuter.ops = admittedVideoOps->frame;
|
||||
admittedOuter.registerTrace = &admittedDestroy;
|
||||
admittedOuter.registerAtCount = 2;
|
||||
admittedVideoOps->frame->setStatusListener(
|
||||
admittedTransport, videoStatusChanged, &admittedOuter);
|
||||
CHECK(atomic_load_explicit(
|
||||
&admittedOuter.count, memory_order_acquire) == 1);
|
||||
LGT_LGMP.disconnect(admittedTransport);
|
||||
CHECK(!admittedTransport);
|
||||
CHECK(atomic_load_explicit(
|
||||
&admittedOuter.count, memory_order_acquire) == 2);
|
||||
CHECK(atomic_load_explicit(
|
||||
&admittedDestroy.count, memory_order_acquire) == 1);
|
||||
|
||||
/* A synchronous callback stays pinned while it drops serialization to
|
||||
* drain a different instance, including when a third callback destroys it.
|
||||
*/
|
||||
thirdVideoOps->frame->setStatusListener(thirdTransport, NULL, NULL);
|
||||
nestedVideoOps->frame->setStatusListener(nestedTransport, NULL, NULL);
|
||||
struct VideoStatusTrace drainTrace;
|
||||
struct VideoStatusTrace pinnedTrace;
|
||||
struct VideoStatusTrace thirdTrace;
|
||||
memset(&drainTrace, 0, sizeof(drainTrace));
|
||||
memset(&pinnedTrace, 0, sizeof(pinnedTrace));
|
||||
memset(&thirdTrace, 0, sizeof(thirdTrace));
|
||||
drainTrace.transport = thirdTransport;
|
||||
drainTrace.ops = thirdVideoOps->frame;
|
||||
thirdVideoOps->frame->setStatusListener(
|
||||
thirdTransport, videoStatusChanged, &drainTrace);
|
||||
atomic_bool drainWaiting = false;
|
||||
atomic_bool drainHold = true;
|
||||
lgmp_testSetVideoStatusDispatchGate(
|
||||
thirdTransport, &drainWaiting, &drainHold);
|
||||
pthread_t drainThread;
|
||||
CHECK(pthread_create(&drainThread, NULL,
|
||||
disconnectVideoSignaled, &drainTrace) == 0);
|
||||
for (unsigned i = 0; i < WAIT_TIMEOUT && !atomic_load_explicit(
|
||||
&drainWaiting, memory_order_acquire); ++i)
|
||||
usleep(1000);
|
||||
CHECK(atomic_load_explicit(&drainWaiting, memory_order_acquire));
|
||||
|
||||
pinnedTrace.transport = transport;
|
||||
pinnedTrace.ops = frameOps;
|
||||
pinnedTrace.unregisterTransport = thirdTransport;
|
||||
pinnedTrace.unregisterOps = thirdVideoOps->frame;
|
||||
pthread_t pinnedThread;
|
||||
CHECK(pthread_create(&pinnedThread, NULL,
|
||||
registerVideoStatus, &pinnedTrace) == 0);
|
||||
CHECK(waitStatusCount(&pinnedTrace, 1));
|
||||
|
||||
thirdTrace.transport = nestedTransport;
|
||||
thirdTrace.ops = nestedVideoOps->frame;
|
||||
nestedVideoOps->frame->setStatusListener(
|
||||
nestedTransport, videoStatusChanged, &thirdTrace);
|
||||
thirdTrace.destroyTransport = &transport;
|
||||
pthread_t thirdThread;
|
||||
CHECK(pthread_create(&thirdThread, NULL,
|
||||
disconnectVideoSignaled, &thirdTrace) == 0);
|
||||
CHECK(waitStatusCount(&thirdTrace, 2));
|
||||
atomic_store_explicit(&drainHold, false, memory_order_release);
|
||||
CHECK(pthread_join(drainThread, NULL) == 0);
|
||||
CHECK(pthread_join(pinnedThread, NULL) == 0);
|
||||
CHECK(pthread_join(thirdThread, NULL) == 0);
|
||||
lgmp_testSetVideoStatusDispatchGate(NULL, NULL, NULL);
|
||||
CHECK(!transport);
|
||||
CHECK(atomic_load_explicit(
|
||||
&pinnedTrace.unregisterDone, memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(
|
||||
&thirdTrace.unregisterDone, memory_order_acquire));
|
||||
|
||||
/* The same lifetime rule applies when nested synchronous callbacks destroy
|
||||
* the instance belonging to the enclosing callback. */
|
||||
secondVideoOps->frame->setStatusListener(secondTransport, NULL, NULL);
|
||||
struct VideoStatusTrace syncDestroy;
|
||||
struct VideoStatusTrace syncOuter;
|
||||
memset(&syncDestroy, 0, sizeof(syncDestroy));
|
||||
memset(&syncOuter, 0, sizeof(syncOuter));
|
||||
syncDestroy.transport = secondTransport;
|
||||
syncDestroy.ops = secondVideoOps->frame;
|
||||
syncDestroy.destroyTransport = &thirdTransport;
|
||||
syncOuter.transport = thirdTransport;
|
||||
syncOuter.ops = thirdVideoOps->frame;
|
||||
syncOuter.registerTrace = &syncDestroy;
|
||||
thirdVideoOps->frame->setStatusListener(
|
||||
thirdTransport, videoStatusChanged, &syncOuter);
|
||||
CHECK(!thirdTransport);
|
||||
CHECK(atomic_load_explicit(
|
||||
&syncOuter.count, memory_order_acquire) == 1);
|
||||
CHECK(atomic_load_explicit(
|
||||
&syncDestroy.count, memory_order_acquire) == 1);
|
||||
|
||||
result = 0;
|
||||
|
||||
cleanup:
|
||||
if (admittedTransport)
|
||||
LGT_LGMP.destroy(&admittedTransport);
|
||||
if (nestedTransport)
|
||||
LGT_LGMP.destroy(&nestedTransport);
|
||||
if (thirdTransport)
|
||||
LGT_LGMP.destroy(&thirdTransport);
|
||||
if (secondTransport)
|
||||
LGT_LGMP.destroy(&secondTransport);
|
||||
if (transport)
|
||||
LGT_LGMP.destroy(&transport);
|
||||
option_free();
|
||||
lgmpHostMemFree(&pointerMemory);
|
||||
lgmpHostMemFree(&malformedPointerMemory);
|
||||
lgmpHostMemFree(&malformedFrameMemory);
|
||||
lgmpHostMemFree(&frameMemory);
|
||||
lgmpHostFree(&host);
|
||||
if (hostMemory != MAP_FAILED)
|
||||
|
||||
303
client/tests/purespice_connect_cancel_test.c
Normal file
303
client/tests/purespice_connect_cancel_test.c
Normal file
@@ -0,0 +1,303 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include <purespice.h>
|
||||
#include "ps.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/un.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define ACCEPT_TIMEOUT_MS 2000U
|
||||
#define CANCEL_BOUND_MS 500U
|
||||
|
||||
struct StallPeer
|
||||
{
|
||||
int listenFd;
|
||||
atomic_bool accepted;
|
||||
atomic_bool stop;
|
||||
};
|
||||
|
||||
struct ConnectTask
|
||||
{
|
||||
PSConfig config;
|
||||
atomic_bool done;
|
||||
bool result;
|
||||
};
|
||||
|
||||
struct ChannelConnectTask
|
||||
{
|
||||
atomic_bool done;
|
||||
bool result;
|
||||
};
|
||||
|
||||
struct PublishGate
|
||||
{
|
||||
atomic_bool entered;
|
||||
atomic_bool release;
|
||||
};
|
||||
|
||||
static void quietLog(const char * file, unsigned int line,
|
||||
const char * function, const char * format, ...)
|
||||
{
|
||||
(void)file;
|
||||
(void)line;
|
||||
(void)function;
|
||||
(void)format;
|
||||
}
|
||||
|
||||
static uint64_t monotonicMs(void)
|
||||
{
|
||||
struct timespec time;
|
||||
CHECK(clock_gettime(CLOCK_MONOTONIC, &time) == 0);
|
||||
return (uint64_t)time.tv_sec * UINT64_C(1000) +
|
||||
(uint64_t)time.tv_nsec / UINT64_C(1000000);
|
||||
}
|
||||
|
||||
static void * stallPeerThread(void * opaque)
|
||||
{
|
||||
struct StallPeer * peer = opaque;
|
||||
const int client = accept(peer->listenFd, NULL, NULL);
|
||||
if (client < 0)
|
||||
return NULL;
|
||||
|
||||
atomic_store_explicit(&peer->accepted, true, memory_order_release);
|
||||
while (!atomic_load_explicit(&peer->stop, memory_order_acquire))
|
||||
usleep(1000);
|
||||
|
||||
close(client);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void * connectThread(void * opaque)
|
||||
{
|
||||
struct ConnectTask * task = opaque;
|
||||
task->result = purespice_connect(&task->config);
|
||||
atomic_store_explicit(&task->done, true, memory_order_release);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void * channelConnectThread(void * opaque)
|
||||
{
|
||||
struct ChannelConnectTask * task = opaque;
|
||||
task->result = purespice_connectChannel(PS_CHANNEL_DISPLAY);
|
||||
atomic_store_explicit(&task->done, true, memory_order_release);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void connectPublished(void * opaque)
|
||||
{
|
||||
struct PublishGate * gate = opaque;
|
||||
atomic_store_explicit(&gate->entered, true, memory_order_release);
|
||||
while (!atomic_load_explicit(&gate->release, memory_order_acquire))
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
static void waitFor(atomic_bool * value, unsigned int timeoutMs)
|
||||
{
|
||||
const uint64_t deadline = monotonicMs() + timeoutMs;
|
||||
while (!atomic_load_explicit(value, memory_order_acquire) &&
|
||||
monotonicMs() < deadline)
|
||||
usleep(1000);
|
||||
CHECK(atomic_load_explicit(value, memory_order_acquire));
|
||||
}
|
||||
|
||||
static void testStalledHandshake(void)
|
||||
{
|
||||
const PSInit init =
|
||||
{
|
||||
.log =
|
||||
{
|
||||
.info = quietLog,
|
||||
.warn = quietLog,
|
||||
.error = quietLog,
|
||||
},
|
||||
};
|
||||
purespice_init(&init);
|
||||
|
||||
const int listenFd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
CHECK(listenFd >= 0);
|
||||
|
||||
struct sockaddr_un address =
|
||||
{
|
||||
.sun_family = AF_UNIX,
|
||||
};
|
||||
const int pathLength = snprintf(address.sun_path,
|
||||
sizeof(address.sun_path), "/tmp/lg-ps-cancel-%ld", (long)getpid());
|
||||
CHECK(pathLength > 0 && (size_t)pathLength < sizeof(address.sun_path));
|
||||
(void)unlink(address.sun_path);
|
||||
const socklen_t addressSize = sizeof(address);
|
||||
CHECK(bind(listenFd, (const struct sockaddr *)&address,
|
||||
addressSize) == 0);
|
||||
CHECK(listen(listenFd, 1) == 0);
|
||||
|
||||
struct StallPeer peer =
|
||||
{
|
||||
.listenFd = listenFd,
|
||||
};
|
||||
atomic_init(&peer.accepted, false);
|
||||
atomic_init(&peer.stop, false);
|
||||
|
||||
pthread_t peerThread;
|
||||
CHECK(pthread_create(&peerThread, NULL, stallPeerThread, &peer) == 0);
|
||||
|
||||
struct ConnectTask connect =
|
||||
{
|
||||
.config =
|
||||
{
|
||||
.host = address.sun_path,
|
||||
.port = 0,
|
||||
.password = "",
|
||||
},
|
||||
};
|
||||
atomic_init(&connect.done, false);
|
||||
|
||||
purespice_beginConnect();
|
||||
pthread_t connectThreadId;
|
||||
CHECK(pthread_create(
|
||||
&connectThreadId, NULL, connectThread, &connect) == 0);
|
||||
waitFor(&peer.accepted, ACCEPT_TIMEOUT_MS);
|
||||
|
||||
const uint64_t start = monotonicMs();
|
||||
purespice_disconnect();
|
||||
const uint64_t disconnectElapsed = monotonicMs() - start;
|
||||
|
||||
waitFor(&connect.done, CANCEL_BOUND_MS);
|
||||
const uint64_t elapsed = monotonicMs() - start;
|
||||
CHECK(pthread_join(connectThreadId, NULL) == 0);
|
||||
CHECK(!connect.result);
|
||||
CHECK(disconnectElapsed < CANCEL_BOUND_MS);
|
||||
CHECK(elapsed < CANCEL_BOUND_MS);
|
||||
|
||||
atomic_store_explicit(&peer.stop, true, memory_order_release);
|
||||
CHECK(pthread_join(peerThread, NULL) == 0);
|
||||
close(listenFd);
|
||||
CHECK(unlink(address.sun_path) == 0);
|
||||
}
|
||||
|
||||
static void testCancelBeforeConnect(void)
|
||||
{
|
||||
struct PublishGate gate;
|
||||
atomic_init(&gate.entered, false);
|
||||
atomic_init(&gate.release, false);
|
||||
purespice_testSetConnectPublishedHook(connectPublished, &gate);
|
||||
|
||||
struct ConnectTask connect =
|
||||
{
|
||||
.config =
|
||||
{
|
||||
.host = "/tmp/lg-ps-unreachable",
|
||||
.port = 0,
|
||||
.password = "",
|
||||
},
|
||||
};
|
||||
atomic_init(&connect.done, false);
|
||||
purespice_beginConnect();
|
||||
|
||||
pthread_t connectThreadId;
|
||||
CHECK(pthread_create(
|
||||
&connectThreadId, NULL, connectThread, &connect) == 0);
|
||||
waitFor(&gate.entered, ACCEPT_TIMEOUT_MS);
|
||||
|
||||
const uint64_t start = monotonicMs();
|
||||
purespice_cancelConnect();
|
||||
atomic_store_explicit(&gate.release, true, memory_order_release);
|
||||
waitFor(&connect.done, CANCEL_BOUND_MS);
|
||||
const uint64_t elapsed = monotonicMs() - start;
|
||||
|
||||
CHECK(pthread_join(connectThreadId, NULL) == 0);
|
||||
CHECK(!connect.result);
|
||||
CHECK(elapsed < CANCEL_BOUND_MS);
|
||||
purespice_testSetConnectPublishedHook(NULL, NULL);
|
||||
}
|
||||
|
||||
static void testStalledChannelHandshake(void)
|
||||
{
|
||||
const int listenFd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
CHECK(listenFd >= 0);
|
||||
|
||||
struct sockaddr_un address =
|
||||
{
|
||||
.sun_family = AF_UNIX,
|
||||
};
|
||||
const int pathLength = snprintf(address.sun_path,
|
||||
sizeof(address.sun_path), "/tmp/lg-ps-channel-%ld", (long)getpid());
|
||||
CHECK(pathLength > 0 && (size_t)pathLength < sizeof(address.sun_path));
|
||||
(void)unlink(address.sun_path);
|
||||
CHECK(bind(listenFd, (const struct sockaddr *)&address,
|
||||
sizeof(address)) == 0);
|
||||
CHECK(listen(listenFd, 1) == 0);
|
||||
|
||||
struct StallPeer peer =
|
||||
{
|
||||
.listenFd = listenFd,
|
||||
};
|
||||
atomic_init(&peer.accepted, false);
|
||||
atomic_init(&peer.stop, false);
|
||||
pthread_t peerThread;
|
||||
CHECK(pthread_create(&peerThread, NULL, stallPeerThread, &peer) == 0);
|
||||
|
||||
g_ps.family = AF_UNIX;
|
||||
memset(&g_ps.addr, 0, sizeof(g_ps.addr));
|
||||
g_ps.addr.un.sun_family = AF_UNIX;
|
||||
CHECK(snprintf(g_ps.addr.un.sun_path, sizeof(g_ps.addr.un.sun_path),
|
||||
"%s", address.sun_path) > 0);
|
||||
g_ps.epollfd = epoll_create1(0);
|
||||
CHECK(g_ps.epollfd >= 0);
|
||||
atomic_store_explicit(&g_ps.channels[PS_CHANNEL_DISPLAY].available,
|
||||
true, memory_order_release);
|
||||
|
||||
struct ChannelConnectTask connect;
|
||||
atomic_init(&connect.done, false);
|
||||
connect.result = true;
|
||||
purespice_beginConnect();
|
||||
purespice_beginChannelConnect(PS_CHANNEL_DISPLAY);
|
||||
pthread_t connectThreadId;
|
||||
CHECK(pthread_create(&connectThreadId, NULL,
|
||||
channelConnectThread, &connect) == 0);
|
||||
waitFor(&peer.accepted, ACCEPT_TIMEOUT_MS);
|
||||
|
||||
const uint64_t start = monotonicMs();
|
||||
purespice_cancelChannelConnect(PS_CHANNEL_DISPLAY);
|
||||
waitFor(&connect.done, CANCEL_BOUND_MS);
|
||||
const uint64_t elapsed = monotonicMs() - start;
|
||||
|
||||
CHECK(pthread_join(connectThreadId, NULL) == 0);
|
||||
CHECK(!connect.result);
|
||||
CHECK(elapsed < CANCEL_BOUND_MS);
|
||||
|
||||
atomic_store_explicit(&peer.stop, true, memory_order_release);
|
||||
CHECK(pthread_join(peerThread, NULL) == 0);
|
||||
close(listenFd);
|
||||
CHECK(unlink(address.sun_path) == 0);
|
||||
close(g_ps.epollfd);
|
||||
g_ps.epollfd = -1;
|
||||
atomic_store_explicit(&g_ps.channels[PS_CHANNEL_DISPLAY].available,
|
||||
false, memory_order_release);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
testStalledHandshake();
|
||||
testCancelBeforeConnect();
|
||||
testStalledChannelHandshake();
|
||||
puts("PureSpice cancellable connect test passed");
|
||||
return 0;
|
||||
}
|
||||
@@ -611,17 +611,129 @@ static void testCache(void)
|
||||
clear, false, NULL) != 0);
|
||||
renderQueue_process();
|
||||
CHECK(f.shapeCount == 2);
|
||||
CHECK(f.cursorCount == 3);
|
||||
CHECK(f.cursorCount == 4);
|
||||
CHECK(!f.cursorVisible);
|
||||
CHECK(f.colorCount == 3);
|
||||
CHECK(f.colorCount == 4);
|
||||
CHECK(f.colorFlags == 0);
|
||||
CHECK(f.colorScalar == 0.0f);
|
||||
CHECK(f.whiteCount == 3);
|
||||
CHECK(f.whiteCount == 4);
|
||||
CHECK(f.whiteLevel == LG_SDR_WHITE_LEVEL_DEFAULT);
|
||||
|
||||
stop();
|
||||
}
|
||||
|
||||
static void testCursorReplacement(void)
|
||||
{
|
||||
start();
|
||||
|
||||
const uint64_t generation =
|
||||
renderQueue_sourceBegin(RENDER_QUEUE_SOURCE_PRIMARY);
|
||||
const uint8_t shape[] = { 1, 2, 3, 4 };
|
||||
LGColorTransform color =
|
||||
{
|
||||
.flags = LG_COLOR_TRANSFORM_LUT,
|
||||
.scalar = 2.0f,
|
||||
};
|
||||
CHECK(renderQueue_sourceTransition(RENDER_QUEUE_SOURCE_PRIMARY,
|
||||
generation, false, NULL) != 0);
|
||||
renderQueue_process();
|
||||
renderQueue_sourceCursorImage(RENDER_QUEUE_SOURCE_PRIMARY, generation,
|
||||
LG_CURSOR_COLOR, 1, 1, 4, shape);
|
||||
renderQueue_sourceCursorColorTransform(RENDER_QUEUE_SOURCE_PRIMARY,
|
||||
generation, &color);
|
||||
renderQueue_sourceCursorWhiteLevel(RENDER_QUEUE_SOURCE_PRIMARY,
|
||||
generation, 250);
|
||||
renderQueue_sourceCursorState(RENDER_QUEUE_SOURCE_PRIMARY, generation,
|
||||
true, 5, 6, 1, 2);
|
||||
renderQueue_process();
|
||||
CHECK(f.shapeCount == 1);
|
||||
CHECK(f.cursorVisible);
|
||||
f.shapeCount = 0;
|
||||
f.colorCount = 0;
|
||||
f.whiteCount = 0;
|
||||
f.cursorCount = 0;
|
||||
renderQueue_sourceCursorImage(RENDER_QUEUE_SOURCE_PRIMARY, generation,
|
||||
LG_CURSOR_COLOR, 1, 1, 4, shape);
|
||||
renderQueue_sourceCursorColorTransform(RENDER_QUEUE_SOURCE_PRIMARY,
|
||||
generation, &color);
|
||||
renderQueue_sourceCursorWhiteLevel(RENDER_QUEUE_SOURCE_PRIMARY,
|
||||
generation, 250);
|
||||
renderQueue_sourceCursorState(RENDER_QUEUE_SOURCE_PRIMARY, generation,
|
||||
true, 10, 11, 1, 2);
|
||||
|
||||
renderQueue_sourceClearCursor(RENDER_QUEUE_SOURCE_PRIMARY);
|
||||
CHECK(f.shapeCount == 0);
|
||||
CHECK(f.colorCount == 0);
|
||||
CHECK(f.whiteCount == 0);
|
||||
CHECK(f.cursorCount == 0);
|
||||
renderQueue_process();
|
||||
CHECK(f.shapeCount == 0);
|
||||
CHECK(f.colorCount == 1);
|
||||
CHECK(f.colorFlags == 0);
|
||||
CHECK(f.whiteCount == 1);
|
||||
CHECK(f.whiteLevel == LG_SDR_WHITE_LEVEL_DEFAULT);
|
||||
CHECK(f.cursorCount == 1);
|
||||
CHECK(!f.cursorVisible);
|
||||
f.shapeCount = 0;
|
||||
f.colorCount = 0;
|
||||
f.whiteCount = 0;
|
||||
f.cursorCount = 0;
|
||||
renderQueue_sourceCursorImage(RENDER_QUEUE_SOURCE_PRIMARY, generation,
|
||||
LG_CURSOR_COLOR, 1, 1, 4, shape);
|
||||
renderQueue_sourceCursorState(RENDER_QUEUE_SOURCE_PRIMARY, generation,
|
||||
true, 12, 13, 1, 2);
|
||||
renderQueue_process();
|
||||
CHECK(f.shapeCount == 1);
|
||||
CHECK(f.cursorCount == 1);
|
||||
CHECK(f.cursorVisible);
|
||||
|
||||
f.shapeCount = 0;
|
||||
f.colorCount = 0;
|
||||
f.whiteCount = 0;
|
||||
f.cursorCount = 0;
|
||||
renderQueue_sourceClearCursor(RENDER_QUEUE_SOURCE_PRIMARY);
|
||||
renderQueue_sourceInvalidate(RENDER_QUEUE_SOURCE_PRIMARY, generation);
|
||||
const uint64_t replacement =
|
||||
renderQueue_sourceBegin(RENDER_QUEUE_SOURCE_PRIMARY);
|
||||
CHECK(replacement != generation);
|
||||
CHECK(f.shapeCount == 0);
|
||||
CHECK(f.colorCount == 0);
|
||||
CHECK(f.whiteCount == 0);
|
||||
CHECK(f.cursorCount == 0);
|
||||
renderQueue_process();
|
||||
CHECK(f.shapeCount == 0);
|
||||
CHECK(f.colorCount == 1);
|
||||
CHECK(f.colorFlags == 0);
|
||||
CHECK(f.whiteCount == 1);
|
||||
CHECK(f.whiteLevel == LG_SDR_WHITE_LEVEL_DEFAULT);
|
||||
CHECK(f.cursorCount == 1);
|
||||
CHECK(!f.cursorVisible);
|
||||
|
||||
f.shapeCount = 0;
|
||||
f.colorCount = 0;
|
||||
f.whiteCount = 0;
|
||||
f.cursorCount = 0;
|
||||
CHECK(renderQueue_sourceTransition(RENDER_QUEUE_SOURCE_PRIMARY,
|
||||
replacement, false, NULL) != 0);
|
||||
renderQueue_process();
|
||||
f.shapeCount = 0;
|
||||
f.colorCount = 0;
|
||||
f.whiteCount = 0;
|
||||
f.cursorCount = 0;
|
||||
renderQueue_sourceCursorImage(RENDER_QUEUE_SOURCE_PRIMARY, replacement,
|
||||
LG_CURSOR_COLOR, 1, 1, 4, shape);
|
||||
renderQueue_sourceCursorState(RENDER_QUEUE_SOURCE_PRIMARY, replacement,
|
||||
true, 20, 21, 3, 4);
|
||||
renderQueue_process();
|
||||
CHECK(f.shapeCount == 1);
|
||||
CHECK(f.cursorCount == 1);
|
||||
CHECK(f.cursorVisible);
|
||||
CHECK(f.cursorX == 20);
|
||||
CHECK(f.cursorY == 21);
|
||||
|
||||
stop();
|
||||
}
|
||||
|
||||
struct Test
|
||||
{
|
||||
const char * name;
|
||||
@@ -637,6 +749,7 @@ static const struct Test tests[] =
|
||||
{ "payload" , testPayload },
|
||||
{ "validation", testValidation },
|
||||
{ "cache" , testCache },
|
||||
{ "cursor-replacement", testCursorReplacement },
|
||||
};
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
|
||||
@@ -63,6 +63,11 @@ struct Pure
|
||||
atomic_bool connectOK;
|
||||
atomic_bool callReady;
|
||||
atomic_bool blockConnect;
|
||||
atomic_bool enterConnect;
|
||||
atomic_bool allowConnect;
|
||||
atomic_bool blockChannelConnect;
|
||||
atomic_bool enterChannelConnect;
|
||||
atomic_bool channelCancel[PS_CHANNEL_MAX];
|
||||
atomic_bool cancel;
|
||||
atomic_bool drop;
|
||||
PSStatus dropStatus;
|
||||
@@ -136,8 +141,18 @@ void purespice_init(const PSInit * init)
|
||||
(void)init;
|
||||
}
|
||||
|
||||
void purespice_beginConnect(void)
|
||||
{
|
||||
atomic_store_explicit(&ps.cancel, false, memory_order_release);
|
||||
}
|
||||
|
||||
bool purespice_connect(const PSConfig * config)
|
||||
{
|
||||
while (!atomic_load_explicit(&ps.allowConnect, memory_order_acquire) &&
|
||||
!atomic_load_explicit(&ps.cancel, memory_order_acquire))
|
||||
usleep(1000);
|
||||
atomic_store_explicit(&ps.enterConnect, true, memory_order_release);
|
||||
|
||||
ps.config = *config;
|
||||
ps.connectOrder = nextOrder();
|
||||
atomic_fetch_add(&ps.connectN, 1);
|
||||
@@ -152,6 +167,23 @@ bool purespice_connect(const PSConfig * config)
|
||||
return true;
|
||||
}
|
||||
|
||||
void purespice_cancelConnect(void)
|
||||
{
|
||||
atomic_store_explicit(&ps.cancel, true, memory_order_release);
|
||||
}
|
||||
|
||||
void purespice_beginChannelConnect(PSChannelType channel)
|
||||
{
|
||||
atomic_store_explicit(
|
||||
&ps.channelCancel[channel], false, memory_order_release);
|
||||
}
|
||||
|
||||
void purespice_cancelChannelConnect(PSChannelType channel)
|
||||
{
|
||||
atomic_store_explicit(
|
||||
&ps.channelCancel[channel], true, memory_order_release);
|
||||
}
|
||||
|
||||
void purespice_disconnect(void)
|
||||
{
|
||||
ps.disconnectOrder = nextOrder();
|
||||
@@ -201,6 +233,18 @@ bool purespice_channelConnected(PSChannelType channel)
|
||||
bool purespice_connectChannel(PSChannelType channel)
|
||||
{
|
||||
++ps.connectChannelN[channel];
|
||||
atomic_store_explicit(
|
||||
&ps.enterChannelConnect, true, memory_order_release);
|
||||
while (atomic_load_explicit(
|
||||
&ps.blockChannelConnect, memory_order_acquire) &&
|
||||
!atomic_load_explicit(&ps.cancel, memory_order_acquire) &&
|
||||
!atomic_load_explicit(
|
||||
&ps.channelCancel[channel], memory_order_acquire))
|
||||
usleep(1000);
|
||||
if (atomic_load_explicit(&ps.cancel, memory_order_acquire) ||
|
||||
atomic_load_explicit(
|
||||
&ps.channelCancel[channel], memory_order_acquire))
|
||||
return false;
|
||||
if (!ps.connectResult[channel])
|
||||
return false;
|
||||
ps.connected[channel] = true;
|
||||
@@ -335,6 +379,12 @@ static void resetPure(void)
|
||||
atomic_init(&ps.connectOK, true);
|
||||
atomic_init(&ps.callReady, true);
|
||||
atomic_init(&ps.blockConnect, false);
|
||||
atomic_init(&ps.enterConnect, false);
|
||||
atomic_init(&ps.allowConnect, true);
|
||||
atomic_init(&ps.blockChannelConnect, false);
|
||||
atomic_init(&ps.enterChannelConnect, false);
|
||||
for(unsigned int i = 0; i < PS_CHANNEL_MAX; ++i)
|
||||
atomic_init(&ps.channelCancel[i], false);
|
||||
atomic_init(&ps.cancel, false);
|
||||
atomic_init(&ps.drop, false);
|
||||
atomic_init(&ps.connectN, 0);
|
||||
@@ -485,10 +535,16 @@ static bool cancel(void * opaque)
|
||||
{
|
||||
unsigned int * calls = opaque;
|
||||
++*calls;
|
||||
atomic_store(&ps.cancel, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cancelBeforeEntry(void * opaque)
|
||||
{
|
||||
unsigned int * calls = opaque;
|
||||
++*calls;
|
||||
return !atomic_load_explicit(&ps.enterConnect, memory_order_acquire);
|
||||
}
|
||||
|
||||
static void testConnectFail(void)
|
||||
{
|
||||
resetPure();
|
||||
@@ -560,6 +616,19 @@ static void testCancel(void)
|
||||
CHECK(f.clipboard.offN == 1);
|
||||
CHECK(!atomic_load(&cbTarget));
|
||||
freeFixture(&f);
|
||||
|
||||
resetPure();
|
||||
atomic_store_explicit(&ps.allowConnect, false, memory_order_release);
|
||||
atomic_store_explicit(&ps.enterConnect, false, memory_order_release);
|
||||
initFixture(&f);
|
||||
cancelN = 0;
|
||||
CHECK(LGT_SPICE.connectCancellable(&f.transport, &session,
|
||||
cancelBeforeEntry, &cancelN) == LG_TRANSPORT_DISCONNECTED);
|
||||
CHECK(cancelN == 1);
|
||||
CHECK(!f.transport.thread);
|
||||
CHECK(!LGT_SPICE.sessionValid(&f.transport));
|
||||
CHECK(atomic_load_explicit(&ps.cancel, memory_order_acquire));
|
||||
freeFixture(&f);
|
||||
}
|
||||
|
||||
static void testReadyDrop(void)
|
||||
@@ -704,6 +773,148 @@ static void testSurfaceActive(void)
|
||||
freeFixture(&f);
|
||||
}
|
||||
|
||||
struct ActiveCall
|
||||
{
|
||||
const LG_SwSurfaceOps * ops;
|
||||
LG_Transport * transport;
|
||||
atomic_bool done;
|
||||
bool result;
|
||||
};
|
||||
|
||||
static void * activateSurface(void * opaque)
|
||||
{
|
||||
struct ActiveCall * call = opaque;
|
||||
call->result = call->ops->setActive(call->transport, true);
|
||||
atomic_store_explicit(&call->done, true, memory_order_release);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void * holdActivationLock(void * opaque)
|
||||
{
|
||||
LG_Transport * transport = opaque;
|
||||
spiceSurface_testLockActivation(transport->surface);
|
||||
atomic_store_explicit(
|
||||
&ps.enterChannelConnect, true, memory_order_release);
|
||||
while (atomic_load_explicit(
|
||||
&ps.blockChannelConnect, memory_order_acquire))
|
||||
usleep(1000);
|
||||
spiceSurface_testUnlockActivation(transport->surface);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void testSurfaceCancel(void)
|
||||
{
|
||||
resetPure();
|
||||
struct Fixture f;
|
||||
initFixture(&f);
|
||||
const LG_SwSurfaceOps * sw = spiceSurface_getVideoOps()->swSurface;
|
||||
CHECK(sw->attach(&f.transport, &surfaceEvents, &f.log));
|
||||
atomic_store_explicit(
|
||||
&f.transport.sessionValid, true, memory_order_release);
|
||||
atomic_store_explicit(
|
||||
&ps.blockChannelConnect, true, memory_order_release);
|
||||
|
||||
struct ActiveCall call =
|
||||
{
|
||||
.ops = sw,
|
||||
.transport = &f.transport,
|
||||
};
|
||||
atomic_init(&call.done, false);
|
||||
pthread_t thread;
|
||||
CHECK(pthread_create(&thread, NULL, activateSurface, &call) == 0);
|
||||
waitBool(&ps.enterChannelConnect);
|
||||
|
||||
sw->cancelPending(&f.transport);
|
||||
waitBool(&call.done);
|
||||
CHECK(pthread_join(thread, NULL) == 0);
|
||||
CHECK(!call.result);
|
||||
CHECK(atomic_load_explicit(
|
||||
&ps.channelCancel[PS_CHANNEL_DISPLAY], memory_order_acquire));
|
||||
CHECK(!atomic_load_explicit(&ps.cancel, memory_order_acquire));
|
||||
CHECK(ps.connectChannelN[PS_CHANNEL_DISPLAY] == 1);
|
||||
CHECK(ps.connectChannelN[PS_CHANNEL_CURSOR] == 0);
|
||||
CHECK(LGT_SPICE.sessionValid(&f.transport));
|
||||
|
||||
atomic_store_explicit(
|
||||
&ps.blockChannelConnect, false, memory_order_release);
|
||||
CHECK(sw->setActive(&f.transport, true));
|
||||
CHECK(LGT_SPICE.sessionValid(&f.transport));
|
||||
CHECK(sw->setActive(&f.transport, false));
|
||||
|
||||
sw->detach(&f.transport);
|
||||
freeFixture(&f);
|
||||
|
||||
resetPure();
|
||||
initFixture(&f);
|
||||
sw = spiceSurface_getVideoOps()->swSurface;
|
||||
CHECK(sw->attach(&f.transport, &surfaceEvents, &f.log));
|
||||
atomic_store_explicit(
|
||||
&f.transport.sessionValid, true, memory_order_release);
|
||||
atomic_store_explicit(
|
||||
&ps.blockChannelConnect, true, memory_order_release);
|
||||
|
||||
pthread_t holdThread;
|
||||
CHECK(pthread_create(
|
||||
&holdThread, NULL, holdActivationLock, &f.transport) == 0);
|
||||
waitBool(&ps.enterChannelConnect);
|
||||
|
||||
call = (struct ActiveCall)
|
||||
{
|
||||
.ops = sw,
|
||||
.transport = &f.transport,
|
||||
};
|
||||
atomic_init(&call.done, false);
|
||||
CHECK(pthread_create(&thread, NULL, activateSurface, &call) == 0);
|
||||
usleep(10000);
|
||||
sw->cancelPending(&f.transport);
|
||||
atomic_store_explicit(
|
||||
&ps.blockChannelConnect, false, memory_order_release);
|
||||
CHECK(pthread_join(holdThread, NULL) == 0);
|
||||
waitBool(&call.done);
|
||||
CHECK(pthread_join(thread, NULL) == 0);
|
||||
CHECK(!call.result);
|
||||
CHECK(ps.connectChannelN[PS_CHANNEL_DISPLAY] == 0);
|
||||
|
||||
sw->detach(&f.transport);
|
||||
freeFixture(&f);
|
||||
|
||||
resetPure();
|
||||
initFixture(&f);
|
||||
sw = spiceSurface_getVideoOps()->swSurface;
|
||||
CHECK(sw->attach(&f.transport, &surfaceEvents, &f.log));
|
||||
atomic_store_explicit(
|
||||
&f.transport.sessionValid, true, memory_order_release);
|
||||
atomic_store_explicit(
|
||||
&ps.blockChannelConnect, true, memory_order_release);
|
||||
|
||||
call = (struct ActiveCall)
|
||||
{
|
||||
.ops = sw,
|
||||
.transport = &f.transport,
|
||||
};
|
||||
atomic_init(&call.done, false);
|
||||
CHECK(pthread_create(&thread, NULL, activateSurface, &call) == 0);
|
||||
waitBool(&ps.enterChannelConnect);
|
||||
|
||||
atomic_store_explicit(
|
||||
&f.transport.sessionValid, false, memory_order_release);
|
||||
spiceSurface_sessionStopped(f.transport.surface);
|
||||
waitBool(&call.done);
|
||||
CHECK(pthread_join(thread, NULL) == 0);
|
||||
CHECK(!call.result);
|
||||
CHECK(atomic_load_explicit(
|
||||
&ps.channelCancel[PS_CHANNEL_DISPLAY], memory_order_acquire));
|
||||
CHECK(atomic_load_explicit(
|
||||
&ps.channelCancel[PS_CHANNEL_CURSOR], memory_order_acquire));
|
||||
CHECK(!atomic_load_explicit(&ps.cancel, memory_order_acquire));
|
||||
CHECK(sw->setActive(&f.transport, false));
|
||||
|
||||
atomic_store_explicit(
|
||||
&ps.blockChannelConnect, false, memory_order_release);
|
||||
sw->detach(&f.transport);
|
||||
freeFixture(&f);
|
||||
}
|
||||
|
||||
static void testSurfaceEvents(void)
|
||||
{
|
||||
resetPure();
|
||||
@@ -864,6 +1075,7 @@ static const struct Test tests[] =
|
||||
{ "ready-drop" , testReadyDrop },
|
||||
{ "session-order" , testSessionOrder },
|
||||
{ "surface-active", testSurfaceActive },
|
||||
{ "surface-cancel", testSurfaceCancel },
|
||||
{ "surface-events", testSurfaceEvents },
|
||||
{ "surface-detach", testSurfaceDetach },
|
||||
};
|
||||
|
||||
84
client/tests/sw_surface_test.c
Normal file
84
client/tests/sw_surface_test.c
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "sw_surface.h"
|
||||
#include "test.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
struct LG_Transport
|
||||
{
|
||||
atomic_bool entered;
|
||||
atomic_bool armed;
|
||||
atomic_bool release;
|
||||
atomic_uint cancelCount;
|
||||
atomic_uint on;
|
||||
atomic_uint off;
|
||||
};
|
||||
|
||||
static bool setActive(LG_Transport * transport, bool active)
|
||||
{
|
||||
atomic_fetch_add(active ? &transport->on : &transport->off, 1);
|
||||
atomic_store(&transport->entered, true);
|
||||
if (active)
|
||||
{
|
||||
/* Lose the first cancellation edge before arming the wait. */
|
||||
while (!atomic_load(&transport->cancelCount))
|
||||
usleep(1000);
|
||||
atomic_store(&transport->release, false);
|
||||
atomic_store(&transport->armed, true);
|
||||
while (!atomic_load(&transport->release))
|
||||
usleep(1000);
|
||||
}
|
||||
return !active || atomic_load(&transport->release);
|
||||
}
|
||||
|
||||
static void cancelPending(LG_Transport * transport)
|
||||
{
|
||||
atomic_fetch_add(&transport->cancelCount, 1);
|
||||
if (atomic_load(&transport->armed))
|
||||
atomic_store(&transport->release, true);
|
||||
}
|
||||
|
||||
static bool cancelled(void * opaque)
|
||||
{
|
||||
return *(const bool *)opaque;
|
||||
}
|
||||
|
||||
static const LG_SwSurfaceOps ops =
|
||||
{
|
||||
.setActive = setActive,
|
||||
.cancelPending = cancelPending,
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
LG_Transport transport = { 0 };
|
||||
const bool stop = true;
|
||||
CHECK(!lgSwSurface_setActive(
|
||||
&ops, &transport, true, cancelled, (void *)&stop));
|
||||
CHECK(atomic_load(&transport.entered));
|
||||
CHECK(atomic_load(&transport.cancelCount) >= 2);
|
||||
CHECK(atomic_load(&transport.on) == 1);
|
||||
CHECK(atomic_load(&transport.off) == 1);
|
||||
|
||||
atomic_store(&transport.entered, false);
|
||||
atomic_store(&transport.release, false);
|
||||
atomic_store(&transport.cancelCount, 0);
|
||||
CHECK(lgSwSurface_setActive(
|
||||
&ops, &transport, false, cancelled, (void *)&stop));
|
||||
CHECK(atomic_load(&transport.entered));
|
||||
CHECK(atomic_load(&transport.cancelCount) == 0);
|
||||
CHECK(atomic_load(&transport.off) == 2);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -63,6 +63,16 @@ struct TransportTrace
|
||||
atomic_bool release;
|
||||
atomic_bool cancel;
|
||||
atomic_bool badProvider;
|
||||
atomic_bool noVideo;
|
||||
atomic_bool failAttach;
|
||||
atomic_bool failActive;
|
||||
atomic_bool blockActive;
|
||||
atomic_bool activeEntered;
|
||||
atomic_bool activeCancelled;
|
||||
atomic_bool holdActiveReturn;
|
||||
atomic_uint cancelPending;
|
||||
const LG_SwSurfaceEventOps * surfaceEvents;
|
||||
void * surfaceOpaque;
|
||||
atomic_uint_fast64_t connTime[4];
|
||||
unsigned int failConn;
|
||||
bool uuidValid;
|
||||
@@ -74,6 +84,11 @@ struct EventTrace
|
||||
atomic_uint conn;
|
||||
atomic_uint disc;
|
||||
atomic_uint mismatch;
|
||||
atomic_uint videoReady;
|
||||
atomic_uint videoUnavailable;
|
||||
atomic_uint configureAccepted;
|
||||
atomic_uint surfaceCleanup;
|
||||
atomic_bool surfaceRetained;
|
||||
LG_TransportSession session;
|
||||
uint8_t primary[16];
|
||||
uint8_t fallback[16];
|
||||
@@ -86,6 +101,7 @@ static struct ProviderTrace pInput;
|
||||
static struct ProviderTrace pAudio;
|
||||
#endif
|
||||
static struct ProviderTrace pClipboard;
|
||||
static _Atomic(LG_TransportFallback *) surfaceFallback;
|
||||
|
||||
static const uint8_t UUID_A[16] =
|
||||
{ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
@@ -125,6 +141,18 @@ static bool waitReady(const LG_TransportFallback * fallback)
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool waitUsable(const LG_TransportFallback * fallback)
|
||||
{
|
||||
for (unsigned int i = 0; i < TEST_WAIT_MS; ++i)
|
||||
{
|
||||
if (lgTransportFallback_usable(fallback))
|
||||
return true;
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void setRemote(const uint8_t uuid[16])
|
||||
{
|
||||
t.uuidValid = true;
|
||||
@@ -203,8 +231,10 @@ static bool fakeAttach(LG_Transport * transport,
|
||||
{
|
||||
CHECK(transport);
|
||||
CHECK(events);
|
||||
t.surfaceEvents = events;
|
||||
t.surfaceOpaque = opaque;
|
||||
atomic_fetch_add(&t.attach, 1);
|
||||
return true;
|
||||
return !atomic_load(&t.failAttach);
|
||||
}
|
||||
|
||||
static void fakeDetach(LG_Transport * transport)
|
||||
@@ -217,7 +247,20 @@ static bool fakeActive(LG_Transport * transport, bool active)
|
||||
{
|
||||
CHECK(transport);
|
||||
atomic_fetch_add(active ? &t.on : &t.off, 1);
|
||||
return true;
|
||||
atomic_store(&t.activeEntered, true);
|
||||
while (atomic_load(&t.blockActive) &&
|
||||
!atomic_load(&t.activeCancelled))
|
||||
usleep(1000);
|
||||
while (active && atomic_load(&t.holdActiveReturn))
|
||||
usleep(1000);
|
||||
return !atomic_load(&t.failActive);
|
||||
}
|
||||
|
||||
static void fakeCancelPending(LG_Transport * transport)
|
||||
{
|
||||
CHECK(transport);
|
||||
atomic_fetch_add(&t.cancelPending, 1);
|
||||
atomic_store(&t.activeCancelled, true);
|
||||
}
|
||||
|
||||
static const LG_InputOps inputOps =
|
||||
@@ -264,6 +307,7 @@ static const LG_SwSurfaceOps surfaceOps =
|
||||
.attach = fakeAttach,
|
||||
.detach = fakeDetach,
|
||||
.setActive = fakeActive,
|
||||
.cancelPending = fakeCancelPending,
|
||||
};
|
||||
|
||||
static const LG_VideoOps videoOps =
|
||||
@@ -276,7 +320,7 @@ static const LG_VideoOps videoOps =
|
||||
static const LG_VideoOps * fakeGetVideo(LG_Transport * transport)
|
||||
{
|
||||
CHECK(transport);
|
||||
return &videoOps;
|
||||
return atomic_load(&t.noVideo) ? NULL : &videoOps;
|
||||
}
|
||||
|
||||
static const LG_TransportOps transportOps =
|
||||
@@ -389,20 +433,48 @@ static void onMismatch(void * opaque, const uint8_t primary[16],
|
||||
atomic_fetch_add(&e.mismatch, 1);
|
||||
}
|
||||
|
||||
static void onVideoState(void * opaque, bool ready)
|
||||
{
|
||||
CHECK(opaque == &e);
|
||||
atomic_fetch_add(ready ? &e.videoReady : &e.videoUnavailable, 1);
|
||||
if (!ready && atomic_exchange(&e.surfaceRetained, false))
|
||||
atomic_fetch_add(&e.surfaceCleanup, 1);
|
||||
}
|
||||
|
||||
static void onSurfaceConfigure(void * opaque,
|
||||
unsigned int width, unsigned int height)
|
||||
{
|
||||
CHECK(opaque == &e);
|
||||
CHECK(width == 1920);
|
||||
CHECK(height == 1080);
|
||||
LG_TransportFallback * fallback = atomic_load(&surfaceFallback);
|
||||
CHECK(fallback);
|
||||
if (!lgTransportFallback_acceptsVideoEvents(fallback))
|
||||
return;
|
||||
|
||||
atomic_store(&e.surfaceRetained, true);
|
||||
atomic_fetch_add(&e.configureAccepted, 1);
|
||||
}
|
||||
|
||||
static LG_TransportFallback * start(const uint8_t primary[16])
|
||||
{
|
||||
static const LG_SwSurfaceEventOps surfaceEvents = { 0 };
|
||||
static const LG_SwSurfaceEventOps surfaceEvents =
|
||||
{
|
||||
.configure = onSurfaceConfigure,
|
||||
};
|
||||
static const LG_TransportFallbackEventOps eventOps =
|
||||
{
|
||||
.connected = onConnected,
|
||||
.disconnected = onDisconnected,
|
||||
.uuidMismatch = onMismatch,
|
||||
.videoStateChanged = onVideoState,
|
||||
.endpointMismatch = onMismatch,
|
||||
};
|
||||
|
||||
LG_TransportFallback * fallback = NULL;
|
||||
CHECK(lgTransportFallback_start("fake", &surfaceEvents, NULL,
|
||||
CHECK(lgTransportFallback_start("fake", &surfaceEvents, &e,
|
||||
&eventOps, &e, primary, &fallback));
|
||||
CHECK(fallback);
|
||||
atomic_store(&surfaceFallback, fallback);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@@ -438,8 +510,9 @@ static void testGraceful(void)
|
||||
atomic_store(&t.release, true);
|
||||
CHECK(waitReady(fallback));
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
CHECK(waitCount(&e.videoReady, 1));
|
||||
|
||||
CHECK(lgTransportFallback_admitted(fallback));
|
||||
CHECK(lgTransportFallback_usable(fallback));
|
||||
CHECK(atomic_load(&t.on) == 1);
|
||||
CHECK(memcmp(e.session.uuid, UUID_A, sizeof(UUID_A)) == 0);
|
||||
checkPublished(1, 0, 0);
|
||||
@@ -459,17 +532,17 @@ static void testDead(void)
|
||||
{
|
||||
setRemote(UUID_A);
|
||||
LG_TransportFallback * fallback = start(UUID_A);
|
||||
CHECK(waitReady(fallback));
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
|
||||
lgTransportFallback_requestVideoActive(fallback, true);
|
||||
CHECK(waitCount(&t.on, 1));
|
||||
CHECK(waitReady(fallback));
|
||||
atomic_store(&t.alive, false);
|
||||
lgTransportFallback_requestVideoActive(fallback, true);
|
||||
CHECK(waitCount(&e.disc, 1));
|
||||
|
||||
CHECK(!lgTransportFallback_ready(fallback));
|
||||
CHECK(!lgTransportFallback_admitted(fallback));
|
||||
CHECK(!lgTransportFallback_usable(fallback));
|
||||
CHECK(atomic_load(&t.off) == 0);
|
||||
checkPublished(1, 0, 1);
|
||||
|
||||
@@ -487,7 +560,7 @@ static void testMismatch(void)
|
||||
CHECK(waitCount(&t.free, 2));
|
||||
|
||||
CHECK(!lgTransportFallback_ready(fallback));
|
||||
CHECK(!lgTransportFallback_admitted(fallback));
|
||||
CHECK(!lgTransportFallback_usable(fallback));
|
||||
CHECK(atomic_load(&e.conn) == 0);
|
||||
CHECK(atomic_load(&e.disc) == 0);
|
||||
CHECK(atomic_load(&e.mismatch) == 1);
|
||||
@@ -503,11 +576,27 @@ static void testMismatch(void)
|
||||
CHECK(atomic_load(&t.free) == 2);
|
||||
}
|
||||
|
||||
static void testRevoke(void)
|
||||
static void testMissingUUID(void)
|
||||
{
|
||||
LG_TransportFallback * fallback = start(UUID_A);
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
CHECK(lgTransportFallback_usable(fallback));
|
||||
CHECK(atomic_load(&e.mismatch) == 0);
|
||||
lgTransportFallback_stop(&fallback);
|
||||
|
||||
setRemote(UUID_B);
|
||||
fallback = start(NULL);
|
||||
CHECK(waitCount(&e.conn, 2));
|
||||
CHECK(lgTransportFallback_usable(fallback));
|
||||
CHECK(atomic_load(&e.mismatch) == 0);
|
||||
lgTransportFallback_stop(&fallback);
|
||||
checkPublished(2, 2, 0);
|
||||
}
|
||||
|
||||
static void testLateMismatch(void)
|
||||
{
|
||||
setRemote(UUID_A);
|
||||
LG_TransportFallback * fallback = start(UUID_A);
|
||||
CHECK(waitReady(fallback));
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
|
||||
lgTransportFallback_setPrimaryUUID(fallback, UUID_B);
|
||||
@@ -515,7 +604,7 @@ static void testRevoke(void)
|
||||
CHECK(waitCount(&t.free, 2));
|
||||
|
||||
CHECK(!lgTransportFallback_ready(fallback));
|
||||
CHECK(!lgTransportFallback_admitted(fallback));
|
||||
CHECK(!lgTransportFallback_usable(fallback));
|
||||
CHECK(atomic_load(&e.conn) == 1);
|
||||
CHECK(atomic_load(&e.disc) == 1);
|
||||
CHECK(atomic_load(&e.mismatch) == 1);
|
||||
@@ -535,7 +624,6 @@ static void testRetry(void)
|
||||
setRemote(UUID_A);
|
||||
t.failConn = 2;
|
||||
LG_TransportFallback * fallback = start(UUID_A);
|
||||
CHECK(waitReady(fallback));
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
|
||||
const uint64_t first = atomic_load(&t.connTime[0]);
|
||||
@@ -549,8 +637,8 @@ static void testRetry(void)
|
||||
lgTransportFallback_stop(&fallback);
|
||||
CHECK(atomic_load(&e.conn) == 1);
|
||||
CHECK(atomic_load(&e.disc) == 1);
|
||||
CHECK(atomic_load(&t.attach) == 3);
|
||||
CHECK(atomic_load(&t.detach) == 3);
|
||||
CHECK(atomic_load(&t.attach) == 1);
|
||||
CHECK(atomic_load(&t.detach) == 1);
|
||||
CHECK(atomic_load(&t.disc) == 3);
|
||||
CHECK(atomic_load(&t.free) == 3);
|
||||
checkPublished(1, 1, 0);
|
||||
@@ -568,13 +656,191 @@ static void testCancel(void)
|
||||
CHECK(atomic_load(&t.cancel));
|
||||
CHECK(atomic_load(&e.conn) == 0);
|
||||
CHECK(atomic_load(&e.disc) == 0);
|
||||
CHECK(atomic_load(&t.attach) == 1);
|
||||
CHECK(atomic_load(&t.detach) == 1);
|
||||
CHECK(atomic_load(&t.attach) == 0);
|
||||
CHECK(atomic_load(&t.detach) == 0);
|
||||
CHECK(atomic_load(&t.disc) == 1);
|
||||
CHECK(atomic_load(&t.free) == 1);
|
||||
checkPublished(0, 0, 0);
|
||||
}
|
||||
|
||||
static void testWithoutVideo(void)
|
||||
{
|
||||
setRemote(UUID_A);
|
||||
atomic_store(&t.noVideo, true);
|
||||
LG_TransportFallback * fallback = start(UUID_A);
|
||||
CHECK(waitUsable(fallback));
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
CHECK(!lgTransportFallback_ready(fallback));
|
||||
checkPublished(1, 0, 0);
|
||||
|
||||
lgTransportFallback_requestVideoActive(fallback, true);
|
||||
usleep(50000);
|
||||
CHECK(atomic_load(&t.on) == 0);
|
||||
CHECK(lgTransportFallback_usable(fallback));
|
||||
|
||||
lgTransportFallback_stop(&fallback);
|
||||
CHECK(atomic_load(&t.attach) == 0);
|
||||
CHECK(atomic_load(&t.detach) == 0);
|
||||
checkPublished(1, 1, 0);
|
||||
}
|
||||
|
||||
static void testAttachFailure(void)
|
||||
{
|
||||
setRemote(UUID_A);
|
||||
atomic_store(&t.failAttach, true);
|
||||
LG_TransportFallback * fallback = start(UUID_A);
|
||||
CHECK(waitUsable(fallback));
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
CHECK(!lgTransportFallback_ready(fallback));
|
||||
CHECK(atomic_load(&t.attach) == 1);
|
||||
CHECK(atomic_load(&t.detach) == 0);
|
||||
checkPublished(1, 0, 0);
|
||||
lgTransportFallback_stop(&fallback);
|
||||
checkPublished(1, 1, 0);
|
||||
}
|
||||
|
||||
static void testActiveFailure(void)
|
||||
{
|
||||
setRemote(UUID_A);
|
||||
atomic_store(&t.failActive, true);
|
||||
LG_TransportFallback * fallback = start(UUID_A);
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
lgTransportFallback_requestVideoActive(fallback, true);
|
||||
CHECK(waitCount(&t.on, 1));
|
||||
CHECK(waitCount(&e.videoUnavailable, 1));
|
||||
CHECK(lgTransportFallback_usable(fallback));
|
||||
CHECK(!lgTransportFallback_ready(fallback));
|
||||
checkPublished(1, 0, 0);
|
||||
lgTransportFallback_stop(&fallback);
|
||||
CHECK(atomic_load(&t.detach) == 1);
|
||||
checkPublished(1, 1, 0);
|
||||
}
|
||||
|
||||
static void testActivationCancel(void)
|
||||
{
|
||||
setRemote(UUID_A);
|
||||
atomic_store(&t.blockActive, true);
|
||||
LG_TransportFallback * fallback = start(UUID_A);
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
lgTransportFallback_requestVideoActive(fallback, true);
|
||||
CHECK(waitCount(&t.on, 1));
|
||||
CHECK(atomic_load(&t.activeEntered));
|
||||
lgTransportFallback_stop(&fallback);
|
||||
CHECK(!fallback);
|
||||
CHECK(atomic_load(&t.cancelPending) > 0);
|
||||
CHECK(atomic_load(&t.detach) == 1);
|
||||
CHECK(atomic_load(&t.disc) == 1);
|
||||
CHECK(atomic_load(&t.free) == 1);
|
||||
}
|
||||
|
||||
static void testActivationRequestCancel(void)
|
||||
{
|
||||
setRemote(UUID_A);
|
||||
atomic_store(&t.blockActive, true);
|
||||
LG_TransportFallback * fallback = start(UUID_A);
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
|
||||
lgTransportFallback_requestVideoActive(fallback, true);
|
||||
CHECK(waitCount(&t.on, 1));
|
||||
CHECK(atomic_load(&t.activeEntered));
|
||||
CHECK(lgTransportFallback_acceptsVideoEvents(fallback));
|
||||
CHECK(t.surfaceEvents);
|
||||
CHECK(t.surfaceEvents->configure);
|
||||
t.surfaceEvents->configure(t.surfaceOpaque, 1920, 1080);
|
||||
CHECK(atomic_load(&e.configureAccepted) == 1);
|
||||
CHECK(atomic_load(&e.surfaceRetained));
|
||||
|
||||
lgTransportFallback_requestVideoActive(fallback, false);
|
||||
CHECK(waitCount(&t.cancelPending, 1));
|
||||
CHECK(waitCount(&t.off, 1));
|
||||
CHECK(waitCount(&e.videoUnavailable, 1));
|
||||
usleep(250000);
|
||||
|
||||
CHECK(atomic_load(&e.videoUnavailable) == 1);
|
||||
CHECK(atomic_load(&e.videoReady) == 0);
|
||||
CHECK(atomic_load(&e.configureAccepted) == 1);
|
||||
CHECK(atomic_load(&e.surfaceCleanup) == 1);
|
||||
CHECK(!atomic_load(&e.surfaceRetained));
|
||||
CHECK(!lgTransportFallback_ready(fallback));
|
||||
CHECK(!lgTransportFallback_acceptsVideoEvents(fallback));
|
||||
CHECK(lgTransportFallback_usable(fallback));
|
||||
checkPublished(1, 0, 0);
|
||||
|
||||
lgTransportFallback_stop(&fallback);
|
||||
CHECK(!fallback);
|
||||
CHECK(atomic_load(&t.off) == 1);
|
||||
CHECK(atomic_load(&t.detach) == 1);
|
||||
CHECK(atomic_load(&t.disc) == 1);
|
||||
CHECK(atomic_load(&t.free) == 1);
|
||||
checkPublished(1, 1, 0);
|
||||
}
|
||||
|
||||
static void testDeactivationCompletes(void)
|
||||
{
|
||||
setRemote(UUID_A);
|
||||
LG_TransportFallback * fallback = start(UUID_A);
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
lgTransportFallback_requestVideoActive(fallback, true);
|
||||
CHECK(waitReady(fallback));
|
||||
atomic_store(&t.activeEntered, false);
|
||||
lgTransportFallback_stop(&fallback);
|
||||
CHECK(!fallback);
|
||||
CHECK(atomic_load(&t.off) == 1);
|
||||
CHECK(atomic_load(&t.cancelPending) == 0);
|
||||
CHECK(atomic_load(&t.detach) == 1);
|
||||
CHECK(atomic_load(&t.disc) == 1);
|
||||
CHECK(atomic_load(&t.free) == 1);
|
||||
}
|
||||
|
||||
static void testMismatchDuringActivation(void)
|
||||
{
|
||||
setRemote(UUID_A);
|
||||
atomic_store(&t.holdActiveReturn, true);
|
||||
LG_TransportFallback * fallback = start(UUID_A);
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
lgTransportFallback_requestVideoActive(fallback, true);
|
||||
CHECK(waitCount(&t.on, 1));
|
||||
CHECK(atomic_load(&t.activeEntered));
|
||||
lgTransportFallback_setPrimaryUUID(fallback, UUID_B);
|
||||
CHECK(waitCount(&e.disc, 1));
|
||||
atomic_store(&t.holdActiveReturn, false);
|
||||
CHECK(waitCount(&t.free, 1));
|
||||
CHECK(!lgTransportFallback_ready(fallback));
|
||||
CHECK(!lgTransportFallback_usable(fallback));
|
||||
CHECK(!lgTransportFallback_acceptsVideoEvents(fallback));
|
||||
CHECK(atomic_load(&e.videoReady) == 0);
|
||||
lgTransportFallback_stop(&fallback);
|
||||
}
|
||||
|
||||
static void testPreRequestedActivation(void)
|
||||
{
|
||||
setRemote(UUID_A);
|
||||
atomic_store(&t.hold, true);
|
||||
atomic_store(&t.blockActive, true);
|
||||
LG_TransportFallback * fallback = start(UUID_A);
|
||||
CHECK(waitCount(&t.conn, 1));
|
||||
lgTransportFallback_requestVideoActive(fallback, true);
|
||||
atomic_store(&t.release, true);
|
||||
CHECK(waitCount(&e.conn, 1));
|
||||
CHECK(waitCount(&t.on, 1));
|
||||
CHECK(atomic_load(&t.activeEntered));
|
||||
CHECK(!lgTransportFallback_ready(fallback));
|
||||
|
||||
atomic_store(&t.blockActive, false);
|
||||
CHECK(waitReady(fallback));
|
||||
CHECK(waitCount(&e.videoReady, 1));
|
||||
lgTransportFallback_setPrimaryUUID(fallback, UUID_B);
|
||||
CHECK(waitCount(&e.disc, 1));
|
||||
CHECK(waitCount(&t.free, 1));
|
||||
CHECK(atomic_load(&e.conn) == 1);
|
||||
CHECK(atomic_load(&e.disc) == 1);
|
||||
CHECK(atomic_load(&e.mismatch) == 1);
|
||||
CHECK(!lgTransportFallback_usable(fallback));
|
||||
CHECK(!lgTransportFallback_ready(fallback));
|
||||
lgTransportFallback_stop(&fallback);
|
||||
}
|
||||
|
||||
|
||||
struct Test
|
||||
{
|
||||
const char * name;
|
||||
@@ -586,9 +852,18 @@ static const struct Test tests[] =
|
||||
{ "graceful", testGraceful },
|
||||
{ "dead" , testDead },
|
||||
{ "mismatch", testMismatch },
|
||||
{ "revoke" , testRevoke },
|
||||
{ "missing-uuid", testMissingUUID },
|
||||
{ "late-mismatch", testLateMismatch },
|
||||
{ "retry" , testRetry },
|
||||
{ "cancel" , testCancel },
|
||||
{ "no-video", testWithoutVideo },
|
||||
{ "attach-fail", testAttachFailure },
|
||||
{ "active-fail", testActiveFailure },
|
||||
{ "activation-cancel", testActivationCancel },
|
||||
{ "activation-request-cancel", testActivationRequestCancel },
|
||||
{ "deactivation-completes", testDeactivationCompletes },
|
||||
{ "activation-mismatch", testMismatchDuringActivation },
|
||||
{ "pre-requested-activation", testPreRequestedActivation },
|
||||
};
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user