[client] video: switch transport sources

Track primary and fallback video with source-specific generations, and
publish a source only after its first frame or software surface is
ready.

Route SPICE surfaces and cursors through the generic render queue. Keep
the current image across handoffs, and switch input only after the new
source has been presented.

Move fallback activation to its worker so display callbacks cannot
block or deadlock the render path.
This commit is contained in:
Geoffrey McRae
2026-08-11 18:09:10 +10:00
parent 053d4e9d9b
commit 1a6f85eae2
10 changed files with 1728 additions and 392 deletions

View File

@@ -227,8 +227,18 @@ bool app_guestIsOther(void);
void app_stopVideo(bool stop); void app_stopVideo(bool stop);
/** /**
* Enable/disable the spice display * Select the primary or fallback video source
*/ */
bool app_useSpiceDisplay(bool enable); typedef enum LG_VideoSource
{
LG_VIDEO_SOURCE_NONE,
LG_VIDEO_SOURCE_PRIMARY,
LG_VIDEO_SOURCE_FALLBACK,
LG_VIDEO_SOURCE_COUNT,
}
LG_VideoSource;
bool app_useVideoSource(LG_VideoSource source);
void app_refreshVideoSource(void);
#endif #endif

View File

@@ -306,6 +306,7 @@ void app_handleEnterEvent(bool entered)
if (!g_params.alwaysShowCursor) if (!g_params.alwaysShowCursor)
g_cursor.draw = false; g_cursor.draw = false;
g_cursor.redraw = true; g_cursor.redraw = true;
app_invalidateWindow(false);
} }
} }
@@ -1139,81 +1140,166 @@ void app_stopVideo(bool stop)
if (stop) if (stop)
{ {
core_stopCursorThread(); if (g_state.videoOps->type == LG_VIDEO_TYPE_FRAME)
core_stopFrameThread(); {
core_stopCursorThread();
core_stopFrameThread();
}
else
g_state.videoOps->swSurface->setActive(
g_state.transport.handle, false);
} }
else else
{ {
core_startCursorThread(); if (g_state.videoOps->type == LG_VIDEO_TYPE_FRAME)
core_startFrameThread(); {
core_startCursorThread();
core_startFrameThread();
}
else
{
g_state.videoOps->swSurface->setActive(
g_state.transport.handle, true);
app_useVideoSource(LG_VIDEO_SOURCE_PRIMARY);
}
} }
} }
bool app_useSpiceDisplay(bool enable) static RenderQueueSource renderSource(LG_VideoSource source)
{ {
if (!g_params.useSpice || !g_state.fallback) switch(source)
return false;
atomic_store_explicit(&g_state.fallbackDisplayRequested, enable,
memory_order_release);
// if the fallback is not yet ready, retain the requested state
if (!lgTransportFallback_ready(g_state.fallback))
return false;
bool active = atomic_load_explicit(&g_state.fallbackDisplayActive,
memory_order_acquire);
if (active == enable)
return active;
// do not allow stopping of the host app if not connected
if (!enable && !atomic_load_explicit(
&g_state.lgHostConnected, memory_order_acquire))
{ {
atomic_store_explicit(&g_state.fallbackDisplayRequested, active, case LG_VIDEO_SOURCE_NONE:
memory_order_release); return RENDER_QUEUE_SOURCE_NONE;
return false;
case LG_VIDEO_SOURCE_PRIMARY:
return RENDER_QUEUE_SOURCE_PRIMARY;
case LG_VIDEO_SOURCE_FALLBACK:
return RENDER_QUEUE_SOURCE_FALLBACK;
case LG_VIDEO_SOURCE_COUNT:
break;
} }
bool expected = false; return RENDER_QUEUE_SOURCE_NONE;
if (!atomic_compare_exchange_strong_explicit( }
&g_state.fallbackDisplayTransition, &expected, true,
memory_order_acquire, memory_order_relaxed)) static bool useVideoSourceLocked(LG_VideoSource source,
return atomic_load_explicit(&g_state.fallbackDisplayActive, LG_VideoSource previousRequested, bool inputAvailable)
memory_order_acquire); {
const RenderQueueSource queueSource = renderSource(source);
active = atomic_load_explicit(&g_state.fallbackDisplayActive, if (source == LG_VIDEO_SOURCE_NONE)
memory_order_relaxed); return renderQueue_sourceTransition(
if (active == enable) queueSource, 0, false, NULL) != 0;
goto done;
struct VideoSourceState * state = &g_state.videoSource[source];
if (!lgTransportFallback_setVideoActive(g_state.fallback, enable)) if (previousRequested != source)
goto fail; {
atomic_store_explicit(
renderQueue_swSurfaceShow(enable); &state->transitionSerial, 0, memory_order_release);
atomic_store_explicit(
active = enable; &state->transitionPending, false, memory_order_release);
atomic_store_explicit(&g_state.fallbackDisplayActive, active, }
memory_order_release);
lgInput_useTransport(!active); const uint64_t generation = atomic_load_explicit(
overlayStatus_set(LG_USER_STATUS_SPICE, enable); &state->generation, memory_order_acquire);
if (!generation || !atomic_load_explicit(
done: &state->ready, memory_order_acquire))
atomic_store_explicit(&g_state.fallbackDisplayTransition, false, return false;
memory_order_release);
const LG_VideoSource applied = atomic_load_explicit(
enable = atomic_load_explicit(&g_state.fallbackDisplayRequested, &g_state.videoSourceApplied, memory_order_acquire);
memory_order_acquire); const uint64_t appliedGeneration = atomic_load_explicit(
if (enable != active) &g_state.videoSourceAppliedGeneration, memory_order_acquire);
return app_useSpiceDisplay(enable); const int width = atomic_load_explicit(
&state->width, memory_order_relaxed);
return active; const int height = atomic_load_explicit(
&state->height, memory_order_relaxed);
fail: const LG_RendererRotate rotate = atomic_load_explicit(
DEBUG_ERROR("Failed to %s the SPICE display", &state->rotate, memory_order_relaxed);
enable ? "enable" : "disable"); if (width <= 0 || height <= 0)
lgTransportFallback_setVideoActive(g_state.fallback, active); return false;
atomic_store_explicit(&g_state.fallbackDisplayRequested, active,
memory_order_release); const LG_RendererRotate appliedRotate = atomic_load_explicit(
goto done; &state->appliedRotate, memory_order_acquire);
const bool geometryApplied =
atomic_load_explicit(&state->appliedWidth, memory_order_relaxed) ==
(unsigned int)width &&
atomic_load_explicit(&state->appliedHeight, memory_order_relaxed) ==
(unsigned int)height &&
appliedRotate == rotate;
if (applied == source && appliedGeneration == generation &&
previousRequested == source && geometryApplied)
return true;
if (previousRequested == source && atomic_load_explicit(
&state->transitionPending, memory_order_acquire))
return true;
if (state->cursorStateValid)
renderQueue_sourceCursorState(queueSource, generation,
state->cursorVisible &&
(source == LG_VIDEO_SOURCE_FALLBACK ||
g_cursor.draw || !inputAvailable),
state->cursorX, state->cursorY,
state->cursorHX, state->cursorHY);
const bool configure = state->swSurface && state->configurePending;
atomic_store_explicit(
&state->transitionPending, true, memory_order_release);
const uint64_t serial = configure ?
renderQueue_sourceSwSurfaceConfigureTransition(
queueSource, generation, width, height,
&state->transitionSerial) :
renderQueue_sourceTransition(
queueSource, generation, state->swSurface,
&state->transitionSerial);
if (!serial)
{
atomic_store_explicit(
&state->transitionPending, false, memory_order_release);
return false;
}
return true;
}
bool app_useVideoSource(LG_VideoSource source)
{
if (source < LG_VIDEO_SOURCE_NONE || source >= LG_VIDEO_SOURCE_COUNT)
return false;
if (source == LG_VIDEO_SOURCE_PRIMARY && !atomic_load_explicit(
&g_state.lgHostConnected, memory_order_acquire))
return false;
if (source == LG_VIDEO_SOURCE_FALLBACK && !g_state.fallback)
return false;
const bool inputAvailable = lgInput_available();
LG_LOCK(g_state.videoSourceLock);
const LG_VideoSource previousRequested = atomic_load_explicit(
&g_state.videoSourceRequested, memory_order_acquire);
atomic_store_explicit(
&g_state.videoSourceRequested, source, memory_order_release);
if (source == LG_VIDEO_SOURCE_FALLBACK)
lgTransportFallback_requestVideoActive(g_state.fallback, true);
const bool result =
useVideoSourceLocked(source, previousRequested, inputAvailable);
LG_UNLOCK(g_state.videoSourceLock);
return result;
}
void app_refreshVideoSource(void)
{
const bool inputAvailable = lgInput_available();
LG_LOCK(g_state.videoSourceLock);
const LG_VideoSource source = atomic_load_explicit(
&g_state.videoSourceRequested, memory_order_acquire);
if (source != LG_VIDEO_SOURCE_NONE &&
(source != LG_VIDEO_SOURCE_PRIMARY || atomic_load_explicit(
&g_state.lgHostConnected, memory_order_acquire)) &&
(source != LG_VIDEO_SOURCE_FALLBACK || g_state.fallback))
useVideoSourceLocked(source, source, inputAvailable);
LG_UNLOCK(g_state.videoSourceLock);
} }

