[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

@@ -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;
LG_RendererFrameToken frameToken;
int fd;
EGLImage image;
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
@@ -42,17 +61,29 @@ typedef struct TexDMABUF
EGLDisplay display;
struct FdImage images[EGL_TEX_BUFFER_MAX];
int lastIndex;
int renderIndex;
struct FdImage images[EGL_TEX_BUFFER_MAX];
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;
}
this->images[i].fd = -1;
this->images[i].texIndex = -1;
this->images[i].frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
if (this->images[i].texture)
glDeleteTextures(1, &this->images[i].texture);
this->images[i].fd = -1;
this->images[i].texture = 0;
}
this->lastIndex = -1;
this->renderIndex = -1;
parent->rIndex = -1;
texture->frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
this->renderIndex = -1;
parent->rIndex = -1;
texture->frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
egl_stateInvalidateShared();
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
@@ -107,13 +183,9 @@ static bool egl_texDMABUFInit(EGL_Texture ** texture, EGL_TexType type,
for(int i = 0; i < ARRAY_LENGTH(this->images); ++i)
{
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->images[i].fd = -1;
this->images[i].image = EGL_NO_IMAGE;
}
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;
@@ -173,10 +308,32 @@ static bool egl_texDMABUFSetup(EGL_Texture * texture, const EGL_TexSetup * setup
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
TexDMABUF * this = UPCAST(TexDMABUF , parent);
this->pixFmt = texture->format.pixFmt;
this->width = texture->format.width;
this->fourcc = texture->format.fourcc;
this->format = texture->format.format;
this->pixFmt = texture->format.pixFmt;
this->width = texture->format.width;
this->fourcc = texture->format.fourcc;
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;
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, fdImage->image);
}
const int slot = (int)(fdImage - this->images);
fdImage->texIndex = slot;
GLsync sync = 0;
INTERLOCKED_SECTION(parent->copyLock,
struct SnapshotRelease releases[2];
int releaseCount = 0;
LG_LOCK(parent->copyLock);
int index = -1;
for (int i = 0; i < ARRAY_LENGTH(this->snapshots); ++i)
if (this->snapshots[i].frameToken == LG_RENDERER_FRAME_TOKEN_NONE)
{
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);
g_egl_dynProcs.glEGLImageTargetTexture2DOES(
GL_TEXTURE_EXTERNAL_OES, image);
index = i;
break;
}
sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
fdImage->sync = sync;
});
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(!sync))
if (unlikely(index < 0))
{
DEBUG_GL_ERROR("Failed to create DMABUF sync");
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;
@@ -350,34 +614,17 @@ static EGL_TexStatus egl_texDMABUFGet(EGL_Texture * texture, GLuint * tex,
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
TexDMABUF * this = UPCAST(TexDMABUF , parent);
int index = -1;
int texIndex = -1;
GLsync sync = 0;
int index = -1;
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,
};