[client] transport: track video component availability

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

View File

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