[client] egl: snapshot DMA frames before release

Copy imported IVSHMEM frames into renderer-owned textures before
acknowledging their LGMP messages. Hold each lease until its copy fence
signals, and protect selected snapshots until rendering completes.

Track independent delivery lanes, drain acknowledgements on the frame
thread, and recover stale queue subscriptions after host timeouts.
This commit is contained in:
Geoffrey McRae
2026-08-06 14:47:40 +10:00
parent f6c72b0bc5
commit 05f3b39b43
12 changed files with 824 additions and 227 deletions

View File

@@ -258,10 +258,18 @@ typedef struct LG_RendererOps
/* called when there is a new frame. frameToken must remain attached to the
* update if the renderer coalesces it, and must be reported by render only
* when that update is consumed. Context: frameThread */
* when that update is consumed. A non-NULL releaseFn transfers ownership to
* the renderer on success and must be called exactly once when the imported
* frame can no longer be sampled. Context: frameThread */
bool (*onFrame)(LG_Renderer * renderer, const FrameBuffer * frame, int dmaFD,
const FrameDamageRect * damage, int damageCount,
LG_RendererFrameToken frameToken);
LG_RendererFrameToken frameToken,
LG_FrameReleaseFn releaseFn,
void * releaseOpaque, uint64_t releaseHandle);
/* optional frame-thread pump for asynchronous imports while no frame is
* available from the transport */
void (*onFramePoll)(LG_Renderer * renderer);
/* called when the rederer is to startup
* Context: renderThread */

View File

@@ -145,6 +145,12 @@ typedef struct LG_TransportFrame
int dmaFD;
const FrameDamageRect * damageRects;
uint32_t damageRectsCount;
/* A transport may keep the frame payload alive asynchronously after
* onFrame returns. The callback is idempotent and releases that ownership. */
LG_FrameReleaseFn releaseFn;
void * releaseOpaque;
uint64_t releaseHandle;
}
LG_TransportFrame;

View File

@@ -72,7 +72,7 @@ struct EGL_Desktop
EGLDisplay * display;
EGL_Texture * texture;
struct DesktopShader dmaShader, shader;
struct DesktopShader shader;
EGL_DesktopRects * mesh;
GLfloat matrix[6];
@@ -194,17 +194,6 @@ bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop_, EGLDisplay * display,
return false;
}
if (useDMA)
if (!egl_initDesktopShader(
&desktop->dmaShader,
b_shader_desktop_vert , b_shader_desktop_vert_size,
b_shader_desktop_rgb_frag, b_shader_desktop_rgb_frag_size,
true))
{
DEBUG_ERROR("Failed to initialize the desktop DMA shader");
return false;
}
app_registerKeybind(KEY_N, toggleNV, desktop,
"Toggle night vision mode");
@@ -264,7 +253,6 @@ void egl_desktopFree(EGL_Desktop ** desktop)
egl_textureFree (&(*desktop)->texture );
egl_textureFree (&(*desktop)->spiceTexture );
egl_shaderFree (&(*desktop)->shader .shader);
egl_shaderFree (&(*desktop)->dmaShader.shader);
egl_desktopRectsFree(&(*desktop)->mesh );
egl_postProcessFree(&(*desktop)->pp);
@@ -394,12 +382,14 @@ bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format)
bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame,
LG_RendererFrameToken frameToken, int dmaFd,
const FrameDamageRect * damageRects, int damageRectsCount,
uint64_t * waitTimeNs)
uint64_t * waitTimeNs, LG_FrameReleaseFn releaseFn,
void * releaseOpaque, uint64_t releaseHandle)
{
if (likely(desktop->useDMA && dmaFd >= 0))
{
if (likely(egl_textureUpdateFromDMA(
desktop->texture, frame, frameToken, dmaFd, waitTimeNs)))
desktop->texture, frame, frameToken, dmaFd, waitTimeNs,
releaseFn, releaseOpaque, releaseHandle)))
{
atomic_store(&desktop->processFrame, true);
return true;
@@ -442,11 +432,26 @@ bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame,
*/
if (likely(egl_textureUpdateFromFrame(desktop->texture, frame, frameToken,
damageRects, damageRectsCount, waitTimeNs)))
{
if (releaseFn)
releaseFn(releaseOpaque, releaseHandle);
return true;
}
return false;
}
void egl_desktopRestart(EGL_Desktop * desktop)
{
egl_textureReset(desktop->texture);
atomic_store(&desktop->processFrame, false);
}
void egl_desktopPoll(EGL_Desktop * desktop)
{
egl_texturePoll(desktop->texture);
}
void egl_desktopResize(EGL_Desktop * desktop, int width, int height)
{
atomic_store(&desktop->processFrame, true);
@@ -464,21 +469,21 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
{
EGL_Texture * tex;
int width, height;
bool dma;
/* This also retires completed snapshots while the SPICE display is shown. */
egl_texturePoll(desktop->texture);
if (unlikely(desktop->useSpice))
{
tex = desktop->spiceTexture;
width = desktop->spiceWidth;
height = desktop->spiceHeight;
dma = false;
}
else
{
tex = desktop->texture;
width = desktop->width;
height = desktop->height;
dma = desktop->useDMA;
}
if (unlikely(outputWidth == 0 || outputHeight == 0))
@@ -535,7 +540,7 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
const uint64_t effectsStart = nanotime();
const bool postProcessed = egl_postProcessRun(
desktop->pp, tex, desktop->mesh,
width, height, outputWidth, outputHeight, dma,
width, height, outputWidth, outputHeight, false,
hdr && desktop->hdrPQ, (float)hdrPeak);
*effectsTime = nanotime() - effectsStart;
@@ -574,6 +579,8 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
{
if (bindStatus != EGL_TEX_STATUS_NOTREADY)
DEBUG_ERROR("Failed to bind the desktop texture");
if (!desktop->useSpice && desktop->useDMA && processFrame)
egl_textureMarkUsed(desktop->texture);
return false;
}
*renderedFrameToken = tex->frameToken;
@@ -601,9 +608,7 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
scaleAlgo = desktop->scaleAlgo;
}
const struct DesktopShader * shader =
desktop->useDMA && texture == desktop->texture ?
&desktop->dmaShader : &desktop->shader;
const struct DesktopShader * shader = &desktop->shader;
/* PQ is normalized to 10000 nits, while scRGB defines 1.0 as 80 nits.
* Convert either encoding into values relative to the selected SDR peak. */
@@ -628,6 +633,9 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
desktop->linearComposition);
egl_shaderUse(shader->shader);
egl_desktopRectsRender(desktop->mesh);
if (!desktop->useSpice && desktop->useDMA &&
(processFrame || texture == tex))
egl_textureMarkUsed(desktop->texture);
return true;
}

View File

@@ -51,7 +51,10 @@ bool egl_desktopSetup (EGL_Desktop * desktop, const LG_RendererFormat format);
bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame,
LG_RendererFrameToken frameToken, int dmaFd,
const FrameDamageRect * damageRects, int damageRectsCount,
uint64_t * waitTimeNs);
uint64_t * waitTimeNs, LG_FrameReleaseFn releaseFn,
void * releaseOpaque, uint64_t releaseHandle);
void egl_desktopRestart(EGL_Desktop * desktop);
void egl_desktopPoll(EGL_Desktop * desktop);
void egl_desktopResize(EGL_Desktop * desktop, int width, int height);
bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
unsigned int outputHeight, const float x, const float y,

