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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user