View File

@@ -187,6 +187,7 @@ static void applyView(bool active, bool force)
(g_params.alwaysShowCursor || g_params.captureInputOnly) (g_params.alwaysShowCursor || g_params.captureInputOnly)
? true : g_cursor.inView; ? true : g_cursor.inView;
g_cursor.redraw = true; g_cursor.redraw = true;
app_invalidateWindow(false);
g_cursor.motionValid = false; g_cursor.motionValid = false;
g_cursor.autoCaptureActive = active && g_params.autoCapture && g_cursor.autoCaptureActive = active && g_params.autoCapture &&
!g_cursor.grab && !g_params.captureInputOnly; !g_cursor.grab && !g_params.captureInputOnly;
@@ -984,6 +985,7 @@ void core_handleMouseNormal(double ex, double ey)
g_cursor.realign = false; g_cursor.realign = false;
g_cursor.realigning = false; g_cursor.realigning = false;
g_cursor.redraw = true; g_cursor.redraw = true;
app_invalidateWindow(false);
if (!g_cursor.inWindow) if (!g_cursor.inWindow)
return; return;

File diff suppressed because it is too large Load Diff

View File

@@ -57,6 +57,30 @@ enum AudioResampler {
AUDIO_RESAMPLER_BACKEND AUDIO_RESAMPLER_BACKEND
}; };
struct VideoSourceState
{
atomic_uint_least64_t generation;
atomic_uint_least64_t transitionSerial;
atomic_bool transitionPending;
atomic_uint_least32_t width;
atomic_uint_least32_t height;
atomic_int rotate;
atomic_uint_least32_t appliedWidth;
atomic_uint_least32_t appliedHeight;
atomic_int appliedRotate;
atomic_bool ready;
bool swSurface;
bool configurePending;
bool cursorStateValid;
bool cursorVisible;
int cursorX;
int cursorY;
int cursorHX;
int cursorHY;
uint32_t cursorWhiteLevel;
};
struct AppState struct AppState
{ {
ImGuiIO * io; ImGuiIO * io;
@@ -79,11 +103,15 @@ struct AppState
bool dsInitialized; bool dsInitialized;
bool jitRender; bool jitRender;
atomic_bool fallbackDisplayRequested; struct VideoSourceState videoSource[LG_VIDEO_SOURCE_COUNT];
atomic_bool fallbackDisplayActive; atomic_int videoSourceRequested;
atomic_bool fallbackDisplayTransition; atomic_int videoSourceApplied;
atomic_uint_least64_t videoSourceAppliedGeneration;
LG_Lock videoSourceLock;
LG_Lock videoSplashLock;
bool videoGeometryDirty;
atomic_bool fallbackUUIDMismatch; atomic_bool fallbackUUIDMismatch;
bool fallbackSurfaceValid;
uint8_t guestUUID[16]; uint8_t guestUUID[16];
bool guestUUIDValid; bool guestUUIDValid;
@@ -296,7 +324,7 @@ struct CursorState
bool realigning; bool realigning;
/* true if the cursor needs re-drawing/updating */ /* true if the cursor needs re-drawing/updating */
bool redraw; atomic_bool redraw;
/* true if the cursor movements should be scaled */ /* true if the cursor movements should be scaled */
bool useScale; bool useScale;

View File

@@ -20,20 +20,167 @@
#include "render_queue.h" #include "render_queue.h"
#include <stdatomic.h>
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include "common/debug.h" #include "common/debug.h"
#include "common/ll.h" #include "common/ll.h"
#include "main.h" #include "main.h"
#include "overlays.h"
struct ll * l_renderQueue = NULL; struct ll * l_renderQueue = NULL;
static bool l_showSwSurface; static bool l_showSwSurface;
static bool l_surfaceFormatValid; static bool l_surfaceFormatValid;
static bool l_rendererSupportsNativeHDR; static bool l_rendererSupportsNativeHDR;
static LG_RendererFormat l_surfaceFormat; static LG_RendererFormat l_surfaceFormat;
static _Atomic(uint64_t) l_sourceGeneration[RENDER_QUEUE_SOURCE_COUNT];
static _Atomic(uint64_t) l_transitionSerial;
static LG_Lock l_sourceLock;
static LG_Lock l_transitionLock;
static RenderQueueSource l_appliedSource;
static uint64_t l_appliedGeneration;
static bool l_appliedSwSurface;
static RenderQueueSourcePrepareFn l_sourcePrepareFn;
static RenderQueueSourceAppliedFn l_sourceAppliedFn;
static void * l_sourceCallbackOpaque;
typedef struct RenderQueueTransition
{
RenderQueueSource source;
uint64_t generation;
uint64_t serial;
bool swSurface;
bool valid;
}
RenderQueueTransition;
static RenderQueueTransition l_pendingTransition;
typedef struct RenderQueueCursor
{
uint64_t stateGeneration;
bool stateValid;
bool visible;
int x;
int y;
int hx;
int hy;
uint64_t imageGeneration;
bool imageValid;
LG_RendererCursor type;
int width;
int height;
int pitch;
uint8_t * data;
uint64_t colorGeneration;
bool colorValid;
LGColorTransform colorTransform;
uint64_t whiteGeneration;
bool whiteValid;
uint32_t sdrWhiteLevel;
}
RenderQueueCursor;
typedef struct RenderQueueFormat
{
uint64_t generation;
bool valid;
LG_RendererFormat format;
bool rendererSupportsNativeHDR;
}
RenderQueueFormat;
static RenderQueueCursor l_cursor[RENDER_QUEUE_SOURCE_COUNT];
static RenderQueueFormat l_format[RENDER_QUEUE_SOURCE_COUNT];
static const LGColorTransform l_identityColorTransform;
static bool sourceValid(RenderQueueSource source)
{
return source > RENDER_QUEUE_SOURCE_NONE &&
source < RENDER_QUEUE_SOURCE_COUNT;
}
static bool generationValid(RenderQueueSource source, uint64_t generation)
{
return sourceValid(source) && generation &&
atomic_load_explicit(&l_sourceGeneration[source],
memory_order_acquire) == generation;
}
static bool transitionCommand(const RenderCommand * cmd)
{
return cmd->op == SOURCE_OP_TRANSITION ||
cmd->op == SW_SURFACE_OP_CONFIGURE_TRANSITION;
}
static bool commandValid(const RenderCommand * cmd)
{
if (transitionCommand(cmd) &&
atomic_load_explicit(&l_transitionSerial, memory_order_acquire) !=
cmd->transitionSerial)
return false;
if (cmd->source == RENDER_QUEUE_SOURCE_NONE)
return cmd->op == SOURCE_OP_TRANSITION;
return generationValid(cmd->source, cmd->generation);
}
static void freeCommand(RenderCommand * cmd)
{
switch (cmd->op)
{
case SW_SURFACE_OP_DRAW_BITMAP:
free(cmd->swSurfaceDrawBitmap.data);
break;
case CURSOR_OP_IMAGE:
free(cmd->cursorImage.data);
break;
case CURSOR_OP_COLOR_TRANSFORM:
free(cmd->cursorColorTransform.data);
break;
default:
break;
}
free(cmd);
}
static void setCommandSource(RenderCommand * cmd, RenderQueueSource source,
uint64_t generation)
{
cmd->source = source;
cmd->generation = generation;
}
static bool copyCursorImage(RenderCommand * cmd, const void * data)
{
if (!data || cmd->cursorImage.height <= 0 || cmd->cursorImage.pitch <= 0)
return false;
if ((size_t)cmd->cursorImage.height >
SIZE_MAX / (size_t)cmd->cursorImage.pitch)
return false;
const size_t size =
(size_t)cmd->cursorImage.height * (size_t)cmd->cursorImage.pitch;
cmd->cursorImage.data = malloc(size);
if (!cmd->cursorImage.data)
return false;
memcpy(cmd->cursorImage.data, data, size);
return true;
}
static void updateSurfaceFormat(void) static void updateSurfaceFormat(void)
{ {
if (!g_state.ds->setHDRImageDesc) if (!g_state.ds->setHDRImageDesc)
@@ -56,7 +203,11 @@ static void updateSurfaceFormat(void)
} }
if (!l_surfaceFormatValid) if (!l_surfaceFormatValid)
{
g_state.ds->setHDRImageDesc(&format);
atomic_store(&g_state.hdrDescFailed, false);
return; return;
}
if (format.hdr && !l_rendererSupportsNativeHDR) if (format.hdr && !l_rendererSupportsNativeHDR)
{ {
@@ -83,7 +234,22 @@ void renderQueue_init(void)
l_showSwSurface = false; l_showSwSurface = false;
l_surfaceFormatValid = false; l_surfaceFormatValid = false;
l_rendererSupportsNativeHDR = false; l_rendererSupportsNativeHDR = false;
l_appliedSource = RENDER_QUEUE_SOURCE_NONE;
l_appliedGeneration = 0;
l_appliedSwSurface = false;
l_sourcePrepareFn = NULL;
l_sourceAppliedFn = NULL;
l_sourceCallbackOpaque = NULL;
l_pendingTransition = (RenderQueueTransition) {};
memset(&l_surfaceFormat, 0, sizeof(l_surfaceFormat)); memset(&l_surfaceFormat, 0, sizeof(l_surfaceFormat));
memset(l_cursor, 0, sizeof(l_cursor));
memset(l_format, 0, sizeof(l_format));
for (int i = 0; i < RENDER_QUEUE_SOURCE_COUNT; ++i)
atomic_store_explicit(&l_sourceGeneration[i], 0, memory_order_relaxed);
atomic_store_explicit(&l_transitionSerial, 0, memory_order_relaxed);
LG_LOCK_INIT(l_sourceLock);
LG_LOCK_INIT(l_transitionLock);
} }
void renderQueue_free(void) void renderQueue_free(void)
@@ -92,47 +258,194 @@ void renderQueue_free(void)
return; return;
renderQueue_clear(); renderQueue_clear();
for (int i = 0; i < RENDER_QUEUE_SOURCE_COUNT; ++i)
{
free(l_cursor[i].data);
l_cursor[i].data = NULL;
}
ll_free(l_renderQueue); ll_free(l_renderQueue);
l_renderQueue = NULL;
LG_LOCK_FREE(l_sourceLock);
LG_LOCK_FREE(l_transitionLock);
} }
void renderQueue_clear(void) void renderQueue_clear(void)
{ {
RenderCommand * cmd; RenderCommand * cmd;
while(ll_shift(l_renderQueue, (void **)&cmd)) while(ll_shift(l_renderQueue, (void **)&cmd))
{ freeCommand(cmd);
if (cmd->op == SW_SURFACE_OP_DRAW_BITMAP)
free(cmd->swSurfaceDrawBitmap.data);
free(cmd);
}
} }
void renderQueue_swSurfaceConfigure(int width, int height) void renderQueue_setSourceFns(RenderQueueSourcePrepareFn prepare,
RenderQueueSourceAppliedFn applied, void * opaque)
{ {
RenderCommand * cmd = malloc(sizeof(*cmd)); l_sourcePrepareFn = prepare;
cmd->op = SW_SURFACE_OP_CONFIGURE; l_sourceAppliedFn = applied;
cmd->swSurfaceConfigure.width = width; l_sourceCallbackOpaque = opaque;
cmd->swSurfaceConfigure.height = height;
ll_push(l_renderQueue, cmd);
app_invalidateWindow(true);
} }
void renderQueue_swSurfaceDrawFill(int x, int y, int width, int height, uint64_t renderQueue_sourceBegin(RenderQueueSource source)
{
if (!sourceValid(source))
return 0;
LG_LOCK(l_sourceLock);
const uint64_t previous = atomic_load_explicit(
&l_sourceGeneration[source], memory_order_relaxed);
const uint64_t generation = previous + 1;
atomic_store_explicit(
&l_sourceGeneration[source], generation, memory_order_release);
RenderQueueCursor * cursor = &l_cursor[source];
if (cursor->stateValid && cursor->stateGeneration == previous)
cursor->stateGeneration = generation;
if (cursor->imageValid && cursor->imageGeneration == previous)
cursor->imageGeneration = generation;
if (cursor->colorValid && cursor->colorGeneration == previous)
cursor->colorGeneration = generation;
if (cursor->whiteValid && cursor->whiteGeneration == previous)
cursor->whiteGeneration = generation;
LG_UNLOCK(l_sourceLock);
return generation;
}
void renderQueue_sourceInvalidate(RenderQueueSource source,
uint64_t generation)
{
if (!sourceValid(source) || !generation)
return;
LG_LOCK(l_sourceLock);
const uint64_t invalidGeneration = generation + 1;
if (atomic_compare_exchange_strong_explicit(&l_sourceGeneration[source],
&generation, invalidGeneration, memory_order_acq_rel,
memory_order_relaxed))
{
RenderQueueCursor * cursor = &l_cursor[source];
if (cursor->stateValid && cursor->stateGeneration == generation)
cursor->stateGeneration = invalidGeneration;
if (cursor->imageValid && cursor->imageGeneration == generation)
cursor->imageGeneration = invalidGeneration;
if (cursor->colorValid && cursor->colorGeneration == generation)
cursor->colorGeneration = invalidGeneration;
if (cursor->whiteValid && cursor->whiteGeneration == generation)
cursor->whiteGeneration = invalidGeneration;
}
LG_UNLOCK(l_sourceLock);
}
void renderQueue_sourceClearCursor(RenderQueueSource source)
{
if (!sourceValid(source))
return;
LG_LOCK(l_sourceLock);
free(l_cursor[source].data);
memset(&l_cursor[source], 0, sizeof(l_cursor[source]));
LG_UNLOCK(l_sourceLock);
}
static uint64_t enqueueTransition(RenderCommand * cmd,
atomic_uint_least64_t * publishedSerial)
{
LG_LOCK(l_transitionLock);
const uint64_t serial = atomic_load_explicit(
&l_transitionSerial, memory_order_relaxed) + 1;
cmd->transitionSerial = serial;
if (!ll_push(l_renderQueue, cmd))
{
LG_UNLOCK(l_transitionLock);
free(cmd);
return 0;
}
atomic_store_explicit(
&l_transitionSerial, serial, memory_order_release);
if (publishedSerial)
atomic_store_explicit(publishedSerial, serial, memory_order_release);
LG_UNLOCK(l_transitionLock);
app_invalidateWindow(true);
return serial;
}
uint64_t renderQueue_sourceTransition(RenderQueueSource source,
uint64_t generation, bool swSurface,
atomic_uint_least64_t * publishedSerial)
{
if (source != RENDER_QUEUE_SOURCE_NONE &&
!generationValid(source, generation))
return 0;
if (source == RENDER_QUEUE_SOURCE_NONE)
{
generation = 0;
swSurface = false;
}
RenderCommand * cmd = malloc(sizeof(*cmd));
if (!cmd)
return 0;
setCommandSource(cmd, source, generation);
cmd->op = SOURCE_OP_TRANSITION;
cmd->sourceTransition.swSurface = swSurface;
return enqueueTransition(cmd, publishedSerial);
}
uint64_t renderQueue_sourceSwSurfaceConfigureTransition(
RenderQueueSource source, uint64_t generation, int width, int height,
atomic_uint_least64_t * publishedSerial)
{
if (!generationValid(source, generation))
return 0;
RenderCommand * cmd = malloc(sizeof(*cmd));
if (!cmd)
return 0;
setCommandSource(cmd, source, generation);
cmd->op =
SW_SURFACE_OP_CONFIGURE_TRANSITION;
cmd->swSurfaceConfigureTransition.width = width;
cmd->swSurfaceConfigureTransition.height = height;
return enqueueTransition(cmd, publishedSerial);
}
void renderQueue_sourceSwSurfaceDrawFill(RenderQueueSource source,
uint64_t generation, int x, int y, int width, int height,
uint32_t color) uint32_t color)
{ {
if (!generationValid(source, generation))
return;
RenderCommand * cmd = malloc(sizeof(*cmd)); RenderCommand * cmd = malloc(sizeof(*cmd));
if (!cmd)
return;
setCommandSource(cmd, source, generation);
cmd->op = SW_SURFACE_OP_DRAW_FILL; cmd->op = SW_SURFACE_OP_DRAW_FILL;
cmd->swSurfaceDrawFill.x = x; cmd->swSurfaceDrawFill.x = x;
cmd->swSurfaceDrawFill.y = y; cmd->swSurfaceDrawFill.y = y;
cmd->swSurfaceDrawFill.width = width; cmd->swSurfaceDrawFill.width = width;
cmd->swSurfaceDrawFill.height = height; cmd->swSurfaceDrawFill.height = height;
cmd->swSurfaceDrawFill.color = color; cmd->swSurfaceDrawFill.color = color;
ll_push(l_renderQueue, cmd); if (!ll_push(l_renderQueue, cmd))
{
free(cmd);
return;
}
app_invalidateWindow(true); app_invalidateWindow(true);
} }
void renderQueue_swSurfaceDrawBitmap(int x, int y, int width, int height, void renderQueue_sourceSwSurfaceDrawBitmap(RenderQueueSource source,
int stride, const void * data, bool topDown) uint64_t generation, int x, int y, int width, int height, int stride,
const void * data, bool topDown)
{ {
if (!generationValid(source, generation))
return;
if (width <= 0 || height <= 0 || stride <= 0) if (width <= 0 || height <= 0 || stride <= 0)
{ {
if (width < 0 || height < 0 || stride < 0) if (width < 0 || height < 0 || stride < 0)
@@ -175,6 +488,7 @@ void renderQueue_swSurfaceDrawBitmap(int x, int y, int width, int height,
memcpy(copy, data, size); memcpy(copy, data, size);
setCommandSource(cmd, source, generation);
cmd->op = SW_SURFACE_OP_DRAW_BITMAP; cmd->op = SW_SURFACE_OP_DRAW_BITMAP;
cmd->swSurfaceDrawBitmap.x = x; cmd->swSurfaceDrawBitmap.x = x;
cmd->swSurfaceDrawBitmap.y = y; cmd->swSurfaceDrawBitmap.y = y;
@@ -186,7 +500,31 @@ void renderQueue_swSurfaceDrawBitmap(int x, int y, int width, int height,
if (!ll_push(l_renderQueue, cmd)) if (!ll_push(l_renderQueue, cmd))
{ {
free(copy); freeCommand(cmd);
return;
}
app_invalidateWindow(true);
}
void renderQueue_sourceSurfaceFormat(RenderQueueSource source,
uint64_t generation, const LG_RendererFormat format,
bool rendererSupportsNativeHDR)
{
if (!generationValid(source, generation))
return;
RenderCommand * cmd = malloc(sizeof(*cmd));
if (!cmd)
return;
setCommandSource(cmd, source, generation);
cmd->op = SURFACE_OP_FORMAT;
cmd->surfaceFormat.format = format;
cmd->surfaceFormat.rendererSupportsNativeHDR =
rendererSupportsNativeHDR;
if (!ll_push(l_renderQueue, cmd))
{
free(cmd); free(cmd);
return; return;
} }
@@ -194,50 +532,161 @@ void renderQueue_swSurfaceDrawBitmap(int x, int y, int width, int height,
app_invalidateWindow(true); app_invalidateWindow(true);
} }
void renderQueue_swSurfaceShow(bool show) void renderQueue_sourceCursorState(RenderQueueSource source,
uint64_t generation, bool visible, int x, int y, int hx, int hy)
{ {
RenderCommand * cmd = malloc(sizeof(*cmd)); if (!generationValid(source, generation))
cmd->op = SW_SURFACE_OP_SHOW; return;
cmd->swSurfaceShow.show = show;
ll_push(l_renderQueue, cmd);
app_invalidateWindow(true);
}
void renderQueue_surfaceFormat(const LG_RendererFormat format,
bool rendererSupportsNativeHDR)
{
RenderCommand * cmd = malloc(sizeof(*cmd)); RenderCommand * cmd = malloc(sizeof(*cmd));
cmd->op = SURFACE_OP_FORMAT; if (!cmd)
cmd->surfaceFormat.format = format; return;
cmd->surfaceFormat.rendererSupportsNativeHDR =
rendererSupportsNativeHDR;
ll_push(l_renderQueue, cmd);
app_invalidateWindow(true);
}
void renderQueue_cursorState(bool visible, int x, int y, int hx, int hy) setCommandSource(cmd, source, generation);
{
RenderCommand * cmd = malloc(sizeof(*cmd));
cmd->op = CURSOR_OP_STATE; cmd->op = CURSOR_OP_STATE;
cmd->cursorState.visible = visible; cmd->cursorState.visible = visible;
cmd->cursorState.x = x; cmd->cursorState.x = x;
cmd->cursorState.y = y; cmd->cursorState.y = y;
cmd->cursorState.hx = hx; cmd->cursorState.hx = hx;
cmd->cursorState.hy = hy; cmd->cursorState.hy = hy;
ll_push(l_renderQueue, cmd); if (!ll_push(l_renderQueue, cmd))
free(cmd);
} }
void renderQueue_cursorImage(LG_RendererCursor type, void renderQueue_sourceCursorImage(RenderQueueSource source,
int width, int height, int pitch, uint8_t * data) uint64_t generation, LG_RendererCursor type,
int width, int height, int pitch, const void * data)
{ {
RenderCommand * cmd = malloc(sizeof(*cmd)); if (!generationValid(source, generation))
return;
RenderCommand * cmd = malloc(sizeof(*cmd));
if (!cmd)
return;
setCommandSource(cmd, source, generation);
cmd->op = CURSOR_OP_IMAGE; cmd->op = CURSOR_OP_IMAGE;
cmd->cursorImage.type = type; cmd->cursorImage.type = type;
cmd->cursorImage.width = width; cmd->cursorImage.width = width;
cmd->cursorImage.height = height; cmd->cursorImage.height = height;
cmd->cursorImage.pitch = pitch; cmd->cursorImage.pitch = pitch;
cmd->cursorImage.data = data; cmd->cursorImage.data = NULL;
ll_push(l_renderQueue, cmd); if (!copyCursorImage(cmd, data) || !ll_push(l_renderQueue, cmd))
freeCommand(cmd);
}
void renderQueue_sourceCursorColorTransform(RenderQueueSource source,
uint64_t generation, const LGColorTransform * transform)
{
if (!generationValid(source, generation) || !transform)
return;
RenderCommand * cmd = malloc(sizeof(*cmd));
if (!cmd)
return;
LGColorTransform * copy = malloc(sizeof(*copy));
if (!copy)
{
free(cmd);
return;
}
*copy = *transform;
setCommandSource(cmd, source, generation);
cmd->op = CURSOR_OP_COLOR_TRANSFORM;
cmd->cursorColorTransform.data = copy;
if (!ll_push(l_renderQueue, cmd))
freeCommand(cmd);
}
void renderQueue_sourceCursorWhiteLevel(RenderQueueSource source,
uint64_t generation, uint32_t sdrWhiteLevel)
{
if (!generationValid(source, generation))
return;
RenderCommand * cmd = malloc(sizeof(*cmd));
if (!cmd)
return;
setCommandSource(cmd, source, generation);
cmd->op = CURSOR_OP_WHITE_LEVEL;
cmd->cursorWhiteLevel.value = sdrWhiteLevel;
if (!ll_push(l_renderQueue, cmd))
free(cmd);
}
static void applyCursor(RenderQueueSource source, uint64_t generation)
{
RenderQueueCursor * cursor = &l_cursor[source];
if (cursor->imageValid && cursor->imageGeneration == generation)
RENDERER(onMouseShape, cursor->type, cursor->width, cursor->height,
cursor->pitch, cursor->data);
if (cursor->imageValid && cursor->imageGeneration == generation &&
cursor->stateValid && cursor->stateGeneration == generation)
RENDERER(onMouseEvent, cursor->visible, cursor->x, cursor->y,
cursor->hx, cursor->hy);
else
RENDERER(onMouseEvent, false, 0, 0, 0, 0);
if (g_state.lgr->ops.onMouseColorTransform)
g_state.lgr->ops.onMouseColorTransform(g_state.lgr,
cursor->colorValid && cursor->colorGeneration == generation ?
&cursor->colorTransform : &l_identityColorTransform);
if (g_state.lgr->ops.onMouseWhiteLevel)
g_state.lgr->ops.onMouseWhiteLevel(g_state.lgr,
cursor->whiteValid && cursor->whiteGeneration == generation ?
cursor->sdrWhiteLevel : LG_SDR_WHITE_LEVEL_DEFAULT);
}
static void applySourceTransition(const RenderCommand * cmd,
bool showSwSurface)
{
if (l_showSwSurface != showSwSurface)
{
RENDERER(swSurfaceShow, showSwSurface);
l_showSwSurface = showSwSurface;
}
l_surfaceFormatValid = false;
l_rendererSupportsNativeHDR = false;
if (sourceValid(cmd->source))
{
const RenderQueueFormat * format = &l_format[cmd->source];
if (format->valid && format->generation == cmd->generation)
{
l_surfaceFormat = format->format;
l_surfaceFormatValid = true;
l_rendererSupportsNativeHDR =
format->rendererSupportsNativeHDR;
}
}
updateSurfaceFormat();
l_appliedSource = cmd->source;
l_appliedGeneration = cmd->generation;
l_appliedSwSurface = showSwSurface;
if (sourceValid(cmd->source))
applyCursor(cmd->source, cmd->generation);
else
RENDERER(onMouseEvent, false, 0, 0, 0, 0);
l_pendingTransition.source = cmd->source;
l_pendingTransition.generation = cmd->generation;
l_pendingTransition.serial = cmd->transitionSerial;
l_pendingTransition.swSurface = l_appliedSwSurface;
l_pendingTransition.valid = true;
}
static bool prepareSourceTransition(const RenderCommand * cmd)
{
return !l_sourcePrepareFn || l_sourcePrepareFn(l_sourceCallbackOpaque,
cmd->source, cmd->generation, cmd->transitionSerial);
} }
void renderQueue_process(void) void renderQueue_process(void)
@@ -245,12 +694,32 @@ void renderQueue_process(void)
RenderCommand * cmd; RenderCommand * cmd;
while(ll_shift(l_renderQueue, (void **)&cmd)) while(ll_shift(l_renderQueue, (void **)&cmd))
{ {
const bool transition = transitionCommand(cmd);
if (transition)
LG_LOCK(l_transitionLock);
LG_LOCK(l_sourceLock);
if (!commandValid(cmd))
{
LG_UNLOCK(l_sourceLock);
if (transition)
LG_UNLOCK(l_transitionLock);
freeCommand(cmd);
continue;
}
switch(cmd->op) switch(cmd->op)
{ {
case SW_SURFACE_OP_CONFIGURE: case SW_SURFACE_OP_CONFIGURE_TRANSITION:
if (!prepareSourceTransition(cmd))
break;
RENDERER(swSurfaceConfigure, RENDERER(swSurfaceConfigure,
cmd->swSurfaceConfigure.width, cmd->swSurfaceConfigureTransition.width,
cmd->swSurfaceConfigure.height); cmd->swSurfaceConfigureTransition.height);
RENDERER(swSurfaceDrawFill, 0, 0,
cmd->swSurfaceConfigureTransition.width,
cmd->swSurfaceConfigureTransition.height, 0);
applySourceTransition(cmd, true);
break; break;
case SW_SURFACE_OP_DRAW_FILL: case SW_SURFACE_OP_DRAW_FILL:
@@ -267,36 +736,132 @@ void renderQueue_process(void)
cmd->swSurfaceDrawBitmap.stride, cmd->swSurfaceDrawBitmap.data, cmd->swSurfaceDrawBitmap.stride, cmd->swSurfaceDrawBitmap.data,
cmd->swSurfaceDrawBitmap.topDown); cmd->swSurfaceDrawBitmap.topDown);
free(cmd->swSurfaceDrawBitmap.data); free(cmd->swSurfaceDrawBitmap.data);
break; cmd->swSurfaceDrawBitmap.data = NULL;
case SW_SURFACE_OP_SHOW:
l_showSwSurface = cmd->swSurfaceShow.show;
RENDERER(swSurfaceShow, cmd->swSurfaceShow.show);
updateSurfaceFormat();
if (cmd->swSurfaceShow.show)
overlaySplash_show(false);
break; break;
case SURFACE_OP_FORMAT: case SURFACE_OP_FORMAT:
l_surfaceFormat = cmd->surfaceFormat.format; {
l_surfaceFormatValid = true; RenderQueueFormat * format = &l_format[cmd->source];
l_rendererSupportsNativeHDR = format->generation = cmd->generation;
format->valid = true;
format->format = cmd->surfaceFormat.format;
format->rendererSupportsNativeHDR =
cmd->surfaceFormat.rendererSupportsNativeHDR; cmd->surfaceFormat.rendererSupportsNativeHDR;
updateSurfaceFormat();
if (l_appliedSource == cmd->source &&
l_appliedGeneration == cmd->generation &&
!l_appliedSwSurface)
{
l_surfaceFormat = format->format;
l_surfaceFormatValid = true;
l_rendererSupportsNativeHDR =
format->rendererSupportsNativeHDR;
updateSurfaceFormat();
}
break; break;
}
case CURSOR_OP_STATE: case CURSOR_OP_STATE:
RENDERER(onMouseEvent, cmd->cursorState.visible, cmd->cursorState.x, {
cmd->cursorState.y, cmd->cursorState.hx, cmd->cursorState.hy); RenderQueueCursor * cursor = &l_cursor[cmd->source];
cursor->stateGeneration = cmd->generation;
cursor->stateValid = true;
cursor->visible = cmd->cursorState.visible;
cursor->x = cmd->cursorState.x;
cursor->y = cmd->cursorState.y;
cursor->hx = cmd->cursorState.hx;
cursor->hy = cmd->cursorState.hy;
if (l_appliedSource == cmd->source &&
l_appliedGeneration == cmd->generation)
RENDERER(onMouseEvent,
cursor->imageValid &&
cursor->imageGeneration == cmd->generation &&
cursor->visible,
cursor->x, cursor->y, cursor->hx, cursor->hy);
break; break;
}
case CURSOR_OP_IMAGE: case CURSOR_OP_IMAGE:
RENDERER(onMouseShape, {
cmd->cursorImage.type, RenderQueueCursor * cursor = &l_cursor[cmd->source];
cmd->cursorImage.width, cmd->cursorImage.height, free(cursor->data);
cmd->cursorImage.pitch, cmd->cursorImage.data); cursor->imageGeneration = cmd->generation;
free(cmd->cursorImage.data); cursor->imageValid = true;
cursor->type = cmd->cursorImage.type;
cursor->width = cmd->cursorImage.width;
cursor->height = cmd->cursorImage.height;
cursor->pitch = cmd->cursorImage.pitch;
cursor->data = cmd->cursorImage.data;
cmd->cursorImage.data = NULL;
if (l_appliedSource == cmd->source &&
l_appliedGeneration == cmd->generation)
{
RENDERER(onMouseShape, cursor->type, cursor->width,
cursor->height, cursor->pitch, cursor->data);
if (cursor->stateValid &&
cursor->stateGeneration == cmd->generation)
RENDERER(onMouseEvent, cursor->visible, cursor->x, cursor->y,
cursor->hx, cursor->hy);
}
break;
}
case CURSOR_OP_COLOR_TRANSFORM:
{
RenderQueueCursor * cursor = &l_cursor[cmd->source];
cursor->colorGeneration = cmd->generation;
cursor->colorValid = true;
cursor->colorTransform = *cmd->cursorColorTransform.data;
if (l_appliedSource == cmd->source &&
l_appliedGeneration == cmd->generation &&
g_state.lgr->ops.onMouseColorTransform)
g_state.lgr->ops.onMouseColorTransform(g_state.lgr,
&cursor->colorTransform);
break;
}
case CURSOR_OP_WHITE_LEVEL:
{
RenderQueueCursor * cursor = &l_cursor[cmd->source];
cursor->whiteGeneration = cmd->generation;
cursor->whiteValid = true;
cursor->sdrWhiteLevel = cmd->cursorWhiteLevel.value;
if (l_appliedSource == cmd->source &&
l_appliedGeneration == cmd->generation &&
g_state.lgr->ops.onMouseWhiteLevel)
g_state.lgr->ops.onMouseWhiteLevel(g_state.lgr,
cursor->sdrWhiteLevel);
break;
}
case SOURCE_OP_TRANSITION:
if (!prepareSourceTransition(cmd))
break;
applySourceTransition(cmd,
cmd->source != RENDER_QUEUE_SOURCE_NONE &&
cmd->sourceTransition.swSurface);
break;
} }
free(cmd); LG_UNLOCK(l_sourceLock);
if (transition)
LG_UNLOCK(l_transitionLock);
freeCommand(cmd);
} }
} }
void renderQueue_presented(void)
{
if (!l_pendingTransition.valid)
return;
const RenderQueueTransition pending = l_pendingTransition;
l_pendingTransition.valid = false;
if (l_sourceAppliedFn)
l_sourceAppliedFn(l_sourceCallbackOpaque, pending.source,
pending.generation, pending.serial, pending.swSurface);
}

View File

@@ -21,17 +21,40 @@
#include "common/ll.h" #include "common/ll.h"
#include "interface/renderer.h" #include "interface/renderer.h"
#include <stdatomic.h>
typedef enum RenderQueueSource
{
RENDER_QUEUE_SOURCE_NONE,
RENDER_QUEUE_SOURCE_PRIMARY,
RENDER_QUEUE_SOURCE_FALLBACK,
RENDER_QUEUE_SOURCE_COUNT,
}
RenderQueueSource;
typedef void (*RenderQueueSourceAppliedFn)(void * opaque,
RenderQueueSource source, uint64_t generation, uint64_t serial,
bool swSurface);
typedef bool (*RenderQueueSourcePrepareFn)(void * opaque,
RenderQueueSource source, uint64_t generation, uint64_t serial);
typedef struct typedef struct
{ {
RenderQueueSource source;
uint64_t generation;
uint64_t transitionSerial;
enum enum
{ {
SW_SURFACE_OP_CONFIGURE, SW_SURFACE_OP_CONFIGURE_TRANSITION,
SW_SURFACE_OP_DRAW_FILL, SW_SURFACE_OP_DRAW_FILL,
SW_SURFACE_OP_DRAW_BITMAP, SW_SURFACE_OP_DRAW_BITMAP,
SW_SURFACE_OP_SHOW,
SURFACE_OP_FORMAT, SURFACE_OP_FORMAT,
CURSOR_OP_STATE, CURSOR_OP_STATE,
CURSOR_OP_IMAGE, CURSOR_OP_IMAGE,
CURSOR_OP_COLOR_TRANSFORM,
CURSOR_OP_WHITE_LEVEL,
SOURCE_OP_TRANSITION,
} }
op; op;
@@ -41,7 +64,7 @@ typedef struct
{ {
int width, height; int width, height;
} }
swSurfaceConfigure; swSurfaceConfigureTransition;
struct struct
{ {
@@ -61,12 +84,6 @@ typedef struct
} }
swSurfaceDrawBitmap; swSurfaceDrawBitmap;
struct
{
bool show;
}
swSurfaceShow;
struct struct
{ {
LG_RendererFormat format; LG_RendererFormat format;
@@ -93,6 +110,24 @@ typedef struct
uint8_t * data; uint8_t * data;
} }
cursorImage; cursorImage;
struct
{
LGColorTransform * data;
}
cursorColorTransform;
struct
{
uint32_t value;
}
cursorWhiteLevel;
struct
{
bool swSurface;
}
sourceTransition;
}; };
} }
RenderCommand; RenderCommand;
@@ -101,22 +136,48 @@ void renderQueue_init(void);
void renderQueue_free(void); void renderQueue_free(void);
void renderQueue_clear(void); void renderQueue_clear(void);
void renderQueue_process(void); void renderQueue_process(void);
void renderQueue_presented(void);
void renderQueue_swSurfaceConfigure(int width, int height); void renderQueue_setSourceFns(RenderQueueSourcePrepareFn prepare,
RenderQueueSourceAppliedFn applied, void * opaque);
void renderQueue_swSurfaceDrawFill(int x, int y, int width, int height, /* Starting a source lifecycle makes commands from its prior generation stale.
* After a successful render, call renderQueue_presented on the render thread
* to acknowledge the newest applied transition. */
uint64_t renderQueue_sourceBegin(RenderQueueSource source);
void renderQueue_sourceInvalidate(RenderQueueSource source,
uint64_t generation);
void renderQueue_sourceClearCursor(RenderQueueSource source);
uint64_t renderQueue_sourceTransition(RenderQueueSource source,
uint64_t generation, bool swSurface,
atomic_uint_least64_t * publishedSerial);
uint64_t renderQueue_sourceSwSurfaceConfigureTransition(
RenderQueueSource source, uint64_t generation, int width, int height,
atomic_uint_least64_t * publishedSerial);
void renderQueue_sourceSwSurfaceDrawFill(RenderQueueSource source,
uint64_t generation, int x, int y, int width, int height,
uint32_t color); uint32_t color);
void renderQueue_swSurfaceDrawBitmap(int x, int y, int width, int height, void renderQueue_sourceSwSurfaceDrawBitmap(RenderQueueSource source,
int stride, const void * data, bool topDown); uint64_t generation, int x, int y, int width, int height, int stride,
const void * data, bool topDown);
void renderQueue_swSurfaceShow(bool show); void renderQueue_sourceSurfaceFormat(RenderQueueSource source,
uint64_t generation, const LG_RendererFormat format,
void renderQueue_surfaceFormat(const LG_RendererFormat format,
bool rendererSupportsNativeHDR); bool rendererSupportsNativeHDR);
void renderQueue_cursorState(bool visible, int x, int y, int hx, int hy); void renderQueue_sourceCursorState(RenderQueueSource source,
uint64_t generation, bool visible, int x, int y, int hx, int hy);
void renderQueue_cursorImage(LG_RendererCursor type, /* Cursor payloads are copied before these calls return. */
int width, int height, int pitch, void renderQueue_sourceCursorImage(RenderQueueSource source,
uint8_t * data); uint64_t generation, LG_RendererCursor type,
int width, int height, int pitch, const void * data);
void renderQueue_sourceCursorColorTransform(RenderQueueSource source,
uint64_t generation, const LGColorTransform * transform);
void renderQueue_sourceCursorWhiteLevel(RenderQueueSource source,
uint64_t generation, uint32_t sdrWhiteLevel);