View File

@@ -374,8 +374,13 @@ static void egl_onRestart(LG_Renderer * renderer)
{
struct Inst * this = UPCAST(struct Inst, renderer);
if (this->frameContext)
{
egl_stateCheckShared();
egl_desktopRestart(this->desktop);
eglDestroyContext(this->display, this->frameContext);
this->frameContext = NULL;
}
INTERLOCKED_SECTION(this->desktopDamageLock, {
this->desktopDamage[this->desktopDamageIdx].frameToken =
@@ -746,7 +751,8 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo
static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame,
int dmaFd, const FrameDamageRect * damageRects, int damageRectsCount,
LG_RendererFrameToken frameToken)
LG_RendererFrameToken frameToken, LG_FrameReleaseFn releaseFn,
void * releaseOpaque, uint64_t releaseHandle)
{
struct Inst * this = UPCAST(struct Inst, renderer);
egl_stateCheckShared();
@@ -755,7 +761,8 @@ static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame,
uint64_t waitTimeNs = 0;
if (unlikely(!egl_desktopUpdate(
this->desktop, frame, frameToken, dmaFd,
damageRects, damageRectsCount, &waitTimeNs)))
damageRects, damageRectsCount, &waitTimeNs,
releaseFn, releaseOpaque, releaseHandle)))
{
DEBUG_INFO("Failed to to update the desktop");
return false;
@@ -790,6 +797,16 @@ static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame,
return true;
}
static void egl_onFramePoll(LG_Renderer * renderer)
{
struct Inst * this = UPCAST(struct Inst, renderer);
if (!this->frameContext)
return;
egl_stateCheckShared();
egl_desktopPoll(this->desktop);
}
static void debugCallback(GLenum source, GLenum type, GLuint id,
GLenum severity, GLsizei length, const GLchar * message,
const void * userParam)
@@ -1835,6 +1852,7 @@ struct LG_RendererOps LGR_EGL =
.onMouseEvent = egl_onMouseEvent,
.onFrameFormat = egl_onFrameFormat,
.onFrame = egl_onFrame,
.onFramePoll = egl_onFramePoll,
.renderStartup = egl_renderStartup,
.render = egl_render,
.capture = egl_capture,

View File

@@ -185,7 +185,8 @@ bool egl_textureUpdateFromFrame(EGL_Texture * this,
bool egl_textureUpdateFromDMA(EGL_Texture * this,
const FrameBuffer * frame, LG_RendererFrameToken frameToken,
const int dmaFd, uint64_t * waitTimeNs)
const int dmaFd, uint64_t * waitTimeNs, LG_FrameReleaseFn releaseFn,
void * releaseOpaque, uint64_t releaseHandle)
{
const struct EGL_TexUpdate update =
{
@@ -197,7 +198,10 @@ bool egl_textureUpdateFromDMA(EGL_Texture * this,
.height = this->format.height,
.pitch = this->format.pitch,
.stride = this->format.stride,
.dmaFD = dmaFd
.dmaFD = dmaFd,
.releaseFn = releaseFn,
.releaseOpaque = releaseOpaque,
.releaseHandle = releaseHandle,
};
/* wait for completion */
@@ -208,6 +212,24 @@ bool egl_textureUpdateFromDMA(EGL_Texture * this,
return this->ops.update(this, &update);
}
void egl_textureReset(EGL_Texture * this)
{
if (this->ops.reset)
this->ops.reset(this);
}
void egl_texturePoll(EGL_Texture * this)
{
if (this->ops.poll)
this->ops.poll(this);
}
void egl_textureMarkUsed(EGL_Texture * this)
{
if (this->ops.markUsed)
this->ops.markUsed(this);
}
enum EGL_TexStatus egl_textureProcess(EGL_Texture * this,
LG_RendererFrameToken frameTokenLimit,
LG_RendererFrameToken * consumedFrameToken)

View File

@@ -44,6 +44,9 @@ typedef struct EGL_TexUpdate
EGL_TexType type;
LG_RendererFrameToken frameToken;
uint64_t * waitTimeNs;
LG_FrameReleaseFn releaseFn;
void * releaseOpaque;
uint64_t releaseHandle;
int x, y, width, height;
@@ -101,6 +104,15 @@ typedef struct EGL_TextureOps
/* bind the texture for use */
enum EGL_TexStatus (*bind)(EGL_Texture * texture, GLuint unit);
/* release queued updates and reset stream state */
void (*reset)(EGL_Texture * texture);
/* poll asynchronous work without changing the selected frame */
void (*poll)(EGL_Texture * texture);
/* record that the selected texture has been sampled */
void (*markUsed)(EGL_Texture * texture);
}
EGL_TextureOps;
@@ -135,7 +147,12 @@ bool egl_textureUpdateFromFrame(EGL_Texture * texture,
bool egl_textureUpdateFromDMA(EGL_Texture * texture,
const FrameBuffer * frame, LG_RendererFrameToken frameToken,
const int dmaFd, uint64_t * waitTimeNs);
const int dmaFd, uint64_t * waitTimeNs, LG_FrameReleaseFn releaseFn,
void * releaseOpaque, uint64_t releaseHandle);
void egl_textureReset(EGL_Texture * texture);
void egl_texturePoll(EGL_Texture * texture);
void egl_textureMarkUsed(EGL_Texture * texture);
enum EGL_TexStatus egl_textureProcess(EGL_Texture * texture,
LG_RendererFrameToken frameTokenLimit,

View File

@@ -20,6 +20,8 @@
#include "texture.h"
#include "texture_buffer.h"
#include "model.h"
#include "shader.h"
#include "state.h"
#include "util.h"
@@ -27,13 +29,30 @@
#include "egl_dynprocs.h"
#include "egldebug.h"
#include <string.h>
#include "hdr_compose.vert.h"
#include "downscale_linear.frag.h"
#define EGL_DMABUF_SNAPSHOT_COUNT (LGMP_Q_FRAME_BUFFER_LEN + 1)
struct FdImage
{
int fd;
EGLImage image;
GLsync sync;
int texIndex;
GLuint texture;
};
struct Snapshot
{
GLuint texture;
GLuint framebuffer;
GLsync copySync;
GLsync useSync;
LG_RendererFrameToken frameToken;
LG_FrameReleaseFn releaseFn;
void * releaseOpaque;
uint64_t releaseHandle;
};
typedef struct TexDMABUF
@@ -43,16 +62,28 @@ typedef struct TexDMABUF
EGLDisplay display;
struct FdImage images[EGL_TEX_BUFFER_MAX];
int lastIndex;
struct Snapshot snapshots[EGL_DMABUF_SNAPSHOT_COUNT];
int renderIndex;
EGL_Shader * copyShader;
EGL_Model * copyModel;
GLuint copySampler;
EGL_PixelFormat pixFmt;
unsigned fourcc;
unsigned width;
GLuint format;
GLenum intFormat;
GLenum dataType;
}
TexDMABUF;
struct SnapshotRelease
{
LG_FrameReleaseFn fn;
void * opaque;
uint64_t handle;
};
EGL_TextureOps EGL_TextureDMABUF;
static bool initDone = false;
@@ -61,11 +92,65 @@ static bool hasImportModifiers = true;
// internal functions
static void egl_texDMABUFFree(EGL_Texture * texture);
static void egl_texDMABUFPoll(EGL_Texture * texture);
static void snapshotRelease(struct Snapshot * snapshot,
struct SnapshotRelease * release)
{
release->fn = snapshot->releaseFn;
release->opaque = snapshot->releaseOpaque;
release->handle = snapshot->releaseHandle;
snapshot->releaseFn = NULL;
snapshot->releaseOpaque = NULL;
snapshot->releaseHandle = 0;
}
static void snapshotInvokeReleases(struct SnapshotRelease * releases,
int count)
{
for (int i = 0; i < count; ++i)
releases[i].fn(releases[i].opaque, releases[i].handle);
}
static void waitSync(GLsync * sync)
{
if (!*sync)
return;
const GLenum result = glClientWaitSync(*sync, 0, GL_TIMEOUT_IGNORED);
if (result == GL_WAIT_FAILED)
DEBUG_FATAL("Failed to wait for DMABUF snapshot fence");
glDeleteSync(*sync);
*sync = 0;
}
static void egl_texDMABUFCleanup(EGL_Texture * texture)
{
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
TexDMABUF * this = UPCAST(TexDMABUF , parent);
struct SnapshotRelease releases[EGL_DMABUF_SNAPSHOT_COUNT];
int releaseCount = 0;
LG_LOCK(parent->copyLock);
for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i)
{
struct Snapshot * snapshot = &this->snapshots[i];
waitSync(&snapshot->copySync);
waitSync(&snapshot->useSync);
if (snapshot->releaseFn)
snapshotRelease(snapshot, &releases[releaseCount++]);
if (snapshot->framebuffer)
glDeleteFramebuffers(1, &snapshot->framebuffer);
if (snapshot->texture)
glDeleteTextures(1, &snapshot->texture);
memset(snapshot, 0, sizeof(*snapshot));
}
for(int i = 0; i < ARRAY_LENGTH(this->images); ++i)
{
if (this->images[i].image != EGL_NO_IMAGE)
@@ -73,28 +158,19 @@ static void egl_texDMABUFCleanup(EGL_Texture * texture)
g_egl_dynProcs.eglDestroyImage(this->display, this->images[i].image);
this->images[i].image = EGL_NO_IMAGE;
}
if (this->images[i].sync)
{
glDeleteSync(this->images[i].sync);
this->images[i].sync = 0;
}
if (this->images[i].texture)
glDeleteTextures(1, &this->images[i].texture);
this->images[i].fd = -1;
this->images[i].texIndex = -1;
this->images[i].frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
this->images[i].texture = 0;
}
this->lastIndex = -1;
this->renderIndex = -1;
parent->rIndex = -1;
texture->frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
egl_texUtilFreeBuffers(parent->buf, parent->texCount);
if (parent->tex[0])
{
glDeleteTextures(parent->texCount, parent->tex);
egl_stateInvalidateShared();
}
LG_UNLOCK(parent->copyLock);
snapshotInvokeReleases(releases, releaseCount);
}
// dmabuf functions
@@ -109,11 +185,7 @@ static bool egl_texDMABUFInit(EGL_Texture ** texture, EGL_TexType type,
{
this->images[i].fd = -1;
this->images[i].image = EGL_NO_IMAGE;
this->images[i].sync = 0;
this->images[i].texIndex = -1;
this->images[i].frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
}
this->lastIndex = -1;
this->renderIndex = -1;
EGL_Texture * parent = &this->base.base;
@@ -126,6 +198,30 @@ static bool egl_texDMABUFInit(EGL_Texture ** texture, EGL_TexType type,
this->display = display;
if (!egl_shaderInit(&this->copyShader) ||
!egl_shaderCompile(this->copyShader,
b_shader_hdr_compose_vert, b_shader_hdr_compose_vert_size,
b_shader_downscale_linear_frag, b_shader_downscale_linear_frag_size,
true, NULL))
{
DEBUG_ERROR("Failed to initialize the DMABUF snapshot pass");
egl_texDMABUFFree(*texture);
*texture = NULL;
return false;
}
egl_shaderAssocTextures(this->copyShader, 1);
glGenSamplers(1, &this->copySampler);
glSamplerParameteri(this->copySampler,
GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glSamplerParameteri(this->copySampler,
GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glSamplerParameteri(this->copySampler,
GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(this->copySampler,
GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (!initDone)
{
const char * client_exts = eglQueryString(this->display, EGL_EXTENSIONS);
@@ -143,6 +239,10 @@ static void egl_texDMABUFFree(EGL_Texture * texture)
TexDMABUF * this = UPCAST(TexDMABUF , parent);
egl_texDMABUFCleanup(texture);
egl_modelFree(&this->copyModel);
egl_shaderFree(&this->copyShader);
if (this->copySampler)
glDeleteSamplers(1, &this->copySampler);
egl_texBufferFree(&parent->base);
free(this);
}
@@ -157,12 +257,47 @@ static bool texDMABUFSetup(EGL_Texture * texture)
this->pixFmt = EGL_PF_RGB_24_32;
this->width = texture->format.pitch / 4;
this->fourcc = DRM_FORMAT_ARGB8888;
this->format = GL_BGRA_EXT;
}
egl_texDMABUFCleanup(texture);
glGenTextures(parent->texCount, parent->tex);
if (!this->copyModel)
{
if (!egl_modelInit(&this->copyModel))
return false;
egl_modelSetDefault(this->copyModel, false);
egl_modelSetShader(this->copyModel, this->copyShader);
}
for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i)
{
struct Snapshot * snapshot = &this->snapshots[i];
glGenTextures(1, &snapshot->texture);
egl_stateBindTexture(0, GL_TEXTURE_2D, snapshot->texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, this->intFormat,
this->width, texture->format.height, 0,
GL_RGBA, this->dataType, NULL);
glGenFramebuffers(1, &snapshot->framebuffer);
egl_stateBindFramebuffer(snapshot->framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, snapshot->texture, 0);
glDrawBuffers(1, &(GLenum){ GL_COLOR_ATTACHMENT0 });
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
DEBUG_ERROR("Failed to create DMABUF snapshot framebuffer");
egl_stateBindFramebuffer(0);
egl_texDMABUFCleanup(texture);
return false;
}
}
egl_stateBindFramebuffer(0);
parent->rIndex = -1;
return true;
@@ -176,7 +311,29 @@ static bool egl_texDMABUFSetup(EGL_Texture * texture, const EGL_TexSetup * setup
this->pixFmt = texture->format.pixFmt;
this->width = texture->format.width;
this->fourcc = texture->format.fourcc;
this->format = texture->format.format;
this->dataType = GL_UNSIGNED_BYTE;
this->intFormat = GL_RGBA8;
switch (texture->format.pixFmt)
{
case EGL_PF_RGBA10:
this->intFormat = GL_RGB10_A2;
this->dataType = GL_UNSIGNED_INT_2_10_10_10_REV;
break;
case EGL_PF_RGBA16F:
this->intFormat = GL_RGBA16F;
this->dataType = GL_HALF_FLOAT;
break;
case EGL_PF_RGB_24:
/* A native RGB888 import is expanded by the snapshot shader. */
this->pixFmt = EGL_PF_RGBA;
break;
default:
break;
}
return texDMABUFSetup(texture);
}
@@ -220,6 +377,10 @@ static bool egl_texDMABUFUpdate(EGL_Texture * texture,
DEBUG_ASSERT(update->type == EGL_TEXTYPE_DMABUF);
/* Retire completed copies before deciding whether a snapshot slot must be
* waited on and coalesced. */
egl_texDMABUFPoll(texture);
struct FdImage *fdImage = NULL;
for (int i = 0; i < ARRAY_LENGTH(this->images); ++i)
if (this->images[i].fd == update->dmaFD)
@@ -242,14 +403,13 @@ static bool egl_texDMABUFUpdate(EGL_Texture * texture,
return false;
}
EGLImage image = fdImage->image;
if (unlikely(image == EGL_NO_IMAGE))
if (unlikely(fdImage->image == EGL_NO_IMAGE))
{
bool setup = false;
if (texture->format.pixFmt == EGL_PF_RGB_24 && has24BitSupport)
{
image = createImage(texture, update->dmaFD);
if (image == EGL_NO_IMAGE)
fdImage->image = createImage(texture, update->dmaFD);
if (fdImage->image == EGL_NO_IMAGE)
{
DEBUG_INFO("Using 24-bit in 32-bit for DMA");
has24BitSupport = false;
@@ -258,80 +418,184 @@ static bool egl_texDMABUFUpdate(EGL_Texture * texture,
}
if (!has24BitSupport && setup)
texDMABUFSetup(texture);
{
if (!texDMABUFSetup(texture))
return false;
fdImage = &this->images[0];
}
if (image == EGL_NO_IMAGE)
image = createImage(texture, update->dmaFD);
if (fdImage->image == EGL_NO_IMAGE)
fdImage->image = createImage(texture, update->dmaFD);
if (unlikely(image == EGL_NO_IMAGE))
if (unlikely(fdImage->image == EGL_NO_IMAGE))
{
DEBUG_EGL_ERROR("Failed to create EGLImage for DMA transfer");
return false;
}
fdImage->fd = update->dmaFD;
fdImage->image = image;
const int slot = (int)(fdImage - this->images);
fdImage->texIndex = slot;
GLsync sync = 0;
INTERLOCKED_SECTION(parent->copyLock,
{
egl_stateBindTexture(0, GL_TEXTURE_EXTERNAL_OES, parent->tex[slot]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glGenTextures(1, &fdImage->texture);
egl_stateBindTexture(0, GL_TEXTURE_EXTERNAL_OES, fdImage->texture);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES,
GL_TEXTURE_MAG_FILTER, GL_NEAREST);
g_egl_dynProcs.glEGLImageTargetTexture2DOES(
GL_TEXTURE_EXTERNAL_OES, image);
GL_TEXTURE_EXTERNAL_OES, fdImage->image);
}
sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
fdImage->sync = sync;
});
struct SnapshotRelease releases[2];
int releaseCount = 0;
LG_LOCK(parent->copyLock);
if (unlikely(!sync))
int index = -1;
for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i)
if (this->snapshots[i].frameToken == LG_RENDERER_FRAME_TOKEN_NONE)
{
DEBUG_GL_ERROR("Failed to create DMABUF sync");
index = i;
break;
}
if (index < 0)
{
/* Drop the oldest snapshot that is no longer selected. If the GPU is
* still finishing it, wait only when all snapshot slots are saturated. */
for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i)
if (i != this->renderIndex &&
(index < 0 || this->snapshots[i].frameToken <
this->snapshots[index].frameToken))
index = i;
if (unlikely(index < 0))
{
LG_UNLOCK(parent->copyLock);
DEBUG_ERROR("No reusable DMABUF snapshot slot");
return false;
}
/* Publish the texture attachment to the shared render context once. The
* backing memory is synchronized separately by framebuffer_wait(), so
* subsequent frames do not add any GL commands to this context. */
glFlush();
struct Snapshot * old = &this->snapshots[index];
waitSync(&old->copySync);
waitSync(&old->useSync);
if (old->releaseFn)
snapshotRelease(old, &releases[releaseCount++]);
old->frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
}
INTERLOCKED_SECTION(parent->copyLock,
struct Snapshot * snapshot = &this->snapshots[index];
egl_stateBindFramebuffer(snapshot->framebuffer);
egl_stateViewport(0, 0, this->width, texture->format.height);
egl_stateBlend(false);
egl_stateScissor(false);
glDisable(GL_DITHER);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
egl_stateBindSampler(0, this->copySampler);
egl_stateBindTexture(0, GL_TEXTURE_EXTERNAL_OES, fdImage->texture);
egl_shaderUse(this->copyShader);
egl_modelRender(this->copyModel);
snapshot->copySync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
if (unlikely(!snapshot->copySync))
{
fdImage->frameToken = update->frameToken;
this->lastIndex = (int)(fdImage - this->images);
});
DEBUG_GL_ERROR("Failed to fence DMABUF snapshot copy");
glFinish();
}
snapshot->frameToken = update->frameToken;
snapshot->releaseFn = update->releaseFn;
snapshot->releaseOpaque = update->releaseOpaque;
snapshot->releaseHandle = update->releaseHandle;
if (!snapshot->copySync && snapshot->releaseFn)
snapshotRelease(snapshot, &releases[releaseCount++]);
glFlush();
LG_UNLOCK(parent->copyLock);
snapshotInvokeReleases(releases, releaseCount);
return true;
}
static void egl_texDMABUFPoll(EGL_Texture * texture)
{
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
TexDMABUF * this = UPCAST(TexDMABUF , parent);
struct SnapshotRelease releases[EGL_DMABUF_SNAPSHOT_COUNT];
int releaseCount = 0;
LG_LOCK(parent->copyLock);
for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i)
{
struct Snapshot * snapshot = &this->snapshots[i];
if (snapshot->copySync)
{
const GLenum result = glClientWaitSync(snapshot->copySync, 0, 0);
if (result == GL_ALREADY_SIGNALED ||
result == GL_CONDITION_SATISFIED)
{
glDeleteSync(snapshot->copySync);
snapshot->copySync = 0;
if (snapshot->releaseFn)
snapshotRelease(snapshot, &releases[releaseCount++]);
}
else if (result == GL_WAIT_FAILED)
DEBUG_FATAL("Failed to poll DMABUF snapshot copy fence");
}
if (snapshot->useSync)
{
const GLenum result = glClientWaitSync(snapshot->useSync, 0, 0);
if (result == GL_ALREADY_SIGNALED ||
result == GL_CONDITION_SATISFIED)
{
glDeleteSync(snapshot->useSync);
snapshot->useSync = 0;
}
else if (result == GL_WAIT_FAILED)
DEBUG_FATAL("Failed to poll DMABUF snapshot use fence");
}
}
LG_UNLOCK(parent->copyLock);
snapshotInvokeReleases(releases, releaseCount);
}
static EGL_TexStatus egl_texDMABUFProcess(EGL_Texture * texture,
LG_RendererFrameToken frameTokenLimit)
{
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
TexDMABUF * this = UPCAST(TexDMABUF , parent);
egl_texDMABUFPoll(texture);
int index = -1;
bool haveImage = false;
bool updated = false;
INTERLOCKED_SECTION(parent->copyLock,
{
index = this->lastIndex;
haveImage = this->renderIndex >= 0;
if (index >= 0 && egl_textureFrameAllowed(
this->images[index].frameToken, frameTokenLimit))
for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i)
if (this->snapshots[i].frameToken != LG_RENDERER_FRAME_TOKEN_NONE &&
egl_textureFrameAllowed(
this->snapshots[i].frameToken, frameTokenLimit) &&
(index < 0 || this->snapshots[i].frameToken >
this->snapshots[index].frameToken))
index = i;
if (index >= 0)
{
const struct FdImage * pending = &this->images[index];
const struct Snapshot * pending = &this->snapshots[index];
if (this->renderIndex != index ||
texture->frameToken != pending->frameToken)
{
if (pending->copySync)
glWaitSync(pending->copySync, 0, GL_TIMEOUT_IGNORED);
this->renderIndex = index;
parent->rIndex = pending->texIndex;
parent->rIndex = index;
texture->frameToken = pending->frameToken;
updated = true;
haveImage = true;
@@ -351,33 +615,16 @@ static EGL_TexStatus egl_texDMABUFGet(EGL_Texture * texture, GLuint * tex,
TexDMABUF * this = UPCAST(TexDMABUF , parent);
int index = -1;
int texIndex = -1;
GLsync sync = 0;
INTERLOCKED_SECTION(parent->copyLock,
{
index = this->renderIndex;
if (index >= 0)
{
struct FdImage * cur = &this->images[index];
texIndex = cur->texIndex;
sync = cur->sync;
cur->sync = 0;
}
});
if (unlikely(index < 0))
return EGL_TEX_STATUS_NOTREADY;
if (sync)
{
/* Keep the cross-context dependency on the GPU timeline. Subsequent
* sampling waits for the import without delaying render submission. */
glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(sync);
}
*tex = parent->tex[texIndex];
*tex = this->snapshots[index].texture;
if (fmt)
*fmt = this->pixFmt;
@@ -393,10 +640,42 @@ static EGL_TexStatus egl_texDMABUFBind(EGL_Texture * texture, GLuint unit)
if ((status = egl_texDMABUFGet(texture, &tex, NULL)) != EGL_TEX_STATUS_OK)
return status;
egl_stateBindTexture(unit, GL_TEXTURE_EXTERNAL_OES, tex);
egl_stateBindTexture(unit, GL_TEXTURE_2D, tex);
return EGL_TEX_STATUS_OK;
}
static void egl_texDMABUFReset(EGL_Texture * texture)
{
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
TexDMABUF * this = UPCAST(TexDMABUF , parent);
egl_texDMABUFCleanup(texture);
egl_modelFree(&this->copyModel);
}
static void egl_texDMABUFMarkUsed(EGL_Texture * texture)
{
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
TexDMABUF * this = UPCAST(TexDMABUF , parent);
LG_LOCK(parent->copyLock);
if (this->renderIndex >= 0)
{
struct Snapshot * snapshot = &this->snapshots[this->renderIndex];
if (snapshot->useSync)
glDeleteSync(snapshot->useSync);
snapshot->useSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
if (unlikely(!snapshot->useSync))
{
DEBUG_GL_ERROR("Failed to fence DMABUF snapshot use");
glFinish();
}
else
glFlush();
}
LG_UNLOCK(parent->copyLock);
}
EGL_TextureOps EGL_TextureDMABUF =
{
.init = egl_texDMABUFInit,
@@ -405,5 +684,8 @@ EGL_TextureOps EGL_TextureDMABUF =
.update = egl_texDMABUFUpdate,
.process = egl_texDMABUFProcess,
.get = egl_texDMABUFGet,
.bind = egl_texDMABUFBind
.bind = egl_texDMABUFBind,
.reset = egl_texDMABUFReset,
.poll = egl_texDMABUFPoll,
.markUsed = egl_texDMABUFMarkUsed,
};

View File

@@ -396,10 +396,19 @@ bool opengl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat format
bool opengl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int dmaFd,
const FrameDamageRect * damage, int damageCount,
LG_RendererFrameToken frameToken)
LG_RendererFrameToken frameToken, LG_FrameReleaseFn releaseFn,
void * releaseOpaque, uint64_t releaseHandle)
{
struct Inst * this = UPCAST(struct Inst, renderer);
/* The legacy renderer retains the shared framebuffer until render. It does
* not support asynchronous DMA ownership. */
if (releaseFn)
return false;
(void)releaseOpaque;
(void)releaseHandle;
LG_LOCK(this->frameLock);
this->frame = frame;
this->pendingFrameToken = frameToken;

View File

@@ -605,7 +605,8 @@ static int renderThread(void * unused)
return 1;
}
if (g_state.lgr->ops.supports && !RENDERER(supports, LG_SUPPORTS_DMABUF))
if (!g_state.lgr->ops.supports ||
!RENDERER(supports, LG_SUPPORTS_DMABUF))
g_state.useDMA = false;
/* start up the fps timer */
@@ -935,7 +936,11 @@ int main_frameThread(void * unused)
if (status != LG_TRANSPORT_OK)
{
if (status == LG_TRANSPORT_TIMEOUT || status == LG_TRANSPORT_UNAVAILABLE)
{
if (g_state.lgr->ops.onFramePoll)
RENDERER(onFramePoll);
continue;
}
if (status == LG_TRANSPORT_DISCONNECTED)
app_setState(APP_STATE_RESTART);
else if (status == LG_TRANSPORT_END)
@@ -1109,8 +1114,13 @@ int main_frameThread(void * unused)
const LG_RendererFrameToken frameToken = frameTimingReserve();
g_state.frameImportTime = 0;
g_state.frameImportWaitTime = 0;
const bool rendererOwnsFrame = frame.dmaFD >= 0 && frame.releaseFn;
if (!RENDERER(onFrame, frame.framebuffer, frame.dmaFD,
frame.damageRects, damageCount, frameToken))
frame.damageRects, damageCount, frameToken,
rendererOwnsFrame ? frame.releaseFn : NULL,
rendererOwnsFrame ? frame.releaseOpaque : NULL,
rendererOwnsFrame ? frame.releaseHandle : 0))
{
frameTimingCancel(frameToken);
g_state.transportOps->releaseFrame(g_state.transport, &frame);
@@ -1119,6 +1129,14 @@ int main_frameThread(void * unused)
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. */
LG_TransportFrameTiming timing = {};
if (g_state.transportOps->getFrameTiming)
g_state.transportOps->getFrameTiming(
g_state.transport, &frame, &timing);
const uint64_t queueStart = nanotime();
atomic_fetch_add_explicit(&g_state.frameCount, 1, memory_order_relaxed);
#ifdef ENABLE_TESTS
@@ -1142,11 +1160,6 @@ int main_frameThread(void * unused)
lgSignalEvent(g_state.frameEvent);
}
LG_TransportFrameTiming timing = {};
if (g_state.transportOps->getFrameTiming)
g_state.transportOps->getFrameTiming(
g_state.transport, &frame, &timing);
if ((frame.flags & LG_TRANSPORT_FRAME_REQUEST_ACTIVATION) &&
g_params.requestActivation)
g_state.ds->requestActivation();
@@ -1165,18 +1178,27 @@ int main_frameThread(void * unused)
frameTimingFinishFrame(frameToken, &timing);
if (!rendererOwnsFrame)
g_state.transportOps->releaseFrame(g_state.transport, &frame);
app_useSpiceDisplay(false);
}
if (g_state.transportOps->stopFrame)
g_state.transportOps->stopFrame(g_state.transport);
RENDERER(onRestart);
if (app_getState() != APP_STATE_SHUTDOWN)
if (!app_useSpiceDisplay(true))
overlaySplash_show(true);
/* Queue the fallback source before clearing desktop snapshots. The render
* thread processes this transition under lgrLock before its next render, so
* restart cannot expose an empty desktop frame between the two operations. */
LG_LOCK(g_state.lgrLock);
RENDERER(onRestart);
LG_UNLOCK(g_state.lgrLock);
/* Renderer reset requests release for every asynchronous DMA snapshot.
* Drain those requests before the transport unsubscribes or reconnects. */
if (g_state.transportOps->stopFrame)
g_state.transportOps->stopFrame(g_state.transport);
return 0;
}