View File

@@ -49,7 +49,6 @@ struct LG_TransportFallback
LGThread * thread; LGThread * thread;
LGEvent * wakeEvent; LGEvent * wakeEvent;
LGEvent * videoIdleEvent;
LG_RWLock lock; LG_RWLock lock;
atomic_bool stop; atomic_bool stop;
@@ -66,7 +65,6 @@ struct LG_TransportFallback
bool closing; bool closing;
bool videoRequested; bool videoRequested;
bool videoActive; bool videoActive;
bool videoBusy;
bool primaryUUIDValid; bool primaryUUIDValid;
uint8_t primaryUUID[16]; uint8_t primaryUUID[16];
@@ -76,6 +74,8 @@ struct LG_TransportFallback
uint8_t mismatchFallback[16]; uint8_t mismatchFallback[16];
}; };
static bool applyVideoRequest(LG_TransportFallback * fallback);
static bool connectCancelled(void * opaque) static bool connectCancelled(void * opaque)
{ {
const LG_TransportFallback * fallback = opaque; const LG_TransportFallback * fallback = opaque;
@@ -139,26 +139,18 @@ static void unpublishProviders(LG_TransportFallback * fallback, bool live)
fallback->providersPublished = false; fallback->providersPublished = false;
} }
static void waitForVideoIdle(LG_TransportFallback * fallback) static void closeVideoAdmission(LG_TransportFallback * fallback)
{ {
for (;;) LG_LOCK_EXCLUSIVE(fallback->lock);
{ atomic_store_explicit(&fallback->ready, false, memory_order_release);
LG_LOCK_EXCLUSIVE(fallback->lock); fallback->closing = true;
atomic_store_explicit(&fallback->ready, false, memory_order_release); LG_UNLOCK_EXCLUSIVE(fallback->lock);
fallback->closing = true;
const bool busy = fallback->videoBusy;
LG_UNLOCK_EXCLUSIVE(fallback->lock);
if (!busy)
return;
lgWaitEvent(fallback->videoIdleEvent, TIMEOUT_INFINITE);
}
} }
static bool cleanupConnection(LG_TransportFallback * fallback, static bool cleanupConnection(LG_TransportFallback * fallback,
bool knownDead) bool knownDead)
{ {
waitForVideoIdle(fallback); closeVideoAdmission(fallback);
LG_LOCK_EXCLUSIVE(fallback->lock); LG_LOCK_EXCLUSIVE(fallback->lock);
const bool reportDisconnected = fallback->connectedReported; const bool reportDisconnected = fallback->connectedReported;
@@ -352,6 +344,7 @@ static bool connectFallback(LG_TransportFallback * fallback)
while (!atomic_load_explicit(&fallback->stop, memory_order_acquire)) while (!atomic_load_explicit(&fallback->stop, memory_order_acquire))
{ {
lgWaitEvent(fallback->wakeEvent, SESSION_POLL_MS); lgWaitEvent(fallback->wakeEvent, SESSION_POLL_MS);
applyVideoRequest(fallback);
LG_LOCK_EXCLUSIVE(fallback->lock); LG_LOCK_EXCLUSIVE(fallback->lock);
const bool reject = uuidMismatchLocked(fallback); const bool reject = uuidMismatchLocked(fallback);
@@ -435,10 +428,6 @@ bool lgTransportFallback_start(const char * transportName,
if (!fallback->wakeEvent) if (!fallback->wakeEvent)
goto fail; goto fail;
fallback->videoIdleEvent = lgCreateEvent(true, 0);
if (!fallback->videoIdleEvent)
goto fail;
*result = fallback; *result = fallback;
if (!lgCreateThread("transportFallback", fallbackThread, if (!lgCreateThread("transportFallback", fallbackThread,
fallback, &fallback->thread)) fallback, &fallback->thread))
@@ -448,8 +437,6 @@ bool lgTransportFallback_start(const char * transportName,
fail: fail:
*result = NULL; *result = NULL;
if (fallback->videoIdleEvent)
lgFreeEvent(fallback->videoIdleEvent);
if (fallback->wakeEvent) if (fallback->wakeEvent)
lgFreeEvent(fallback->wakeEvent); lgFreeEvent(fallback->wakeEvent);
LG_RWLOCK_FREE(fallback->lock); LG_RWLOCK_FREE(fallback->lock);
@@ -477,7 +464,6 @@ void lgTransportFallback_stop(LG_TransportFallback ** fallbackPtr)
return; return;
} }
lgFreeEvent(fallback->videoIdleEvent);
lgFreeEvent(fallback->wakeEvent); lgFreeEvent(fallback->wakeEvent);
LG_RWLOCK_FREE(fallback->lock); LG_RWLOCK_FREE(fallback->lock);
free(fallback->transportName); free(fallback->transportName);
@@ -490,20 +476,16 @@ bool lgTransportFallback_ready(const LG_TransportFallback * fallback)
&fallback->ready, memory_order_acquire); &fallback->ready, memory_order_acquire);
} }
bool lgTransportFallback_setVideoActive( static bool applyVideoRequest(LG_TransportFallback * fallback)
LG_TransportFallback * fallback, bool active)
{ {
if (!fallback)
return false;
uint64_t connectionSerial;
LG_Transport * transport;
const LG_SwSurfaceOps * surfaceOps;
for (;;) for (;;)
{ {
uint64_t connectionSerial;
LG_Transport * transport;
const LG_SwSurfaceOps * surfaceOps;
bool requested;
LG_LOCK_EXCLUSIVE(fallback->lock); LG_LOCK_EXCLUSIVE(fallback->lock);
fallback->videoRequested = active;
if (!atomic_load_explicit(&fallback->ready, memory_order_acquire) || if (!atomic_load_explicit(&fallback->ready, memory_order_acquire) ||
fallback->closing) fallback->closing)
{ {
@@ -511,38 +493,44 @@ bool lgTransportFallback_setVideoActive(
return false; return false;
} }
if (fallback->videoActive == active) requested = fallback->videoRequested;
if (fallback->videoActive == requested)
{ {
LG_UNLOCK_EXCLUSIVE(fallback->lock); LG_UNLOCK_EXCLUSIVE(fallback->lock);
return true; return true;
} }
if (!fallback->videoBusy) connectionSerial = fallback->connectionSerial;
{ transport = fallback->transport.handle;
fallback->videoBusy = true; surfaceOps = fallback->videoOps->swSurface;
connectionSerial = fallback->connectionSerial;
transport = fallback->transport.handle;
surfaceOps = fallback->videoOps->swSurface;
LG_UNLOCK_EXCLUSIVE(fallback->lock);
break;
}
LG_UNLOCK_EXCLUSIVE(fallback->lock); LG_UNLOCK_EXCLUSIVE(fallback->lock);
lgWaitEvent(fallback->videoIdleEvent, TIMEOUT_INFINITE);
}
lgResetEvent(fallback->videoIdleEvent); const bool result = surfaceOps->setActive(transport, requested);
const bool result = surfaceOps->setActive(transport, active);
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 retry = current && !fallback->closing &&
fallback->videoRequested != requested;
LG_UNLOCK_EXCLUSIVE(fallback->lock);
if (!retry)
return accepted;
}
}
void lgTransportFallback_requestVideoActive(
LG_TransportFallback * fallback, bool active)
{
if (!fallback)
return;
LG_LOCK_EXCLUSIVE(fallback->lock); LG_LOCK_EXCLUSIVE(fallback->lock);
const bool current = fallback->connectionSerial == connectionSerial; fallback->videoRequested = active;
if (current && result)
fallback->videoActive = active;
const bool accepted = current && !fallback->closing && result;
fallback->videoBusy = false;
LG_UNLOCK_EXCLUSIVE(fallback->lock); LG_UNLOCK_EXCLUSIVE(fallback->lock);
lgSignalEvent(fallback->videoIdleEvent); lgSignalEvent(fallback->wakeEvent);
return accepted;
} }
void lgTransportFallback_setPrimaryUUID( void lgTransportFallback_setPrimaryUUID(

View File

@@ -46,7 +46,7 @@ void lgTransportFallback_stop(LG_TransportFallback ** fallback);
bool lgTransportFallback_ready(const LG_TransportFallback * fallback); bool lgTransportFallback_ready(const LG_TransportFallback * fallback);
/* The requested state is retained across reconnects. */ /* The requested state is retained across reconnects. */
bool lgTransportFallback_setVideoActive( void lgTransportFallback_requestVideoActive(
LG_TransportFallback * fallback, bool active); LG_TransportFallback * fallback, bool active);
void lgTransportFallback_setPrimaryUUID( void lgTransportFallback_setPrimaryUUID(

View File

@@ -262,6 +262,11 @@ bool app_isRunning(void)
return false; return false;
} }
void app_invalidateWindow(bool full)
{
(void)full;
}
uint64_t app_mouseSeq(void) uint64_t app_mouseSeq(void)
{ {
return 0; return 0;