View File

@@ -45,28 +45,44 @@ struct DMAFrameInfo
int fd;
};
#define LGMP_FRAME_LEASE_COUNT (LGMP_Q_FRAME_LEN + 1)
struct LGMPFrameLease
{
PLGMPClientQueue * subscription;
PLGMPClientQueue queue;
const KVMFRFrame * frame;
LG_TransportFrameFormat format;
uint64_t handle;
uint32_t generation;
bool releaseRequested;
bool active;
};
struct LG_Transport
{
struct IVSHMEM shm;
PLGMPClient client;
PLGMPClientQueue frameQueue;
PLGMPClientQueue ownerFrameQueue[LGMP_Q_FRAME_LEN];
PLGMPClientQueue pendingFrameQueue;
PLGMPClientQueue pointerQueue;
LG_Lock frameLock;
LG_Lock pointerLock;
unsigned cursorPollInterval;
unsigned framePollInterval;
bool allowDMA;
bool connected;
bool framePending;
bool frameStopRequested;
bool frameScheduleSupported;
const KVMFRFrame * pendingFrame;
uint64_t frameLeaseHandle;
uint32_t frameGeneration;
uint32_t clientID;
uint32_t frameSerial;
bool frameSerialValid;
bool formatValid;
LG_TransportFrameFormat format;
struct LGMPFrameLease frameLease[LGMP_FRAME_LEASE_COUNT];
struct DMAFrameInfo dma[LGMP_Q_FRAME_BUFFER_LEN];
uint8_t * pointerData;
@@ -187,11 +203,18 @@ static bool lgmp_create(LG_Transport ** result)
for (unsigned i = 0; i < LGMP_Q_FRAME_BUFFER_LEN; ++i)
this->dma[i].fd = -1;
LG_LOCK_INIT(this->frameLock);
LG_LOCK_INIT(this->pointerLock);
this->frameGeneration = 1;
this->frameLease[0].subscription = &this->frameQueue;
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
this->frameLease[i + 1].subscription = &this->ownerFrameQueue[i];
if (!ivshmemOpenDev(&this->shm,
option_get_string("lgmp", "shmDevice")))
{
LG_LOCK_FREE(this->frameLock);
LG_LOCK_FREE(this->pointerLock);
free(this);
return false;
@@ -203,6 +226,7 @@ static bool lgmp_create(LG_Transport ** result)
{
DEBUG_ERROR("lgmpClientInit failed: %s", lgmpStatusString(status));
ivshmemClose(&this->shm);
LG_LOCK_FREE(this->frameLock);
LG_LOCK_FREE(this->pointerLock);
free(this);
return false;
@@ -212,49 +236,96 @@ static bool lgmp_create(LG_Transport ** result)
return true;
}
static void lgmp_stopFrame(struct LG_Transport * this)
static void lgmp_unsubscribeFrameLease(struct LGMPFrameLease * lease)
{
if (this->framePending && this->pendingFrameQueue)
{
const LGMP_STATUS status =
lgmpClientMessageDone(this->pendingFrameQueue);
if (status != LGMP_OK)
DEBUG_WARN("Failed to release pending LGMP frame: %s",
lgmpStatusString(status));
}
this->framePending = false;
this->pendingFrame = NULL;
this->pendingFrameQueue = NULL;
if (lease->active || !*lease->subscription)
return;
LGMP_STATUS status = lgmpClientUnsubscribe(&this->frameQueue);
const LGMP_STATUS status = lgmpClientUnsubscribe(lease->subscription);
if (status != LGMP_OK)
{
DEBUG_WARN("Failed to unsubscribe from the shared LGMP frame queue: %s",
if (status != LGMP_ERR_QUEUE_TIMEOUT &&
status != LGMP_ERR_QUEUE_UNSUBSCRIBED)
DEBUG_WARN("Failed to unsubscribe from LGMP frame queue: %s",
lgmpStatusString(status));
this->frameQueue = NULL;
*lease->subscription = NULL;
}
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
}
static struct LGMPFrameLease * lgmp_findFrameLeaseLocked(
struct LG_Transport * this, uint64_t handle)
{
for (unsigned i = 0; i < LGMP_FRAME_LEASE_COUNT; ++i)
if (this->frameLease[i].active &&
this->frameLease[i].handle == handle)
return &this->frameLease[i];
return NULL;
}
static void lgmp_finishFrameLeaseLocked(struct LG_Transport * this,
struct LGMPFrameLease * lease)
{
if (lease->generation == this->frameGeneration && lease->queue &&
*lease->subscription == lease->queue)
{
status = lgmpClientUnsubscribe(&this->ownerFrameQueue[i]);
if (status != LGMP_OK)
{
DEBUG_WARN("Failed to unsubscribe from owner LGMP frame queue %u: %s",
i, lgmpStatusString(status));
this->ownerFrameQueue[i] = NULL;
}
const LGMP_STATUS status = lgmpClientMessageDone(lease->queue);
if (status == LGMP_ERR_INVALID_SESSION ||
status == LGMP_ERR_QUEUE_TIMEOUT ||
status == LGMP_ERR_QUEUE_UNSUBSCRIBED)
*lease->subscription = NULL;
else if (status != LGMP_OK)
DEBUG_WARN("Failed to release LGMP frame: %s",
lgmpStatusString(status));
}
lease->queue = NULL;
lease->frame = NULL;
lease->handle = 0;
lease->releaseRequested = false;
lease->active = false;
if (this->frameStopRequested)
lgmp_unsubscribeFrameLease(lease);
}
static void lgmp_drainFrameLeasesLocked(struct LG_Transport * this)
{
for (unsigned i = 0; i < LGMP_FRAME_LEASE_COUNT; ++i)
if (this->frameLease[i].active &&
this->frameLease[i].releaseRequested)
lgmp_finishFrameLeaseLocked(this, &this->frameLease[i]);
}
static void lgmp_stopFrameLocked(struct LG_Transport * this)
{
this->frameStopRequested = true;
/* stopFrame ends every lease by contract. The renderer is drained before
* this call, while direct transport users may still hold a returned frame. */
for (unsigned i = 0; i < LGMP_FRAME_LEASE_COUNT; ++i)
if (this->frameLease[i].active)
lgmp_finishFrameLeaseLocked(this, &this->frameLease[i]);
for (unsigned i = 0; i < LGMP_FRAME_LEASE_COUNT; ++i)
lgmp_unsubscribeFrameLease(&this->frameLease[i]);
this->frameSerial = 0;
this->frameSerialValid = false;
this->formatValid = false;
}
static void lgmp_stopFrame(struct LG_Transport * this)
{
LG_LOCK(this->frameLock);
lgmp_stopFrameLocked(this);
LG_UNLOCK(this->frameLock);
}
static void lgmp_stopPointer(struct LG_Transport * this)
{
LG_LOCK(this->pointerLock);
const LGMP_STATUS status = lgmpClientUnsubscribe(&this->pointerQueue);
if (status != LGMP_OK)
{
if (status != LGMP_ERR_QUEUE_TIMEOUT &&
status != LGMP_ERR_QUEUE_UNSUBSCRIBED)
DEBUG_WARN("Failed to unsubscribe from the LGMP pointer queue: %s",
lgmpStatusString(status));
this->pointerQueue = NULL;
@@ -291,6 +362,7 @@ static void lgmp_destroy(LG_Transport ** transport)
free(this->pointerData);
lgmpClientFree(&this->client);
ivshmemClose(&this->shm);
LG_LOCK_FREE(this->frameLock);
LG_LOCK_FREE(this->pointerLock);
free(this);
*transport = NULL;
@@ -388,12 +460,18 @@ static LG_TransportStatus lgmp_connect(LG_Transport * this,
case LGMP_OK:
if (!lgmp_parseSession(data, size, session))
return LG_TRANSPORT_INVALID_VERSION;
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->frameSerial = 0;
this->frameSerialValid = false;
this->formatValid = false;
LG_UNLOCK(this->frameLock);
return LG_TRANSPORT_OK;
case LGMP_ERR_INVALID_VERSION:
@@ -412,10 +490,19 @@ static LG_TransportStatus lgmp_connect(LG_Transport * this,
static void lgmp_disconnect(LG_Transport * this)
{
lgmp_closeQueues(this);
lgmp_closeDMA(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;
this->frameScheduleSupported = false;
this->clientID = 0;
LG_UNLOCK(this->frameLock);
lgmp_closeDMA(this);
}
static bool lgmp_sessionValid(LG_Transport * this)
@@ -459,9 +546,13 @@ static LG_TransportStatus lgmp_subscribe(PLGMPClient client, uint32_t id,
}
}
static LG_TransportStatus lgmp_process(PLGMPClientQueue queue,
static LG_TransportStatus lgmp_process(PLGMPClientQueue * subscription,
unsigned interval, LGMPMessage * message)
{
PLGMPClientQueue queue = *subscription;
if (!queue)
return LG_TRANSPORT_TIMEOUT;
LGMP_STATUS status = lgmpClientProcess(queue, message);
switch (status)
{
@@ -471,7 +562,12 @@ static LG_TransportStatus lgmp_process(PLGMPClientQueue queue,
if (interval)
usleep(interval);
return LG_TRANSPORT_TIMEOUT;
case LGMP_ERR_QUEUE_TIMEOUT:
case LGMP_ERR_QUEUE_UNSUBSCRIBED:
*subscription = NULL;
return LG_TRANSPORT_TIMEOUT;
case LGMP_ERR_INVALID_SESSION:
*subscription = NULL;
return LG_TRANSPORT_DISCONNECTED;
default:
DEBUG_ERROR("lgmpClientProcess failed: %s", lgmpStatusString(status));
@@ -484,12 +580,18 @@ struct LGMPFrameMessage
PLGMPClientQueue queue;
LGMPMessage message;
const KVMFRFrame * frame;
struct LGMPFrameLease * lease;
bool owner;
};
static LG_TransportStatus lgmp_pollFrameQueue(PLGMPClientQueue queue,
static LG_TransportStatus lgmp_pollFrameQueue(
PLGMPClientQueue * subscription,
bool owner, struct LGMPFrameMessage * result)
{
PLGMPClientQueue queue = *subscription;
if (!queue)
return LG_TRANSPORT_TIMEOUT;
const LGMP_STATUS advance = lgmpClientAdvanceToLast(queue);
switch (advance)
{
@@ -497,7 +599,12 @@ static LG_TransportStatus lgmp_pollFrameQueue(PLGMPClientQueue queue,
break;
case LGMP_ERR_QUEUE_EMPTY:
break;
case LGMP_ERR_QUEUE_TIMEOUT:
case LGMP_ERR_QUEUE_UNSUBSCRIBED:
*subscription = NULL;
return LG_TRANSPORT_TIMEOUT;
case LGMP_ERR_INVALID_SESSION:
*subscription = NULL;
return LG_TRANSPORT_DISCONNECTED;
default:
DEBUG_ERROR("lgmpClientAdvanceToLast failed: %s",
@@ -505,7 +612,8 @@ static LG_TransportStatus lgmp_pollFrameQueue(PLGMPClientQueue queue,
return LG_TRANSPORT_ERROR;
}
const LG_TransportStatus status = lgmp_process(queue, 0, &result->message);
const LG_TransportStatus status =
lgmp_process(subscription, 0, &result->message);
if (status == LG_TRANSPORT_OK)
{
result->queue = queue;
@@ -527,13 +635,44 @@ static LG_TransportStatus lgmp_doneFrameMessage(
if (status == LGMP_OK)
return LG_TRANSPORT_OK;
if (status == LGMP_ERR_INVALID_SESSION)
{
*message->lease->subscription = NULL;
return LG_TRANSPORT_DISCONNECTED;
}
if (status == LGMP_ERR_QUEUE_TIMEOUT ||
status == LGMP_ERR_QUEUE_UNSUBSCRIBED)
{
*message->lease->subscription = NULL;
return LG_TRANSPORT_OK;
}
DEBUG_WARN("Failed to release discarded LGMP frame: %s",
lgmpStatusString(status));
return LG_TRANSPORT_ERROR;
}
static LG_TransportStatus lgmp_donePointerMessage(LG_Transport * this)
{
const LGMP_STATUS status = lgmpClientMessageDone(this->pointerQueue);
if (status == LGMP_OK)
return LG_TRANSPORT_OK;
if (status == LGMP_ERR_INVALID_SESSION)
{
this->pointerQueue = NULL;
return LG_TRANSPORT_DISCONNECTED;
}
if (status == LGMP_ERR_QUEUE_TIMEOUT ||
status == LGMP_ERR_QUEUE_UNSUBSCRIBED)
{
this->pointerQueue = NULL;
return LG_TRANSPORT_OK;
}
DEBUG_WARN("Failed to release LGMP pointer message: %s",
lgmpStatusString(status));
return LG_TRANSPORT_ERROR;
}
static void lgmp_mergeFrameStatus(LG_TransportStatus status,
LG_TransportStatus * result)
{
@@ -624,13 +763,17 @@ static int lgmp_getDMA(struct LG_Transport * this, const KVMFRFrame * frame,
return dma->fd;
}
static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA,
static void lgmp_releaseFrameLease(void * opaque, uint64_t handle);
static LG_TransportStatus lgmp_nextFrameLocked(LG_Transport * this,
bool useDMA,
LG_TransportFrame * result)
{
lgmp_drainFrameLeasesLocked(this);
if (!this->connected)
return LG_TRANSPORT_DISCONNECTED;
if (this->framePending)
return LG_TRANSPORT_ERROR;
this->frameStopRequested = false;
if (this->frameScheduleSupported)
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
@@ -653,14 +796,16 @@ static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA,
LG_TransportStatus ownerStatus[LGMP_Q_FRAME_LEN];
/* Select the newest frame across the shared and owner delivery lanes. */
if (this->frameQueue)
sharedStatus = lgmp_pollFrameQueue(this->frameQueue, false, &shared);
shared.lease = &this->frameLease[0];
if (this->frameQueue && !shared.lease->active)
sharedStatus = lgmp_pollFrameQueue(&this->frameQueue, false, &shared);
for (unsigned i = 0; i < LGMP_Q_FRAME_LEN; ++i)
{
owner[i].lease = &this->frameLease[i + 1];
ownerStatus[i] = LG_TRANSPORT_TIMEOUT;
if (this->ownerFrameQueue[i])
if (this->ownerFrameQueue[i] && !owner[i].lease->active)
ownerStatus[i] = lgmp_pollFrameQueue(
this->ownerFrameQueue[i], true, &owner[i]);
&this->ownerFrameQueue[i], true, &owner[i]);
}
LG_TransportStatus failure = LG_TRANSPORT_OK;
@@ -718,11 +863,7 @@ static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA,
lgmp_selectNewestFrameMessage(&owner[i], &selected);
if (!selected)
{
if (this->framePollInterval)
usleep(this->framePollInterval);
return malformed ? LG_TRANSPORT_ERROR : LG_TRANSPORT_TIMEOUT;
}
if (selected != &shared)
releaseFailure = lgmp_doneFrameMessage(&shared);
@@ -811,7 +952,9 @@ static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA,
}
this->formatValid = true;
}
result->format = format;
struct LGMPFrameLease * lease = selected->lease;
memcpy(&lease->format, format, sizeof(lease->format));
result->format = &lease->format;
result->framebuffer = (const FrameBuffer *)((const uint8_t *)frame +
frame->offset);
@@ -837,12 +980,33 @@ static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA,
else if (!fullDamage)
DEBUG_WARN("Invalid damage rectangles, forcing a full update");
this->framePending = true;
this->pendingFrame = frame;
this->pendingFrameQueue = selected->queue;
lease->queue = selected->queue;
lease->frame = frame;
if (++this->frameLeaseHandle == 0)
++this->frameLeaseHandle;
lease->handle = this->frameLeaseHandle;
lease->generation = this->frameGeneration;
lease->releaseRequested = false;
lease->active = true;
result->releaseFn = lgmp_releaseFrameLease;
result->releaseOpaque = this;
result->releaseHandle = lease->handle;
return LG_TRANSPORT_OK;
}
static LG_TransportStatus lgmp_nextFrame(LG_Transport * this, bool useDMA,
LG_TransportFrame * result)
{
LG_LOCK(this->frameLock);
const LG_TransportStatus status =
lgmp_nextFrameLocked(this, useDMA, result);
LG_UNLOCK(this->frameLock);
if ((status == LG_TRANSPORT_TIMEOUT ||
status == LG_TRANSPORT_UNAVAILABLE) && this->framePollInterval)
usleep(this->framePollInterval);
return status;
}
static bool lgmp_frameTimingReady(const KVMFRFrame * frame)
{
return __atomic_load_n(&frame->timingValid, __ATOMIC_ACQUIRE) &&
@@ -853,38 +1017,70 @@ static void lgmp_getFrameTiming(LG_Transport * this,
const LG_TransportFrame * frame, LG_TransportFrameTiming * timing)
{
memset(timing, 0, sizeof(*timing));
if (!this->framePending || !this->pendingFrame ||
this->pendingFrame->frameSerial != frame->serial)
LG_LOCK(this->frameLock);
struct LGMPFrameLease * lease =
lgmp_findFrameLeaseLocked(this, frame->releaseHandle);
if (!lease ||
lease->generation != this->frameGeneration || !lease->frame ||
lease->frame->frameSerial != frame->serial)
{
LG_UNLOCK(this->frameLock);
return;
}
/* 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
* the frame-acquisition thread. */
for (unsigned i = 0;
!lgmp_frameTimingReady(this->pendingFrame) &&
!lgmp_frameTimingReady(lease->frame) &&
i < LGMP_TIMING_SPIN_COUNT;
++i)
{
}
if (!lgmp_frameTimingReady(this->pendingFrame))
if (!lgmp_frameTimingReady(lease->frame))
{
LG_UNLOCK(this->frameLock);
return;
}
timing->valid = true;
timing->captureTime = this->pendingFrame->captureTime;
timing->postProcessTime = this->pendingFrame->postProcessTime;
timing->copyTime = this->pendingFrame->copyTime;
timing->readyTime = this->pendingFrame->readyTime;
timing->captureTime = lease->frame->captureTime;
timing->postProcessTime = lease->frame->postProcessTime;
timing->copyTime = lease->frame->copyTime;
timing->readyTime = lease->frame->readyTime;
LG_UNLOCK(this->frameLock);
}
static void lgmp_releaseFrameLease(void * opaque, uint64_t handle)
{
LG_Transport * this = opaque;
LG_LOCK(this->frameLock);
struct LGMPFrameLease * lease = lgmp_findFrameLeaseLocked(this, handle);
if (lease)
lease->releaseRequested = true;
LG_UNLOCK(this->frameLock);
}
static void lgmp_releaseFrame(LG_Transport * this, LG_TransportFrame * frame)
{
if (this->framePending && this->pendingFrameQueue)
lgmpClientMessageDone(this->pendingFrameQueue);
this->framePending = false;
this->pendingFrame = NULL;
this->pendingFrameQueue = NULL;
if (frame->releaseFn)
{
LG_LOCK(this->frameLock);
struct LGMPFrameLease * lease =
lgmp_findFrameLeaseLocked(this, frame->releaseHandle);
if (lease)
lgmp_finishFrameLeaseLocked(this, lease);
LG_UNLOCK(this->frameLock);
frame->releaseFn = NULL;
frame->releaseOpaque = NULL;
frame->releaseHandle = 0;
}
memset(frame, 0, sizeof(*frame));
}
@@ -910,14 +1106,15 @@ static LG_TransportStatus lgmp_nextPointer(LG_Transport * this,
if (status == LG_TRANSPORT_OK)
{
LGMPMessage message;
status = lgmp_process(this->pointerQueue, this->cursorPollInterval,
status = lgmp_process(&this->pointerQueue, this->cursorPollInterval,
&message);
if (status == LG_TRANSPORT_OK)
{
if (message.size < sizeof(KVMFRCursor))
{
lgmpClientMessageDone(this->pointerQueue);
status = LG_TRANSPORT_ERROR;
const LG_TransportStatus done = lgmp_donePointerMessage(this);
status = done == LG_TRANSPORT_DISCONNECTED ?
done : LG_TRANSPORT_ERROR;
}
else
{
@@ -930,8 +1127,9 @@ static LG_TransportStatus lgmp_nextPointer(LG_Transport * this,
if (shapeSize > message.size - sizeof(*cursor) ||
transformSize > message.size - sizeof(*cursor) - shapeSize)
{
lgmpClientMessageDone(this->pointerQueue);
status = LG_TRANSPORT_ERROR;
const LG_TransportStatus done = lgmp_donePointerMessage(this);
status = done == LG_TRANSPORT_DISCONNECTED ?
done : LG_TRANSPORT_ERROR;
}
else
{
@@ -952,7 +1150,9 @@ static LG_TransportStatus lgmp_nextPointer(LG_Transport * this,
memcpy(this->pointerData, message.mem, needed);
pointerFlags = (uint32_t)message.udata;
}
lgmpClientMessageDone(this->pointerQueue);
const LG_TransportStatus done = lgmp_donePointerMessage(this);
if (status == LG_TRANSPORT_OK)
status = done;
}
}
}

View File

@@ -23,6 +23,8 @@
#include <stdint.h>
typedef void (*LG_FrameReleaseFn)(void * opaque, uint64_t handle);
struct Point
{
int x, y;